PK!>悞NotFoundExceptionInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A set is a collection that contains no duplicate elements. * * Great care must be exercised if mutable objects are used as set elements. * The behavior of a set is not specified if the value of an object is changed * in a manner that affects equals comparisons while the object is an element in * the set. * * Example usage: * * ``` php * $foo = new \My\Foo(); * $set = new Set(\My\Foo::class); * * $set->add($foo); // returns TRUE, the element don't exists * $set->add($foo); // returns FALSE, the element already exists * * $bar = new \My\Foo(); * $set->add($bar); // returns TRUE, $bar !== $foo * ``` * * @template T * @extends AbstractSet */ class Set extends AbstractSet { /** * The type of elements stored in this set * * A set's type is immutable. For this reason, this property is private. * * @var string */ private $setType; /** * Constructs a set object of the specified type, optionally with the * specified data. * * @param string $setType The type (FQCN) associated with this set. * @param array $data The initial items to store in the set. */ public function __construct(string $setType, array $data = []) { $this->setType = $setType; parent::__construct($data); } public function getType(): string { return $this->setType; } } PK!)AbstractArray.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use ArrayIterator; use Traversable; use function serialize; use function unserialize; /** * This class provides a basic implementation of `ArrayInterface`, to minimize * the effort required to implement this interface. * * @template T * @implements ArrayInterface */ abstract class AbstractArray implements ArrayInterface { /** * The items of this array. * * @var array */ protected $data = []; /** * Constructs a new array object. * * @param array $data The initial items to add to this array. */ public function __construct(array $data = []) { // Invoke offsetSet() for each value added; in this way, sub-classes // may provide additional logic about values added to the array object. foreach ($data as $key => $value) { $this[$key] = $value; } } /** * Returns an iterator for this array. * * @link http://php.net/manual/en/iteratoraggregate.getiterator.php IteratorAggregate::getIterator() * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->data); } /** * Returns `true` if the given offset exists in this array. * * @link http://php.net/manual/en/arrayaccess.offsetexists.php ArrayAccess::offsetExists() * * @param array-key $offset The offset to check. */ public function offsetExists($offset): bool { return isset($this->data[$offset]); } /** * Returns the value at the specified offset. * * @link http://php.net/manual/en/arrayaccess.offsetget.php ArrayAccess::offsetGet() * * @param array-key $offset The offset for which a value should be returned. * * @return T|null the value stored at the offset, or null if the offset * does not exist. * * @psalm-suppress InvalidAttribute */ #[\ReturnTypeWillChange] // phpcs:ignore public function offsetGet($offset) { return $this->data[$offset] ?? null; } /** * Sets the given value to the given offset in the array. * * @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet() * * @param array-key|null $offset The offset to set. If `null`, the value may be * set at a numerically-indexed offset. * @param T $value The value to set at the given offset. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offsetSet($offset, $value): void { if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; } } /** * Removes the given offset and its value from the array. * * @link http://php.net/manual/en/arrayaccess.offsetunset.php ArrayAccess::offsetUnset() * * @param array-key $offset The offset to remove from the array. */ public function offsetUnset($offset): void { unset($this->data[$offset]); } /** * Returns a serialized string representation of this array object. * * @deprecated The Serializable interface will go away in PHP 9. * * @link http://php.net/manual/en/serializable.serialize.php Serializable::serialize() * * @return string a PHP serialized string. */ public function serialize(): string { return serialize($this->data); } /** * Returns data suitable for PHP serialization. * * @link https://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.serialize * @link https://www.php.net/serialize * * @return array */ public function __serialize(): array { return $this->data; } /** * Converts a serialized string representation into an instance object. * * @deprecated The Serializable interface will go away in PHP 9. * * @link http://php.net/manual/en/serializable.unserialize.php Serializable::unserialize() * * @param string $serialized A PHP serialized string to unserialize. * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, ['allowed_classes' => false]); $this->data = $data; } /** * Adds unserialized data to the object. * * @param array $data */ public function __unserialize(array $data): void { $this->data = $data; } /** * Returns the number of items in this array. * * @link http://php.net/manual/en/countable.count.php Countable::count() */ public function count(): int { return count($this->data); } public function clear(): void { $this->data = []; } /** * @inheritDoc */ public function toArray(): array { return $this->data; } public function isEmpty(): bool { return count($this->data) === 0; } } PK!KDCollectionInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A collection represents a group of objects, known as its elements. * * Some collections allow duplicate elements and others do not. Some are ordered * and others unordered. * * @template T * @extends ArrayInterface */ interface CollectionInterface extends ArrayInterface { /** * Ascending sort type. */ public const SORT_ASC = 'asc'; /** * Descending sort type. */ public const SORT_DESC = 'desc'; /** * Ensures that this collection contains the specified element (optional * operation). * * Returns `true` if this collection changed as a result of the call. * (Returns `false` if this collection does not permit duplicates and * already contains the specified element.) * * Collections that support this operation may place limitations on what * elements may be added to this collection. In particular, some * collections will refuse to add `null` elements, and others will impose * restrictions on the type of elements that may be added. Collection * classes should clearly specify in their documentation any restrictions * on what elements may be added. * * If a collection refuses to add a particular element for any reason other * than that it already contains the element, it must throw an exception * (rather than returning `false`). This preserves the invariant that a * collection always contains the specified element after this call returns. * * @param T $element The element to add to the collection. * * @return bool `true` if this collection changed as a result of the call. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function add($element): bool; /** * Returns `true` if this collection contains the specified element. * * @param T $element The element to check whether the collection contains. * @param bool $strict Whether to perform a strict type check on the value. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function contains($element, bool $strict = true): bool; /** * Returns the type associated with this collection. */ public function getType(): string; /** * Removes a single instance of the specified element from this collection, * if it is present. * * @param T $element The element to remove from the collection. * * @return bool `true` if an element was removed as a result of this call. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function remove($element): bool; /** * Returns the values from the given property or method. * * @param string $propertyOrMethod The property or method name to filter by. * * @return list */ public function column(string $propertyOrMethod): array; /** * Returns the first item of the collection. * * @return T */ public function first(); /** * Returns the last item of the collection. * * @return T */ public function last(); /** * Sort the collection by a property or method with the given sort order. * * This will always leave the original collection untouched and will return * a new one. * * @param string $propertyOrMethod The property or method to sort by. * @param string $order The sort order for the resulting collection (one of * this interface's `SORT_*` constants). * * @return CollectionInterface */ public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self; /** * Filter out items of the collection which don't match the criteria of * given callback. * * This will always leave the original collection untouched and will return * a new one. * * See the {@link http://php.net/manual/en/function.array-filter.php PHP array_filter() documentation} * for examples of how the `$callback` parameter works. * * @param callable(T):bool $callback A callable to use for filtering elements. * * @return CollectionInterface */ public function filter(callable $callback): self; /** * Create a new collection where items match the criteria of given callback. * * This will always leave the original collection untouched and will return * a new one. * * @param string $propertyOrMethod The property or method to evaluate. * @param mixed $value The value to match. * * @return CollectionInterface */ public function where(string $propertyOrMethod, $value): self; /** * Apply a given callback method on each item of the collection. * * This will always leave the original collection untouched. The new * collection is created by mapping the callback to each item of the * original collection. * * See the {@link http://php.net/manual/en/function.array-map.php PHP array_map() documentation} * for examples of how the `$callback` parameter works. * * @param callable(T):TCallbackReturn $callback A callable to apply to each * item of the collection. * * @return CollectionInterface * * @template TCallbackReturn */ public function map(callable $callback): self; /** * Create a new collection with divergent items between current and given * collection. * * @param CollectionInterface $other The collection to check for divergent * items. * * @return CollectionInterface */ public function diff(CollectionInterface $other): self; /** * Create a new collection with intersecting item between current and given * collection. * * @param CollectionInterface $other The collection to check for * intersecting items. * * @return CollectionInterface */ public function intersect(CollectionInterface $other): self; /** * Merge current items and items of given collections into a new one. * * @param CollectionInterface ...$collections The collections to merge. * * @return CollectionInterface */ public function merge(CollectionInterface ...$collections): self; } PK!ɢ; Queue.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\NoSuchElementException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; /** * This class provides a basic implementation of `QueueInterface`, to minimize * the effort required to implement this interface. * * @template T * @extends AbstractArray * @implements QueueInterface */ class Queue extends AbstractArray implements QueueInterface { use TypeTrait; use ValueToStringTrait; /** * The type of elements stored in this queue. * * A queue's type is immutable once it is set. For this reason, this * property is set private. * * @var string */ private $queueType; /** * The index of the head of the queue. * * @var int */ protected $index = 0; /** * Constructs a queue object of the specified type, optionally with the * specified data. * * @param string $queueType The type (FQCN) associated with this queue. * @param array $data The initial items to store in the collection. */ public function __construct(string $queueType, array $data = []) { $this->queueType = $queueType; parent::__construct($data); } /** * {@inheritDoc} * * Since arbitrary offsets may not be manipulated in a queue, this method * serves only to fulfill the `ArrayAccess` interface requirements. It is * invoked by other operations when adding values to the queue. */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } $this->data[] = $value; } /** * @inheritDoc */ public function add($element): bool { $this[] = $element; return true; } /** * @inheritDoc */ public function element() { $element = $this->peek(); if ($element === null) { throw new NoSuchElementException( 'Can\'t return element from Queue. Queue is empty.' ); } return $element; } /** * @inheritDoc */ public function offer($element): bool { try { return $this->add($element); } catch (InvalidArgumentException $e) { return false; } } /** * @inheritDoc */ public function peek() { if ($this->count() === 0) { return null; } return $this[$this->index]; } /** * @inheritDoc */ public function poll() { if ($this->count() === 0) { return null; } $head = $this[$this->index]; unset($this[$this->index]); $this->index++; return $head; } /** * @inheritDoc */ public function remove() { $head = $this->poll(); if ($head === null) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $head; } public function getType(): string { return $this->queueType; } } PK!1Tool/TypeTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use function is_array; use function is_bool; use function is_callable; use function is_float; use function is_int; use function is_numeric; use function is_object; use function is_resource; use function is_scalar; use function is_string; /** * Provides functionality to check values for specific types. */ trait TypeTrait { /** * Returns `true` if value is of the specified type. * * @param string $type The type to check the value against. * @param mixed $value The value to check. */ protected function checkType(string $type, $value): bool { switch ($type) { case 'array': return is_array($value); case 'bool': case 'boolean': return is_bool($value); case 'callable': return is_callable($value); case 'float': case 'double': return is_float($value); case 'int': case 'integer': return is_int($value); case 'null': return $value === null; case 'numeric': return is_numeric($value); case 'object': return is_object($value); case 'resource': return is_resource($value); case 'scalar': return is_scalar($value); case 'string': return is_string($value); case 'mixed': return true; default: return $value instanceof $type; } } } PK!@W W Tool/ValueToStringTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use DateTimeInterface; use function get_class; use function get_resource_type; use function is_array; use function is_bool; use function is_callable; use function is_resource; use function is_scalar; /** * Provides functionality to express a value as string */ trait ValueToStringTrait { /** * Returns a string representation of the value. * * - null value: `'NULL'` * - boolean: `'TRUE'`, `'FALSE'` * - array: `'Array'` * - scalar: converted-value * - resource: `'(type resource #number)'` * - object with `__toString()`: result of `__toString()` * - object DateTime: ISO 8601 date * - object: `'(className Object)'` * - anonymous function: same as object * * @param mixed $value the value to return as a string. */ protected function toolValueToString($value): string { // null if ($value === null) { return 'NULL'; } // boolean constants if (is_bool($value)) { return $value ? 'TRUE' : 'FALSE'; } // array if (is_array($value)) { return 'Array'; } // scalar types (integer, float, string) if (is_scalar($value)) { return (string) $value; } // resource if (is_resource($value)) { return '(' . get_resource_type($value) . ' resource #' . (int) $value . ')'; } // If we don't know what it is, use var_export(). if (!is_object($value)) { return '(' . var_export($value, true) . ')'; } // From here, $value should be an object. // __toString() is implemented if (is_callable([$value, '__toString'])) { return (string) $value->__toString(); } // object of type \DateTime if ($value instanceof DateTimeInterface) { return $value->format('c'); } // unknown type return '(' . get_class($value) . ' Object)'; } } PK!X'Tool/ValueExtractorTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use Ramsey\Collection\Exception\ValueExtractionException; use function get_class; use function method_exists; use function property_exists; use function sprintf; /** * Provides functionality to extract the value of a property or method from an object. */ trait ValueExtractorTrait { /** * Extracts the value of the given property or method from the object. * * @param mixed $object The object to extract the value from. * @param string $propertyOrMethod The property or method for which the * value should be extracted. * * @return mixed the value extracted from the specified property or method. * * @throws ValueExtractionException if the method or property is not defined. */ protected function extractValue($object, string $propertyOrMethod) { if (!is_object($object)) { throw new ValueExtractionException('Unable to extract a value from a non-object'); } if (property_exists($object, $propertyOrMethod)) { return $object->$propertyOrMethod; } if (method_exists($object, $propertyOrMethod)) { return $object->{$propertyOrMethod}(); } throw new ValueExtractionException( sprintf('Method or property "%s" not defined in %s', $propertyOrMethod, get_class($object)) ); } } PK!)^ K))DoubleEndedQueueInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\NoSuchElementException; /** * A linear collection that supports element insertion and removal at both ends. * * Most `DoubleEndedQueueInterface` implementations place no fixed limits on the * number of elements they may contain, but this interface supports * capacity-restricted double-ended queues as well as those with no fixed size * limit. * * This interface defines methods to access the elements at both ends of the * double-ended queue. Methods are provided to insert, remove, and examine the * element. Each of these methods exists in two forms: one throws an exception * if the operation fails, the other returns a special value (either `null` or * `false`, depending on the operation). The latter form of the insert operation * is designed specifically for use with capacity-restricted implementations; in * most implementations, insert operations cannot fail. * * The twelve methods described above are summarized in the following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary of DoubleEndedQueueInterface methods
First Element (Head)Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
InsertaddFirst()offerFirst()addLast()offerLast()
RemoveremoveFirst()pollFirst()removeLast()pollLast()
ExaminefirstElement()peekFirst()lastElement()peekLast()
* * This interface extends the `QueueInterface`. When a double-ended queue is * used as a queue, FIFO (first-in-first-out) behavior results. Elements are * added at the end of the double-ended queue and removed from the beginning. * The methods inherited from the `QueueInterface` are precisely equivalent to * `DoubleEndedQueueInterface` methods as indicated in the following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Comparison of QueueInterface and DoubleEndedQueueInterface methods
QueueInterface MethodDoubleEndedQueueInterface Method
add()addLast()
offer()offerLast()
remove()removeFirst()
poll()pollFirst()
element()firstElement()
peek()peekFirst()
* * Double-ended queues can also be used as LIFO (last-in-first-out) stacks. When * a double-ended queue is used as a stack, elements are pushed and popped from * the beginning of the double-ended queue. Stack concepts are precisely * equivalent to `DoubleEndedQueueInterface` methods as indicated in the table * below: * * * * * * * * * * * * * * * * * * * * * * * *
Comparison of stack concepts and DoubleEndedQueueInterface methods
Stack conceptDoubleEndedQueueInterface Method
pushaddFirst()
popremoveFirst()
peekpeekFirst()
* * Note that the `peek()` method works equally well when a double-ended queue is * used as a queue or a stack; in either case, elements are drawn from the * beginning of the double-ended queue. * * While `DoubleEndedQueueInterface` implementations are not strictly required * to prohibit the insertion of `null` elements, they are strongly encouraged to * do so. Users of any `DoubleEndedQueueInterface` implementations that do allow * `null` elements are strongly encouraged *not* to take advantage of the * ability to insert nulls. This is so because `null` is used as a special * return value by various methods to indicated that the double-ended queue is * empty. * * @template T * @extends QueueInterface */ interface DoubleEndedQueueInterface extends QueueInterface { /** * Inserts the specified element at the front of this queue if it is * possible to do so immediately without violating capacity restrictions. * * When using a capacity-restricted double-ended queue, it is generally * preferable to use the `offerFirst()` method. * * @param T $element The element to add to the front of this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function addFirst($element): bool; /** * Inserts the specified element at the end of this queue if it is possible * to do so immediately without violating capacity restrictions. * * When using a capacity-restricted double-ended queue, it is generally * preferable to use the `offerLast()` method. * * This method is equivalent to `add()`. * * @param T $element The element to add to the end of this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function addLast($element): bool; /** * Inserts the specified element at the front of this queue if it is * possible to do so immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `addFirst()`, which can fail to insert an element only by * throwing an exception. * * @param T $element The element to add to the front of this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offerFirst($element): bool; /** * Inserts the specified element at the end of this queue if it is possible * to do so immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `addLast()` which can fail to insert an element only by * throwing an exception. * * @param T $element The element to add to the end of this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offerLast($element): bool; /** * Retrieves and removes the head of this queue. * * This method differs from `pollFirst()` only in that it throws an * exception if this queue is empty. * * @return T the first element in this queue. * * @throws NoSuchElementException if this queue is empty. */ public function removeFirst(); /** * Retrieves and removes the tail of this queue. * * This method differs from `pollLast()` only in that it throws an exception * if this queue is empty. * * @return T the last element in this queue. * * @throws NoSuchElementException if this queue is empty. */ public function removeLast(); /** * Retrieves and removes the head of this queue, or returns `null` if this * queue is empty. * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function pollFirst(); /** * Retrieves and removes the tail of this queue, or returns `null` if this * queue is empty. * * @return T|null the tail of this queue, or `null` if this queue is empty. */ public function pollLast(); /** * Retrieves, but does not remove, the head of this queue. * * This method differs from `peekFirst()` only in that it throws an * exception if this queue is empty. * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function firstElement(); /** * Retrieves, but does not remove, the tail of this queue. * * This method differs from `peekLast()` only in that it throws an exception * if this queue is empty. * * @return T the tail of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function lastElement(); /** * Retrieves, but does not remove, the head of this queue, or returns `null` * if this queue is empty. * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function peekFirst(); /** * Retrieves, but does not remove, the tail of this queue, or returns `null` * if this queue is empty. * * @return T|null the tail of this queue, or `null` if this queue is empty. */ public function peekLast(); } PK!N$Exception/NoSuchElementException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to access an element that does not exist. */ class NoSuchElementException extends \RuntimeException { } PK!>II+Exception/UnsupportedOperationException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use LogicException as PhpLogicException; /** * Thrown to indicate that the requested operation is not supported */ class UnsupportedOperationException extends PhpLogicException implements UuidExceptionInterface { } PK!GF  )Exception/CollectionMismatchException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to operate on collections of differing types. */ class CollectionMismatchException extends \RuntimeException { } PK!- "Exception/OutOfBoundsException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to access an element out of the range of the collection. */ class OutOfBoundsException extends \OutOfBoundsException { } PK! C~'Exception/InvalidSortOrderException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to use a sort order that is not recognized. */ class InvalidSortOrderException extends \RuntimeException { } PK!|f&Exception/ValueExtractionException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to extract a value for a method or property that does not exist. */ class ValueExtractionException extends \RuntimeException { } PK!2^&Exception/InvalidArgumentException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * This class contains the basic implementation of a collection that does not * allow duplicated values (a set), to minimize the effort required to implement * this specific type of collection. * * @template T * @extends AbstractCollection */ abstract class AbstractSet extends AbstractCollection { /** * @inheritDoc */ public function add($element): bool { if ($this->contains($element)) { return false; } return parent::add($element); } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->contains($value)) { return; } parent::offsetSet($offset, $value); } } PK!;I( ( Collection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A collection represents a group of objects. * * Each object in the collection is of a specific, defined type. * * This is a direct implementation of `CollectionInterface`, provided for * the sake of convenience. * * Example usage: * * ``` php * $collection = new \Ramsey\Collection\Collection('My\\Foo'); * $collection->add(new \My\Foo()); * $collection->add(new \My\Foo()); * * foreach ($collection as $foo) { * // Do something with $foo * } * ``` * * It is preferable to subclass `AbstractCollection` to create your own typed * collections. For example: * * ``` php * namespace My\Foo; * * class FooCollection extends \Ramsey\Collection\AbstractCollection * { * public function getType() * { * return 'My\\Foo'; * } * } * ``` * * And then use it similarly to the earlier example: * * ``` php * $fooCollection = new \My\Foo\FooCollection(); * $fooCollection->add(new \My\Foo()); * $fooCollection->add(new \My\Foo()); * * foreach ($fooCollection as $foo) { * // Do something with $foo * } * ``` * * The benefit with this approach is that you may do type-checking on the * collection object: * * ``` php * if ($collection instanceof \My\Foo\FooCollection) { * // the collection is a collection of My\Foo objects * } * ``` * * @template T * @extends AbstractCollection */ class Collection extends AbstractCollection { /** * The type of elements stored in this collection. * * A collection's type is immutable once it is set. For this reason, this * property is set private. * * @var string */ private $collectionType; /** * Constructs a collection object of the specified type, optionally with the * specified data. * * @param string $collectionType The type (FQCN) associated with this * collection. * @param array $data The initial items to store in the collection. */ public function __construct(string $collectionType, array $data = []) { $this->collectionType = $collectionType; parent::__construct($data); } public function getType(): string { return $this->collectionType; } } PK!hDoubleEndedQueue.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\NoSuchElementException; /** * This class provides a basic implementation of `DoubleEndedQueueInterface`, to * minimize the effort required to implement this interface. * * @template T * @extends Queue * @implements DoubleEndedQueueInterface */ class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface { /** * Index of the last element in the queue. * * @var int */ private $tail = -1; /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } $this->tail++; $this->data[$this->tail] = $value; } /** * @inheritDoc */ public function addFirst($element): bool { if ($this->checkType($this->getType(), $element) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($element) ); } $this->index--; $this->data[$this->index] = $element; return true; } /** * @inheritDoc */ public function addLast($element): bool { return $this->add($element); } /** * @inheritDoc */ public function offerFirst($element): bool { try { return $this->addFirst($element); } catch (InvalidArgumentException $e) { return false; } } /** * @inheritDoc */ public function offerLast($element): bool { return $this->offer($element); } /** * @inheritDoc */ public function removeFirst() { return $this->remove(); } /** * @inheritDoc */ public function removeLast() { $tail = $this->pollLast(); if ($tail === null) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $tail; } /** * @inheritDoc */ public function pollFirst() { return $this->poll(); } /** * @inheritDoc */ public function pollLast() { if ($this->count() === 0) { return null; } $tail = $this[$this->tail]; unset($this[$this->tail]); $this->tail--; return $tail; } /** * @inheritDoc */ public function firstElement() { return $this->element(); } /** * @inheritDoc */ public function lastElement() { if ($this->count() === 0) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $this->data[$this->tail]; } /** * @inheritDoc */ public function peekFirst() { return $this->peek(); } /** * @inheritDoc */ public function peekLast() { if ($this->count() === 0) { return null; } return $this->data[$this->tail]; } } PK!㸿 Map/AbstractMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\AbstractArray; use Ramsey\Collection\Exception\InvalidArgumentException; use function array_key_exists; use function array_keys; use function in_array; /** * This class provides a basic implementation of `MapInterface`, to minimize the * effort required to implement this interface. * * @template T * @extends AbstractArray * @implements MapInterface */ abstract class AbstractMap extends AbstractArray implements MapInterface { /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } $this->data[$offset] = $value; } /** * @inheritDoc */ public function containsKey($key): bool { return array_key_exists($key, $this->data); } /** * @inheritDoc */ public function containsValue($value): bool { return in_array($value, $this->data, true); } /** * @inheritDoc */ public function keys(): array { return array_keys($this->data); } /** * @inheritDoc */ public function get($key, $defaultValue = null) { if (!$this->containsKey($key)) { return $defaultValue; } return $this[$key]; } /** * @inheritDoc */ public function put($key, $value) { $previousValue = $this->get($key); $this[$key] = $value; return $previousValue; } /** * @inheritDoc */ public function putIfAbsent($key, $value) { $currentValue = $this->get($key); if ($currentValue === null) { $this[$key] = $value; } return $currentValue; } /** * @inheritDoc */ public function remove($key) { $previousValue = $this->get($key); unset($this[$key]); return $previousValue; } /** * @inheritDoc */ public function removeIf($key, $value): bool { if ($this->get($key) === $value) { unset($this[$key]); return true; } return false; } /** * @inheritDoc */ public function replace($key, $value) { $currentValue = $this->get($key); if ($this->containsKey($key)) { $this[$key] = $value; } return $currentValue; } /** * @inheritDoc */ public function replaceIf($key, $oldValue, $newValue): bool { if ($this->get($key) === $oldValue) { $this[$key] = $newValue; return true; } return false; } } PK!:(  Map/NamedParameterMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; use function array_combine; use function array_key_exists; use function is_int; /** * `NamedParameterMap` represents a mapping of values to a set of named keys * that may optionally be typed * * @extends AbstractMap */ class NamedParameterMap extends AbstractMap { use TypeTrait; use ValueToStringTrait; /** * Named parameters defined for this map. * * @var array */ protected $namedParameters; /** * Constructs a new `NamedParameterMap`. * * @param array $namedParameters The named parameters defined for this map. * @param array $data An initial set of data to set on this map. */ public function __construct(array $namedParameters, array $data = []) { $this->namedParameters = $this->filterNamedParameters($namedParameters); parent::__construct($data); } /** * Returns named parameters set for this `NamedParameterMap`. * * @return array */ public function getNamedParameters(): array { return $this->namedParameters; } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } if (!array_key_exists($offset, $this->namedParameters)) { throw new InvalidArgumentException( 'Attempting to set value for unconfigured parameter \'' . $offset . '\'' ); } if ($this->checkType($this->namedParameters[$offset], $value) === false) { throw new InvalidArgumentException( 'Value for \'' . $offset . '\' must be of type ' . $this->namedParameters[$offset] . '; value is ' . $this->toolValueToString($value) ); } $this->data[$offset] = $value; } /** * Given an array of named parameters, constructs a proper mapping of * named parameters to types. * * @param array $namedParameters The named parameters to filter. * * @return array */ protected function filterNamedParameters(array $namedParameters): array { $names = []; $types = []; foreach ($namedParameters as $key => $value) { if (is_int($key)) { $names[] = $value; $types[] = 'mixed'; } else { $names[] = $key; $types[] = $value; } } return array_combine($names, $types) ?: []; } } PK! =T%%Map/AssociativeArrayMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; /** * `AssociativeArrayMap` represents a standard associative array object. * * @template T * @extends AbstractMap */ class AssociativeArrayMap extends AbstractMap { } PK!`!  Map/MapInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\ArrayInterface; /** * An object that maps keys to values. * * A map cannot contain duplicate keys; each key can map to at most one value. * * @template T * @extends ArrayInterface */ interface MapInterface extends ArrayInterface { /** * Returns `true` if this map contains a mapping for the specified key. * * @param array-key $key The key to check in the map. */ public function containsKey($key): bool; /** * Returns `true` if this map maps one or more keys to the specified value. * * This performs a strict type check on the value. * * @param T $value The value to check in the map. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function containsValue($value): bool; /** * Return an array of the keys contained in this map. * * @return list */ public function keys(): array; /** * Returns the value to which the specified key is mapped, `null` if this * map contains no mapping for the key, or (optionally) `$defaultValue` if * this map contains no mapping for the key. * * @param array-key $key The key to return from the map. * @param T|null $defaultValue The default value to use if `$key` is not found. * * @return T|null the value or `null` if the key could not be found. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function get($key, $defaultValue = null); /** * Associates the specified value with the specified key in this map. * * If the map previously contained a mapping for the key, the old value is * replaced by the specified value. * * @param array-key $key The key to put or replace in the map. * @param T $value The value to store at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function put($key, $value); /** * Associates the specified value with the specified key in this map only if * it is not already set. * * If there is already a value associated with `$key`, this returns that * value without replacing it. * * @param array-key $key The key to put in the map. * @param T $value The value to store at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function putIfAbsent($key, $value); /** * Removes the mapping for a key from this map if it is present. * * @param array-key $key The key to remove from the map. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function remove($key); /** * Removes the entry for the specified key only if it is currently mapped to * the specified value. * * This performs a strict type check on the value. * * @param array-key $key The key to remove from the map. * @param T $value The value to match. * * @return bool true if the value was removed. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function removeIf($key, $value): bool; /** * Replaces the entry for the specified key only if it is currently mapped * to some value. * * @param array-key $key The key to replace. * @param T $value The value to set at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function replace($key, $value); /** * Replaces the entry for the specified key only if currently mapped to the * specified value. * * This performs a strict type check on the value. * * @param array-key $key The key to remove from the map. * @param T $oldValue The value to match. * @param T $newValue The value to use as a replacement. * * @return bool true if the value was replaced. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function replaceIf($key, $oldValue, $newValue): bool; } PK!dʼn Map/TypedMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Tool\TypeTrait; /** * A `TypedMap` represents a map of elements where key and value are typed. * * Each element is identified by a key with defined type and a value of defined * type. The keys of the map must be unique. The values on the map can be= * repeated but each with its own different key. * * The most common case is to use a string type key, but it's not limited to * this type of keys. * * This is a direct implementation of `TypedMapInterface`, provided for the sake * of convenience. * * Example usage: * * ```php * $map = new TypedMap('string', Foo::class); * $map['x'] = new Foo(); * foreach ($map as $key => $value) { * // do something with $key, it will be a Foo::class * } * * // this will throw an exception since key must be string * $map[10] = new Foo(); * * // this will throw an exception since value must be a Foo * $map['bar'] = 'bar'; * * // initialize map with contents * $map = new TypedMap('string', Foo::class, [ * new Foo(), new Foo(), new Foo() * ]); * ``` * * It is preferable to subclass `AbstractTypedMap` to create your own typed map * implementation: * * ```php * class FooTypedMap extends AbstractTypedMap * { * public function getKeyType() * { * return 'int'; * } * * public function getValueType() * { * return Foo::class; * } * } * ``` * * … but you also may use the `TypedMap` class: * * ```php * class FooTypedMap extends TypedMap * { * public function __constructor(array $data = []) * { * parent::__construct('int', Foo::class, $data); * } * } * ``` * * @template K * @template T * @extends AbstractTypedMap */ class TypedMap extends AbstractTypedMap { use TypeTrait; /** * The data type of keys stored in this collection. * * A map key's type is immutable once it is set. For this reason, this * property is set private. * * @var string data type of the map key. */ private $keyType; /** * The data type of values stored in this collection. * * A map value's type is immutable once it is set. For this reason, this * property is set private. * * @var string data type of the map value. */ private $valueType; /** * Constructs a map object of the specified key and value types, * optionally with the specified data. * * @param string $keyType The data type of the map's keys. * @param string $valueType The data type of the map's values. * @param array $data The initial data to set for this map. */ public function __construct(string $keyType, string $valueType, array $data = []) { $this->keyType = $keyType; $this->valueType = $valueType; /** @psalm-suppress MixedArgumentTypeCoercion */ parent::__construct($data); } public function getKeyType(): string { return $this->keyType; } public function getValueType(): string { return $this->valueType; } } PK!RMap/AbstractTypedMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; /** * This class provides a basic implementation of `TypedMapInterface`, to * minimize the effort required to implement this interface. * * @template K * @template T * @extends AbstractMap * @implements TypedMapInterface */ abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface { use TypeTrait; use ValueToStringTrait; /** * @param K|null $offset * @param T $value * * @inheritDoc * * @psalm-suppress MoreSpecificImplementedParamType */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } if ($this->checkType($this->getKeyType(), $offset) === false) { throw new InvalidArgumentException( 'Key must be of type ' . $this->getKeyType() . '; key is ' . $this->toolValueToString($offset) ); } if ($this->checkType($this->getValueType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getValueType() . '; value is ' . $this->toolValueToString($value) ); } /** @psalm-suppress MixedArgumentTypeCoercion */ parent::offsetSet($offset, $value); } } PK!Map/TypedMapInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; /** * A `TypedMapInterface` represents a map of elements where key and value are * typed. * * @template T * @extends MapInterface */ interface TypedMapInterface extends MapInterface { /** * Return the type used on the key. */ public function getKeyType(): string; /** * Return the type forced on the values. */ public function getValueType(): string; } PK!VVQueueInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\NoSuchElementException; /** * A queue is a collection in which the entities in the collection are kept in * order. * * The principal operations on the queue are the addition of entities to the end * (tail), also known as *enqueue*, and removal of entities from the front * (head), also known as *dequeue*. This makes the queue a first-in-first-out * (FIFO) data structure. * * Besides basic array operations, queues provide additional insertion, * extraction, and inspection operations. Each of these methods exists in two * forms: one throws an exception if the operation fails, the other returns a * special value (either `null` or `false`, depending on the operation). The * latter form of the insert operation is designed specifically for use with * capacity-restricted `QueueInterface` implementations; in most * implementations, insert operations cannot fail. * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary of QueueInterface methods
Throws exceptionReturns special value
Insertadd()offer()
Removeremove()poll()
Examineelement()peek()
* * Queues typically, but do not necessarily, order elements in a FIFO * (first-in-first-out) manner. Among the exceptions are priority queues, which * order elements according to a supplied comparator, or the elements' natural * ordering, and LIFO queues (or stacks) which order the elements LIFO * (last-in-first-out). Whatever the ordering used, the head of the queue is * that element which would be removed by a call to remove() or poll(). In a * FIFO queue, all new elements are inserted at the tail of the queue. Other * kinds of queues may use different placement rules. Every `QueueInterface` * implementation must specify its ordering properties. * * The `offer()` method inserts an element if possible, otherwise returning * `false`. This differs from the `add()` method, which can fail to add an * element only by throwing an unchecked exception. The `offer()` method is * designed for use when failure is a normal, rather than exceptional * occurrence, for example, in fixed-capacity (or "bounded") queues. * * The `remove()` and `poll()` methods remove and return the head of the queue. * Exactly which element is removed from the queue is a function of the queue's * ordering policy, which differs from implementation to implementation. The * `remove()` and `poll()` methods differ only in their behavior when the queue * is empty: the `remove()` method throws an exception, while the `poll()` * method returns `null`. * * The `element()` and `peek()` methods return, but do not remove, the head of * the queue. * * `QueueInterface` implementations generally do not allow insertion of `null` * elements, although some implementations do not prohibit insertion of `null`. * Even in the implementations that permit it, `null` should not be inserted * into a queue, as `null` is also used as a special return value by the * `poll()` method to indicate that the queue contains no elements. * * @template T * @extends ArrayInterface */ interface QueueInterface extends ArrayInterface { /** * Ensures that this queue contains the specified element (optional * operation). * * Returns `true` if this queue changed as a result of the call. (Returns * `false` if this queue does not permit duplicates and already contains the * specified element.) * * Queues that support this operation may place limitations on what elements * may be added to this queue. In particular, some queues will refuse to add * `null` elements, and others will impose restrictions on the type of * elements that may be added. Queue classes should clearly specify in their * documentation any restrictions on what elements may be added. * * If a queue refuses to add a particular element for any reason other than * that it already contains the element, it must throw an exception (rather * than returning `false`). This preserves the invariant that a queue always * contains the specified element after this call returns. * * @see self::offer() * * @param T $element The element to add to this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function add($element): bool; /** * Retrieves, but does not remove, the head of this queue. * * This method differs from `peek()` only in that it throws an exception if * this queue is empty. * * @see self::peek() * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function element(); /** * Inserts the specified element into this queue if it is possible to do so * immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `add()`, which can fail to insert an element only by * throwing an exception. * * @see self::add() * * @param T $element The element to add to this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offer($element): bool; /** * Retrieves, but does not remove, the head of this queue, or returns `null` * if this queue is empty. * * @see self::element() * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function peek(); /** * Retrieves and removes the head of this queue, or returns `null` * if this queue is empty. * * @see self::remove() * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function poll(); /** * Retrieves and removes the head of this queue. * * This method differs from `poll()` only in that it throws an exception if * this queue is empty. * * @see self::poll() * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function remove(); /** * Returns the type associated with this queue. */ public function getType(): string; } PK!IIArrayInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use ArrayAccess; use Countable; use IteratorAggregate; use Serializable; /** * `ArrayInterface` provides traversable array functionality to data types. * * @template T * @extends ArrayAccess * @extends IteratorAggregate */ interface ArrayInterface extends ArrayAccess, Countable, IteratorAggregate, Serializable { /** * Removes all items from this array. */ public function clear(): void; /** * Returns a native PHP array representation of this array object. * * @return array */ public function toArray(): array; /** * Returns `true` if this array is empty. */ public function isEmpty(): bool; } PK!KSGenericArray.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * `GenericArray` represents a standard array object. * * @extends AbstractArray */ class GenericArray extends AbstractArray { } PK!F~M#M#AbstractCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Closure; use Ramsey\Collection\Exception\CollectionMismatchException; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\InvalidSortOrderException; use Ramsey\Collection\Exception\OutOfBoundsException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueExtractorTrait; use Ramsey\Collection\Tool\ValueToStringTrait; use function array_filter; use function array_map; use function array_merge; use function array_search; use function array_udiff; use function array_uintersect; use function current; use function end; use function in_array; use function is_int; use function reset; use function sprintf; use function unserialize; use function usort; /** * This class provides a basic implementation of `CollectionInterface`, to * minimize the effort required to implement this interface * * @template T * @extends AbstractArray * @implements CollectionInterface */ abstract class AbstractCollection extends AbstractArray implements CollectionInterface { use TypeTrait; use ValueToStringTrait; use ValueExtractorTrait; /** * @inheritDoc */ public function add($element): bool { $this[] = $element; return true; } /** * @inheritDoc */ public function contains($element, bool $strict = true): bool { return in_array($element, $this->data, $strict); } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; } } /** * @inheritDoc */ public function remove($element): bool { if (($position = array_search($element, $this->data, true)) !== false) { unset($this->data[$position]); return true; } return false; } /** * @inheritDoc */ public function column(string $propertyOrMethod): array { $temp = []; foreach ($this->data as $item) { /** @var mixed $value */ $value = $this->extractValue($item, $propertyOrMethod); /** @psalm-suppress MixedAssignment */ $temp[] = $value; } return $temp; } /** * @inheritDoc */ public function first() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine first item. Collection is empty'); } reset($this->data); /** @var T $first */ $first = current($this->data); return $first; } /** * @inheritDoc */ public function last() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine last item. Collection is empty'); } /** @var T $item */ $item = end($this->data); reset($this->data); return $item; } public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface { if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) { throw new InvalidSortOrderException('Invalid sort order given: ' . $order); } $collection = clone $this; usort( $collection->data, /** * @param T $a * @param T $b */ function ($a, $b) use ($propertyOrMethod, $order): int { /** @var mixed $aValue */ $aValue = $this->extractValue($a, $propertyOrMethod); /** @var mixed $bValue */ $bValue = $this->extractValue($b, $propertyOrMethod); return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1); } ); return $collection; } public function filter(callable $callback): CollectionInterface { $collection = clone $this; $collection->data = array_merge([], array_filter($collection->data, $callback)); return $collection; } /** * {@inheritdoc} */ public function where(string $propertyOrMethod, $value): CollectionInterface { return $this->filter(function ($item) use ($propertyOrMethod, $value) { /** @var mixed $accessorValue */ $accessorValue = $this->extractValue($item, $propertyOrMethod); return $accessorValue === $value; }); } public function map(callable $callback): CollectionInterface { return new Collection('mixed', array_map($callback, $this->data)); } public function diff(CollectionInterface $other): CollectionInterface { $this->compareCollectionTypes($other); $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator()); $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator()); /** @var array $diff */ $diff = array_merge($diffAtoB, $diffBtoA); $collection = clone $this; $collection->data = $diff; return $collection; } public function intersect(CollectionInterface $other): CollectionInterface { $this->compareCollectionTypes($other); /** @var array $intersect */ $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator()); $collection = clone $this; $collection->data = $intersect; return $collection; } public function merge(CollectionInterface ...$collections): CollectionInterface { $mergedCollection = clone $this; foreach ($collections as $index => $collection) { if (!$collection instanceof static) { throw new CollectionMismatchException( sprintf('Collection with index %d must be of type %s', $index, static::class) ); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($collection->getType() !== $this->getType()) { throw new CollectionMismatchException( sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType()) ); } foreach ($collection as $key => $value) { if (is_int($key)) { $mergedCollection[] = $value; } else { $mergedCollection[$key] = $value; } } } return $mergedCollection; } /** * @inheritDoc */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]); $this->data = $data; } /** * @param CollectionInterface $other */ private function compareCollectionTypes(CollectionInterface $other): void { if (!$other instanceof static) { throw new CollectionMismatchException('Collection must be of type ' . static::class); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($other->getType() !== $this->getType()) { throw new CollectionMismatchException('Collection items must be of type ' . $this->getType()); } } private function getComparator(): Closure { return /** * @param T $a * @param T $b */ function ($a, $b): int { // If the two values are object, we convert them to unique scalars. // If the collection contains mixed values (unlikely) where some are objects // and some are not, we leave them as they are. // The comparator should still work and the result of $a < $b should // be consistent but unpredictable since not documented. if (is_object($a) && is_object($b)) { $a = spl_object_id($a); $b = spl_object_id($b); } return $a === $b ? 0 : ($a < $b ? 1 : -1); }; } } PK!ᶏllautoloader.phpnu[chunks = $bytesIterator; $this->jsonPointer = $options['pointer']; $this->jsonDecoder = $options['decoder']; $this->debugEnabled = $options['debug']; if ($this->debugEnabled) { $tokensClass = TokensWithDebugging::class; } else { $tokensClass = Tokens::class; } $this->parser = new Parser( new $tokensClass( $this->chunks ), $this->jsonPointer, $this->jsonDecoder ?: new ExtJsonDecoder() ); } /** * @param string $string * * @return self * * @throws InvalidArgumentException */ public static function fromString($string, array $options = []) { return new self(new StringChunks($string), $options); } /** * @param string $file * * @return self * * @throws Exception\InvalidArgumentException */ public static function fromFile($file, array $options = []) { return new self(new FileChunks($file), $options); } /** * @param resource $stream * * @return self * * @throws Exception\InvalidArgumentException */ public static function fromStream($stream, array $options = []) { return new self(new StreamChunks($stream), $options); } /** * @param iterable $iterable * * @return self * * @throws Exception\InvalidArgumentException */ public static function fromIterable($iterable, array $options = []) { return new self($iterable, $options); } #[\ReturnTypeWillChange] public function getIterator() { return $this->parser->getIterator(); } public function getPosition() { return $this->parser->getPosition(); } public function getJsonPointers(): array { return $this->parser->getJsonPointers(); } public function getCurrentJsonPointer(): string { return $this->parser->getCurrentJsonPointer(); } public function getMatchedJsonPointer(): string { return $this->parser->getMatchedJsonPointer(); } /** * @return bool */ public function isDebugEnabled() { return $this->debugEnabled; } } PK!TrrItemsOptions.phpnu[validateOptions($options); parent::__construct($this->options); } public function toArray(): array { return $this->options; } /** * @throws InvalidArgumentException */ private function validateOptions(array $options) { $mergedOptions = array_merge(self::defaultOptions(), $options); try { foreach ($mergedOptions as $optionName => $optionValue) { if ( ! isset(self::defaultOptions()[$optionName])) { throw new InvalidArgumentException("Option '$optionName' does not exist."); } $this->options[$optionName] = $this->{"opt_$optionName"}($optionValue); } } catch (\TypeError $typeError) { throw new InvalidArgumentException( preg_replace('~Argument #[0-9]+~', "Option '$optionName'", $typeError->getMessage()) ); } } private function opt_pointer($pointer) { if (is_array($pointer)) { (function (string ...$p) {})(...$pointer); } else { (function (string $p) {})($pointer); } return $pointer; } private function opt_decoder(ItemDecoder $decoder = null) { return $decoder; } private function opt_debug(bool $debug) { return $debug; } public static function defaultOptions(): array { return [ 'pointer' => '', 'decoder' => new ExtJsonDecoder(), 'debug' => false, ]; } } PK!KjFileChunks.phpnu[fileName = $fileName; $this->chunkSize = $chunkSize; } /** * @return \Generator */ #[\ReturnTypeWillChange] public function getIterator() { $fileHandle = fopen($this->fileName, 'r'); try { yield from new StreamChunks($fileHandle, $this->chunkSize); } finally { fclose($fileHandle); } } } PK!PositionAware.phpnu[stream = $stream; $this->chunkSize = $chunkSize; } /** * @return \Generator */ #[\ReturnTypeWillChange] public function getIterator() { while ('' !== ($chunk = fread($this->stream, $this->chunkSize))) { yield $chunk; } } } PK!L$uu"Exception/JsonMachineException.phpnu[malformedJson = $malformedJson; $this->errorMessage = $errorMessage; } /** * @return string */ public function getMalformedJson() { return $this->malformedJson; } /** * @return string */ public function getErrorMessage() { return $this->errorMessage; } } PK!dJsonDecoder/ItemDecoder.phpnu[assoc = $assoc; $this->depth = $depth; $this->options = $options; } public function decode($jsonValue) { $decoded = json_decode($jsonValue, $this->assoc, $this->depth, $this->options); if (json_last_error() !== JSON_ERROR_NONE) { return new InvalidResult(json_last_error_msg()); } return new ValidResult($decoded); } } PK!!lJsonDecoder/InvalidResult.phpnu[errorMessage = $errorMessage; } public function getErrorMessage(): string { return $this->errorMessage; } public function isOk(): bool { return false; } } PK!쩭JsonDecoder/PassThruDecoder.phpnu[innerDecoder = $innerDecoder; } public function decode($jsonValue) { $result = $this->innerDecoder->decode($jsonValue); if ( ! $result->isOk()) { return new ValidResult(new DecodingError($jsonValue, $result->getErrorMessage())); } return $result; } } PK!8uJsonDecoder/ValidResult.phpnu[value = $value; } /** * @return mixed */ public function getValue() { return $this->value; } public function isOk(): bool { return true; } } PK!* LL Tokens.phpnu[ $jsonChunks */ public function __construct($jsonChunks) { $this->jsonChunks = $jsonChunks; } /** * @return Generator */ #[\ReturnTypeWillChange] public function getIterator() { $insignificantBytes = $this->insignificantBytes(); $tokenBoundaries = $this->tokenBoundaries(); $colonCommaBracket = $this->colonCommaBracketTokenBoundaries(); $inString = false; $tokenBuffer = ''; $escaping = false; foreach ($this->jsonChunks as $jsonChunk) { $bytesLength = strlen($jsonChunk); for ($i = 0; $i < $bytesLength; ++$i) { $byte = $jsonChunk[$i]; if ($escaping) { $escaping = false; $tokenBuffer .= $byte; continue; } if (isset($insignificantBytes[$byte])) { // is a JSON-structure insignificant byte $tokenBuffer .= $byte; continue; } if ($inString) { if ($byte == '"') { $inString = false; } elseif ($byte == '\\') { $escaping = true; } $tokenBuffer .= $byte; continue; } if (isset($tokenBoundaries[$byte])) { if ($tokenBuffer != '') { yield $tokenBuffer; $tokenBuffer = ''; } if (isset($colonCommaBracket[$byte])) { yield $byte; } } else { // else branch matches `"` but also `\` outside of a string literal which is an error anyway but strictly speaking not correctly parsed token $inString = true; $tokenBuffer .= $byte; } } } if ($tokenBuffer != '') { yield $tokenBuffer; } } private function tokenBoundaries() { $utf8bom1 = "\xEF"; $utf8bom2 = "\xBB"; $utf8bom3 = "\xBF"; return array_merge( [ $utf8bom1 => true, $utf8bom2 => true, $utf8bom3 => true, ' ' => true, "\n" => true, "\r" => true, "\t" => true, ], $this->colonCommaBracketTokenBoundaries() ); } private function colonCommaBracketTokenBoundaries(): array { return [ '{' => true, '}' => true, '[' => true, ']' => true, ':' => true, ',' => true, ]; } private function insignificantBytes(): array { $insignificantBytes = []; foreach (range(0, 255) as $ord) { if ( ! in_array( chr($ord), ['\\', '"', "\xEF", "\xBB", "\xBF", ' ', "\n", "\r", "\t", '{', '}', '[', ']', ':', ','] )) { $insignificantBytes[chr($ord)] = true; } } return $insignificantBytes; } public function getPosition(): int { return 0; } public function getLine(): int { return 1; } public function getColumn(): int { return 0; } } PK!eT$9$9 Parser.phpnu[toArray(); $this->tokens = $tokens; $this->jsonDecoder = $jsonDecoder ?: new ExtJsonDecoder(); $this->hasSingleJsonPointer = (count($jsonPointers) === 1); $this->jsonPointers = array_combine($jsonPointers, $jsonPointers); $this->paths = $this->buildPaths($this->jsonPointers); } private function buildPaths(array $jsonPointers): array { return array_map(function ($jsonPointer) { return self::jsonPointerToPath($jsonPointer); }, $jsonPointers); } /** * @return \Generator * * @throws PathNotFoundException */ #[\ReturnTypeWillChange] public function getIterator() { $tokenTypes = $this->tokenTypes(); $iteratorStruct = null; $currentPath = &$this->currentPath; $currentPath = []; $currentPathWildcard = []; $pointersFound = []; $currentLevel = -1; $stack = [$currentLevel => null]; $jsonBuffer = ''; $key = null; $objectKeyExpected = false; $inObject = true; // hack to make "!$inObject" in first iteration work. Better code structure? $expectedType = self::OBJECT_START | self::ARRAY_START; $subtreeEnded = false; $token = null; $currentPathChanged = true; $jsonPointerPath = []; $iteratorLevel = 0; // local variables for faster name lookups $tokens = $this->tokens; foreach ($tokens as $token) { if ($currentPathChanged) { $currentPathChanged = false; $jsonPointerPath = $this->getMatchingJsonPointerPath(); $iteratorLevel = count($jsonPointerPath); } $tokenType = $tokenTypes[$token[0]]; if (0 == ($tokenType & $expectedType)) { $this->error('Unexpected symbol', $token); } $isValue = ($tokenType | 23) == 23; // 23 = self::ANY_VALUE if ( ! $inObject && $isValue && $currentLevel < $iteratorLevel) { $currentPathChanged = ! $this->hasSingleJsonPointer; $currentPath[$currentLevel] = isset($currentPath[$currentLevel]) ? (string) (1 + (int) $currentPath[$currentLevel]) : '0'; $currentPathWildcard[$currentLevel] = preg_match('/^(?:\d+|-)$/S', $jsonPointerPath[$currentLevel]) ? '-' : $currentPath[$currentLevel]; unset($currentPath[$currentLevel + 1], $currentPathWildcard[$currentLevel + 1], $stack[$currentLevel + 1]); } if ( ( $jsonPointerPath == $currentPath || $jsonPointerPath == $currentPathWildcard ) && ( $currentLevel > $iteratorLevel || ( ! $objectKeyExpected && ( ($currentLevel == $iteratorLevel && $isValue) || ($currentLevel + 1 == $iteratorLevel && ($tokenType | 3) == 3) // 3 = self::SCALAR_VALUE ) ) ) ) { $jsonBuffer .= $token; } // todo move this switch to the top just after the syntax check to be a correct FSM switch ($token[0]) { case '"': if ($objectKeyExpected) { $objectKeyExpected = false; $expectedType = 128; // 128 = self::COLON if ($currentLevel == $iteratorLevel) { $key = $token; } elseif ($currentLevel < $iteratorLevel) { $key = $token; $referenceToken = substr($token, 1, -1); $currentPathChanged = ! $this->hasSingleJsonPointer; $currentPath[$currentLevel] = $referenceToken; $currentPathWildcard[$currentLevel] = $referenceToken; unset($currentPath[$currentLevel + 1], $currentPathWildcard[$currentLevel + 1]); } continue 2; // a valid json chunk is not completed yet } if ($inObject) { $expectedType = 72; // 72 = self::AFTER_OBJECT_VALUE; } else { $expectedType = 96; // 96 = self::AFTER_ARRAY_VALUE; } break; case ',': if ($inObject) { $objectKeyExpected = true; $expectedType = 2; // 2 = self::SCALAR_STRING } else { $expectedType = 23; // 23 = self::ANY_VALUE } continue 2; // a valid json chunk is not completed yet case ':': $expectedType = 23; // 23 = self::ANY_VALUE continue 2; // a valid json chunk is not completed yet case '{': ++$currentLevel; if ($currentLevel <= $iteratorLevel) { $iteratorStruct = '{'; } $stack[$currentLevel] = '{'; $inObject = true; $expectedType = 10; // 10 = self::AFTER_OBJECT_START $objectKeyExpected = true; continue 2; // a valid json chunk is not completed yet case '[': ++$currentLevel; if ($currentLevel <= $iteratorLevel) { $iteratorStruct = '['; } $stack[$currentLevel] = '['; $inObject = false; $expectedType = 55; // 55 = self::AFTER_ARRAY_START; continue 2; // a valid json chunk is not completed yet case '}': $objectKeyExpected = false; // no break case ']': --$currentLevel; $inObject = $stack[$currentLevel] == '{'; // no break default: if ($inObject) { $expectedType = 72; // 72 = self::AFTER_OBJECT_VALUE; } else { $expectedType = 96; // 96 = self::AFTER_ARRAY_VALUE; } } if ($currentLevel > $iteratorLevel) { continue; // a valid json chunk is not completed yet } if ($jsonBuffer !== '') { $valueResult = $this->jsonDecoder->decode($jsonBuffer); $jsonBuffer = ''; if ( ! $valueResult->isOk()) { $this->error($valueResult->getErrorMessage(), $token); } if ($iteratorStruct == '[') { yield $valueResult->getValue(); } else { $keyResult = $this->jsonDecoder->decode($key); if ( ! $keyResult->isOk()) { $this->error($keyResult->getErrorMessage(), $key); } yield $keyResult->getValue() => $valueResult->getValue(); unset($keyResult); } unset($valueResult); } if ($jsonPointerPath == $currentPath || $jsonPointerPath == $currentPathWildcard) { if ( ! in_array($this->matchedJsonPointer, $pointersFound, true)) { $pointersFound[] = $this->matchedJsonPointer; } } elseif (count($pointersFound) == count($this->jsonPointers)) { $subtreeEnded = true; break; } } if ($token === null) { $this->error('Cannot iterate empty JSON', $token); } if ($currentLevel > -1 && ! $subtreeEnded) { $this->error('JSON string ended unexpectedly', $token, UnexpectedEndSyntaxErrorException::class); } if (count($pointersFound) !== count($this->jsonPointers)) { throw new PathNotFoundException(sprintf("Paths '%s' were not found in json stream.", implode(', ', array_diff($this->jsonPointers, $pointersFound)))); } $this->matchedJsonPointer = null; $this->currentPath = null; } private function tokenTypes() { return [ 'n' => self::SCALAR_CONST, 't' => self::SCALAR_CONST, 'f' => self::SCALAR_CONST, '-' => self::SCALAR_CONST, '0' => self::SCALAR_CONST, '1' => self::SCALAR_CONST, '2' => self::SCALAR_CONST, '3' => self::SCALAR_CONST, '4' => self::SCALAR_CONST, '5' => self::SCALAR_CONST, '6' => self::SCALAR_CONST, '7' => self::SCALAR_CONST, '8' => self::SCALAR_CONST, '9' => self::SCALAR_CONST, '"' => self::SCALAR_STRING, '{' => self::OBJECT_START, '}' => self::OBJECT_END, '[' => self::ARRAY_START, ']' => self::ARRAY_END, ',' => self::COMMA, ':' => self::COLON, ]; } private function getMatchingJsonPointerPath(): array { $matchingPointer = key($this->paths); if (count($this->paths) === 1) { $this->matchedJsonPointer = $matchingPointer; return $this->paths[$matchingPointer]; } $currentPathLength = count($this->currentPath); $matchLength = -1; foreach ($this->paths as $jsonPointer => $path) { $matchingReferenceTokens = []; foreach ($path as $i => $referenceToken) { if ( ! isset($this->currentPath[$i]) || ( $this->currentPath[$i] !== $referenceToken && ValidJsonPointers::wildcardify($this->currentPath[$i]) !== $referenceToken ) ) { continue; } $matchingReferenceTokens[$i] = $referenceToken; } if (empty($matchingReferenceTokens)) { continue; } $currentMatchLength = count($matchingReferenceTokens); if ($currentMatchLength > $matchLength) { $matchingPointer = $jsonPointer; $matchLength = $currentMatchLength; } if ($matchLength === $currentPathLength) { break; } } $this->matchedJsonPointer = $matchingPointer; return $this->paths[$matchingPointer]; } public function getJsonPointers(): array { return array_values($this->jsonPointers); } public function getCurrentJsonPointer(): string { if ($this->currentPath === null) { throw new JsonMachineException(__METHOD__.' must be called inside a loop'); } return self::pathToJsonPointer($this->currentPath); } public function getMatchedJsonPointer(): string { if ($this->matchedJsonPointer === null) { throw new JsonMachineException(__METHOD__.' must be called inside a loop'); } return $this->matchedJsonPointer; } /** * @param string $msg * @param string $token * @param string $exception */ private function error($msg, $token, $exception = SyntaxErrorException::class) { throw new $exception($msg." '".$token."'", $this->tokens->getPosition()); } /** * @return int * * @throws JsonMachineException */ public function getPosition() { if ($this->tokens instanceof PositionAware) { return $this->tokens->getPosition(); } throw new JsonMachineException('Provided tokens iterable must implement PositionAware to call getPosition on it.'); } private static function jsonPointerToPath(string $jsonPointer): array { return array_slice(array_map(function ($jsonPointerPart) { return str_replace(['~1', '~0'], ['/', '~'], $jsonPointerPart); }, explode('/', $jsonPointer)), 1); } private static function pathToJsonPointer(array $path): string { $encodedParts = array_map(function ($addressPart) { return str_replace(['~', '/'], ['~0', '~1'], $addressPart); }, $path); array_unshift($encodedParts, ''); return implode('/', $encodedParts); } } PK!"StringChunks.phpnu[string = $string; $this->chunkSize = $chunkSize; } /** * @return \Generator */ #[\ReturnTypeWillChange] public function getIterator() { $len = strlen($this->string); for ($i = 0; $i < $len; $i += $this->chunkSize) { yield substr($this->string, $i, $this->chunkSize); } } } PK!#TokensWithDebugging.phpnu[ $jsonChunks */ public function __construct($jsonChunks) { $this->jsonChunks = $jsonChunks; } /** * @return \Generator */ #[\ReturnTypeWillChange] public function getIterator() { // Treat UTF-8 BOM bytes as whitespace ${"\xEF"} = ${"\xBB"} = ${"\xBF"} = 0; ${' '} = 0; ${"\n"} = 0; ${"\r"} = 0; ${"\t"} = 0; ${'{'} = 1; ${'}'} = 1; ${'['} = 1; ${']'} = 1; ${':'} = 1; ${','} = 1; $inString = false; $tokenBuffer = ''; $escaping = false; $tokenWidth = 0; $ignoreLF = false; $position = 0; $line = 1; $column = 0; foreach ($this->jsonChunks as $bytes) { $bytesLength = strlen($bytes); for ($i = 0; $i < $bytesLength; ++$i) { $byte = $bytes[$i]; if ($inString) { if ($byte == '"' && ! $escaping) { $inString = false; } $escaping = ($byte == '\\' && ! $escaping); $tokenBuffer .= $byte; ++$tokenWidth; continue; } if (isset($$byte)) { ++$column; if ($tokenBuffer != '') { $this->position = $position + $i; $this->column = $column; $this->line = $line; yield $tokenBuffer; $column += $tokenWidth; $tokenBuffer = ''; $tokenWidth = 0; } if ($$byte) { // is not whitespace $this->position = $position + $i + 1; $this->column = $column; $this->line = $line; yield $byte; // track line number and reset column for each newline } elseif ($byte == "\n") { // handle CRLF newlines if ($ignoreLF) { --$column; $ignoreLF = false; continue; } ++$line; $column = 0; } elseif ($byte == "\r") { ++$line; $ignoreLF = true; $column = 0; } } else { if ($byte == '"') { $inString = true; } $tokenBuffer .= $byte; ++$tokenWidth; } } $position += $i; } $this->position = $position; if ($tokenBuffer != '') { $this->column = $column; yield $tokenBuffer; } } /** * @return int */ public function getPosition() { return $this->position; } /** * Returns the line number of the lexeme currently being processed (index starts at one). * * @return int */ public function getLine() { return $this->line; } /** * The position of currently being processed lexeme within the line (index starts at one). * * @return int */ public function getColumn() { return $this->column; } } PK!<  ValidJsonPointers.phpnu[jsonPointers = array_values($jsonPointers); } /** * @throws InvalidArgumentException */ public function toArray(): array { if ( ! $this->validated) { $this->validate(); } return $this->jsonPointers; } /** * @throws InvalidArgumentException */ private function validate() { $this->validateFormat(); $this->validateJsonPointersDoNotIntersect(); $this->validated = true; } /** * @throws InvalidArgumentException */ private function validateFormat() { foreach ($this->jsonPointers as $jsonPointerEl) { if (preg_match('_^(/(([^/~])|(~[01]))*)*$_', $jsonPointerEl) === 0) { throw new InvalidArgumentException( sprintf("Given value '%s' of \$jsonPointer is not valid JSON Pointer", $jsonPointerEl) ); } } } /** * @throws InvalidArgumentException */ private function validateJsonPointersDoNotIntersect() { foreach ($this->jsonPointers as $keyA => $jsonPointerA) { foreach ($this->jsonPointers as $keyB => $jsonPointerB) { if ($keyA === $keyB) { continue; } if ($jsonPointerA === $jsonPointerB || self::str_contains($jsonPointerA, $jsonPointerB) || self::str_contains($jsonPointerA, self::wildcardify($jsonPointerB)) ) { throw new InvalidArgumentException( sprintf( "JSON Pointers must not intersect. At least these two do: '%s', '%s'", $jsonPointerA, $jsonPointerB ) ); } } } } public static function wildcardify(string $jsonPointerPart): string { return preg_replace('~/\d+(/|$)~S', '/-$1', $jsonPointerPart); } /** * @see https://github.com/symfony/polyfill/blob/v1.24.0/src/Php80/Php80.php */ public static function str_contains(string $haystack, string $needle): bool { return '' === $needle || false !== strpos($haystack, $needle); } } PK!966DegradedUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; /** * @deprecated DegradedUuid is no longer necessary to represent UUIDs on 32-bit * systems. Transition typehints to {@see UuidInterface}. * * @psalm-immutable */ class DegradedUuid extends Uuid { } PK!Kf22DeprecatedUuidMethodsTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Throwable; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * This trait encapsulates deprecated methods for ramsey/uuid; this trait and * its methods will be removed in ramsey/uuid 5.0.0. * * @psalm-immutable */ trait DeprecatedUuidMethodsTrait { /** * @var Rfc4122FieldsInterface */ protected $fields; /** * @var NumberConverterInterface */ protected $numberConverter; /** * @var TimeConverterInterface */ protected $timeConverter; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSeqHiAndReserved(): string { return $this->numberConverter->fromHex($this->fields->getClockSeqHiAndReserved()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}. */ public function getClockSeqHiAndReservedHex(): string { return $this->fields->getClockSeqHiAndReserved()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSeqLow(): string { return $this->numberConverter->fromHex($this->fields->getClockSeqLow()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}. */ public function getClockSeqLowHex(): string { return $this->fields->getClockSeqLow()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSequence(): string { return $this->numberConverter->fromHex($this->fields->getClockSeq()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}. */ public function getClockSequenceHex(): string { return $this->fields->getClockSeq()->toString(); } /** * @deprecated This method will be removed in 5.0.0. There is no alternative * recommendation, so plan accordingly. */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * @deprecated In ramsey/uuid version 5.0.0, this will be removed. * It is available at {@see UuidV1::getDateTime()}. * * @return DateTimeImmutable An immutable instance of DateTimeInterface * * @throws UnsupportedOperationException if UUID is not time-based * @throws DateTimeException if DateTime throws an exception/error */ public function getDateTime(): DateTimeInterface { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. * * @return string[] */ public function getFieldsHex(): array { return [ 'time_low' => $this->fields->getTimeLow()->toString(), 'time_mid' => $this->fields->getTimeMid()->toString(), 'time_hi_and_version' => $this->fields->getTimeHiAndVersion()->toString(), 'clock_seq_hi_and_reserved' => $this->fields->getClockSeqHiAndReserved()->toString(), 'clock_seq_low' => $this->fields->getClockSeqLow()->toString(), 'node' => $this->fields->getNode()->toString(), ]; } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBits(): string { $leastSignificantHex = substr($this->getHex()->toString(), 16); return $this->numberConverter->fromHex($leastSignificantHex); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBitsHex(): string { return substr($this->getHex()->toString(), 16); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBits(): string { $mostSignificantHex = substr($this->getHex()->toString(), 0, 16); return $this->numberConverter->fromHex($mostSignificantHex); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBitsHex(): string { return substr($this->getHex()->toString(), 0, 16); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getNode()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getNode(): string { return $this->numberConverter->fromHex($this->fields->getNode()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getNode()}. */ public function getNodeHex(): string { return $this->fields->getNode()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getTimeHiAndVersion(): string { return $this->numberConverter->fromHex($this->fields->getTimeHiAndVersion()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}. */ public function getTimeHiAndVersionHex(): string { return $this->fields->getTimeHiAndVersion()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getTimeLow(): string { return $this->numberConverter->fromHex($this->fields->getTimeLow()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()}. */ public function getTimeLowHex(): string { return $this->fields->getTimeLow()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getTimeMid(): string { return $this->numberConverter->fromHex($this->fields->getTimeMid()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()}. */ public function getTimeMidHex(): string { return $this->fields->getTimeMid()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getTimestamp(): string { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $this->numberConverter->fromHex($this->fields->getTimestamp()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()}. */ public function getTimestampHex(): string { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $this->fields->getTimestamp()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. */ public function getVariant(): ?int { return $this->fields->getVariant(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. */ public function getVersion(): ?int { return $this->fields->getVersion(); } } PK!݁BinaryUtils.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; /** * Provides binary math utilities */ class BinaryUtils { /** * Applies the RFC 4122 variant field to the 16-bit clock sequence * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant * * @param int $clockSeq The 16-bit clock sequence value before the RFC 4122 * variant is applied * * @return int The 16-bit clock sequence multiplexed with the UUID variant * * @psalm-pure */ public static function applyVariant(int $clockSeq): int { $clockSeq = $clockSeq & 0x3fff; $clockSeq |= 0x8000; return $clockSeq; } /** * Applies the RFC 4122 version number to the 16-bit `time_hi_and_version` field * * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version * * @param int $timeHi The value of the 16-bit `time_hi_and_version` field * before the RFC 4122 version is applied * @param int $version The RFC 4122 version to apply to the `time_hi` field * * @return int The 16-bit time_hi field of the timestamp multiplexed with * the UUID version number * * @psalm-pure */ public static function applyVersion(int $timeHi, int $version): int { $timeHi = $timeHi & 0x0fff; $timeHi |= $version << 12; return $timeHi; } } PK!C Validator/ValidatorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Validator; /** * A validator validates a string as a proper UUID * * @psalm-immutable */ interface ValidatorInterface { /** * Returns the regular expression pattern used by this validator * * @return string The regular expression pattern this validator uses * * @psalm-return non-empty-string */ public function getPattern(): string; /** * Returns true if the provided string represents a UUID * * @param string $uuid The string to validate as a UUID * * @return bool True if the string is a valid UUID, false otherwise */ public function validate(string $uuid): bool; } PK!m4t:jjValidator/GenericValidator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Validator; use Ramsey\Uuid\Uuid; use function preg_match; use function str_replace; /** * GenericValidator validates strings as UUIDs of any variant * * @psalm-immutable */ final class GenericValidator implements ValidatorInterface { /** * Regular expression pattern for matching a UUID of any variant. */ private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\z'; /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function getPattern(): string { return self::VALID_PATTERN; } public function validate(string $uuid): bool { $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } PK!HBFFLazy/LazyUuidFromString.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Lazy; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\UuidFactory; use Ramsey\Uuid\UuidInterface; use ValueError; use function assert; use function bin2hex; use function hex2bin; use function sprintf; use function str_replace; use function substr; /** * Lazy version of a UUID: its format has not been determined yet, so it is mostly only usable for string/bytes * conversion. This object optimizes instantiation, serialization and string conversion time, at the cost of * increased overhead for more advanced UUID operations. * * @internal this type is used internally for performance reasons, and is not supposed to be directly referenced * in consumer libraries. * * @psalm-immutable * * Note: the {@see FieldsInterface} does not declare methods that deprecated API * relies upon: the API has been ported from the {@see \Ramsey\Uuid\Uuid} definition, * and is deprecated anyway. * Note: the deprecated API from {@see \Ramsey\Uuid\Uuid} is in use here (on purpose): it will be removed * once the deprecated API is gone from this class too. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod */ final class LazyUuidFromString implements UuidInterface { public const VALID_REGEX = '/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ms'; /** * @var string * @psalm-var non-empty-string */ private $uuid; /** @var UuidInterface|null */ private $unwrapped; /** @psalm-param non-empty-string $uuid */ public function __construct(string $uuid) { $this->uuid = $uuid; } /** @psalm-pure */ public static function fromBytes(string $bytes): self { $base16Uuid = bin2hex($bytes); return new self( substr($base16Uuid, 0, 8) . '-' . substr($base16Uuid, 8, 4) . '-' . substr($base16Uuid, 12, 4) . '-' . substr($base16Uuid, 16, 4) . '-' . substr($base16Uuid, 20, 12) ); } public function serialize(): string { return $this->uuid; } /** * @return array{string: string} * * @psalm-return array{string: non-empty-string} */ public function __serialize(): array { return ['string' => $this->uuid]; } /** * {@inheritDoc} * * @param string $serialized * * @psalm-param non-empty-string $serialized */ public function unserialize($serialized): void { $this->uuid = $serialized; } /** * @param array{string: string} $data * * @psalm-param array{string: non-empty-string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } /** @psalm-suppress DeprecatedMethod */ public function getNumberConverter(): NumberConverterInterface { return ($this->unwrapped ?? $this->unwrap()) ->getNumberConverter(); } /** * {@inheritDoc} * * @psalm-suppress DeprecatedMethod */ public function getFieldsHex(): array { return ($this->unwrapped ?? $this->unwrap()) ->getFieldsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSeqHiAndReservedHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSeqHiAndReservedHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSeqLowHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSeqLowHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSequenceHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSequenceHex(); } /** @psalm-suppress DeprecatedMethod */ public function getDateTime(): DateTimeInterface { return ($this->unwrapped ?? $this->unwrap()) ->getDateTime(); } /** @psalm-suppress DeprecatedMethod */ public function getLeastSignificantBitsHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getLeastSignificantBitsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getMostSignificantBitsHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getMostSignificantBitsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getNodeHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getNodeHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeHiAndVersionHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeHiAndVersionHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeLowHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeLowHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeMidHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeMidHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimestampHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimestampHex(); } /** @psalm-suppress DeprecatedMethod */ public function getUrn(): string { return ($this->unwrapped ?? $this->unwrap()) ->getUrn(); } /** @psalm-suppress DeprecatedMethod */ public function getVariant(): ?int { return ($this->unwrapped ?? $this->unwrap()) ->getVariant(); } /** @psalm-suppress DeprecatedMethod */ public function getVersion(): ?int { return ($this->unwrapped ?? $this->unwrap()) ->getVersion(); } public function compareTo(UuidInterface $other): int { return ($this->unwrapped ?? $this->unwrap()) ->compareTo($other); } public function equals(?object $other): bool { if (! $other instanceof UuidInterface) { return false; } return $this->uuid === $other->toString(); } /** * {@inheritDoc} * * @psalm-suppress MoreSpecificReturnType * @psalm-suppress LessSpecificReturnStatement we know that {@see self::$uuid} is a non-empty string, so * we know that {@see hex2bin} will retrieve a non-empty string too. */ public function getBytes(): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return (string) hex2bin(str_replace('-', '', $this->uuid)); } public function getFields(): FieldsInterface { return ($this->unwrapped ?? $this->unwrap()) ->getFields(); } public function getHex(): Hexadecimal { return ($this->unwrapped ?? $this->unwrap()) ->getHex(); } public function getInteger(): IntegerObject { return ($this->unwrapped ?? $this->unwrap()) ->getInteger(); } public function toString(): string { return $this->uuid; } public function __toString(): string { return $this->uuid; } public function jsonSerialize(): string { return $this->uuid; } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSeqHiAndReserved(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeqHiAndReserved() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSeqLow(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeqLow() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSequence(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeq() ->toString() ); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getLeastSignificantBits(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex(substr($instance->getHex()->toString(), 16)); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getMostSignificantBits(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex(substr($instance->getHex()->toString(), 0, 16)); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getNode()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getNode(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getNode() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeHiAndVersion(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeHiAndVersion() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeLow(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeLow() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeMid(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeMid() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimestamp(): string { $instance = ($this->unwrapped ?? $this->unwrap()); $fields = $instance->getFields(); if ($fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $instance->getNumberConverter() ->fromHex($fields->getTimestamp()->toString()); } public function toUuidV1(): UuidV1 { $instance = ($this->unwrapped ?? $this->unwrap()); if ($instance instanceof UuidV1) { return $instance; } assert($instance instanceof UuidV6); return $instance->toUuidV1(); } public function toUuidV6(): UuidV6 { $instance = ($this->unwrapped ?? $this->unwrap()); assert($instance instanceof UuidV6); return $instance; } /** * @psalm-suppress ImpureMethodCall the retrieval of the factory is a clear violation of purity here: this is a * known pitfall of the design of this library, where a value object contains * a mutable reference to a factory. We use a fixed factory here, so the violation * will not have real-world effects, as this object is only instantiated with the * default factory settings/features. * @psalm-suppress InaccessibleProperty property {@see $unwrapped} is used as a cache: we don't expose it to the * outside world, so we should be fine here. */ private function unwrap(): UuidInterface { return $this->unwrapped = (new UuidFactory()) ->fromString($this->uuid); } } PK!R0Rfc4122/UuidV2.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; use Throwable; use function hexdec; use function str_pad; use const STR_PAD_LEFT; /** * DCE Security version, or version 2, UUIDs include local domain identifier, * local ID for the specified domain, and node values that are combined into a * 128-bit unsigned integer * * @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services * @link https://publications.opengroup.org/c706 DCE 1.1: Remote Procedure Call * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1: Auth & Sec, §5.2.1.1 * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1: Auth & Sec, §11.5.1.1 * @link https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm DCE 1.1: RPC, Appendix A * @link https://github.com/google/uuid Go package for UUIDs (includes DCE implementation) * * @psalm-immutable */ final class UuidV2 extends Uuid implements UuidInterface { /** * Creates a version 2 (DCE Security) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_DCE_SECURITY) { throw new InvalidArgumentException( 'Fields used to create a UuidV2 must represent a ' . 'version 2 (DCE Security) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } /** * Returns a DateTimeInterface object representing the timestamp associated * with the UUID * * It is important to note that a version 2 UUID suffers from some loss of * fidelity of the timestamp, due to replacing the time_low field with the * local identifier. When constructing the timestamp value for date * purposes, we replace the local identifier bits with zeros. As a result, * the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 * minutes, 9 seconds, and 496730 microseconds). * * Astute observers might note this value directly corresponds to 2^32 - 1, * or 0xffffffff. The local identifier is 32-bits, and we have set each of * these bits to 0, so the maximum range of timestamp drift is 0x00000000 * to 0xffffffff (counted in 100-nanosecond intervals). * * @return DateTimeImmutable A PHP DateTimeImmutable instance representing * the timestamp of a version 2 UUID */ public function getDateTime(): DateTimeInterface { $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Returns the local domain used to create this version 2 UUID */ public function getLocalDomain(): int { /** @var Rfc4122FieldsInterface $fields */ $fields = $this->getFields(); return (int) hexdec($fields->getClockSeqLow()->toString()); } /** * Returns the string name of the local domain */ public function getLocalDomainName(): string { return Uuid::DCE_DOMAIN_NAMES[$this->getLocalDomain()]; } /** * Returns the local identifier for the domain used to create this version 2 UUID */ public function getLocalIdentifier(): IntegerObject { /** @var Rfc4122FieldsInterface $fields */ $fields = $this->getFields(); return new IntegerObject( $this->numberConverter->fromHex($fields->getTimeLow()->toString()) ); } } PK!- Rfc4122/UuidV1.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; use Throwable; use function str_pad; use const STR_PAD_LEFT; /** * Time-based, or version 1, UUIDs include timestamp, clock sequence, and node * values that are combined into a 128-bit unsigned integer * * @psalm-immutable */ final class UuidV1 extends Uuid implements UuidInterface { /** * Creates a version 1 (time-based) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_TIME) { throw new InvalidArgumentException( 'Fields used to create a UuidV1 must represent a ' . 'version 1 (time-based) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } /** * Returns a DateTimeInterface object representing the timestamp associated * with the UUID * * The timestamp value is only meaningful in a time-based UUID, which * has version type 1. * * @return DateTimeImmutable A PHP DateTimeImmutable instance representing * the timestamp of a version 1 UUID */ public function getDateTime(): DateTimeInterface { $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } } PK!}uRfc4122/UuidV3.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Version 3 UUIDs are named-based, using combination of a namespace and name * that are hashed into a 128-bit unsigned integer using MD5 * * @psalm-immutable */ final class UuidV3 extends Uuid implements UuidInterface { /** * Creates a version 3 (name-based, MD5-hashed) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_MD5) { throw new InvalidArgumentException( 'Fields used to create a UuidV3 must represent a ' . 'version 3 (name-based, MD5-hashed) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PK!ͧMMRfc4122/UuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Nonstandard\UuidV6; use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface; use Ramsey\Uuid\UuidInterface; use Throwable; /** * UuidBuilder builds instances of RFC 4122 UUIDs * * @psalm-immutable */ class UuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeConverterInterface */ private $timeConverter; /** * Constructs the DefaultUuidBuilder * * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Uuid * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( NumberConverterInterface $numberConverter, TimeConverterInterface $timeConverter ) { $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter; } /** * Builds and returns a Uuid * * @param CodecInterface $codec The codec to use for building this Uuid instance * @param string $bytes The byte string from which to construct a UUID * * @return Rfc4122UuidInterface UuidBuilder returns instances of Rfc4122UuidInterface * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { $fields = $this->buildFields($bytes); if ($fields->isNil()) { return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } switch ($fields->getVersion()) { case 1: return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter); case 2: return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter); case 3: return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter); case 4: return new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter); case 5: return new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter); case 6: return new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter); } throw new UnsupportedOperationException( 'The UUID version in the given fields is not supported ' . 'by this UUID builder' ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): FieldsInterface { return new Fields($bytes); } } PK!zRfc4122/UuidV5.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Version 5 UUIDs are named-based, using combination of a namespace and name * that are hashed into a 128-bit unsigned integer using SHA1 * * @psalm-immutable */ final class UuidV5 extends Uuid implements UuidInterface { /** * Creates a version 5 (name-based, SHA1-hashed) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_SHA1) { throw new InvalidArgumentException( 'Fields used to create a UuidV5 must represent a ' . 'version 5 (named-based, SHA1-hashed) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PK!dMccRfc4122/Validator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Validator\ValidatorInterface; use function preg_match; use function str_replace; /** * Rfc4122\Validator validates strings as UUIDs of the RFC 4122 variant * * @psalm-immutable */ final class Validator implements ValidatorInterface { private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-' . '[1-5]{1}[0-9A-Fa-f]{3}-[ABab89]{1}[0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z'; /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function getPattern(): string { return self::VALID_PATTERN; } public function validate(string $uuid): bool { $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } PK!HRfc4122/VersionTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; /** * Provides common functionality for handling the version, as defined by RFC 4122 * * @psalm-immutable */ trait VersionTrait { /** * Returns the version */ abstract public function getVersion(): ?int; /** * Returns true if these fields represent a nil UUID */ abstract public function isNil(): bool; /** * Returns true if the version matches one of those defined by RFC 4122 * * @return bool True if the UUID version is valid, false otherwise */ private function isCorrectVersion(): bool { if ($this->isNil()) { return true; } switch ($this->getVersion()) { case 1: case 2: case 3: case 4: case 5: case 6: return true; } return false; } } PK!'Rfc4122/NilTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; /** * Provides common functionality for nil UUIDs * * The nil UUID is special form of UUID that is specified to have all 128 bits * set to zero. * * @link https://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID * * @psalm-immutable */ trait NilTrait { /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns true if the byte string represents a nil UUID */ public function isNil(): bool { return $this->getBytes() === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; } } PK!NTRfc4122/FieldsInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Fields\FieldsInterface as BaseFieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * RFC 4122 defines fields for a specific variant of UUID * * The fields of an RFC 4122 variant UUID are: * * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer * * **time_hi_and_version**: The high field of the timestamp multiplexed with * the version number, an unsigned 16-bit integer * * **clock_seq_hi_and_reserved**: The high field of the clock sequence * multiplexed with the variant, an unsigned 8-bit integer * * **clock_seq_low**: The low field of the clock sequence, an unsigned * 8-bit integer * * **node**: The spatially unique node identifier, an unsigned 48-bit * integer * * @link http://tools.ietf.org/html/rfc4122#section-4.1 RFC 4122, § 4.1: Format * * @psalm-immutable */ interface FieldsInterface extends BaseFieldsInterface { /** * Returns the full 16-bit clock sequence, with the variant bits (two most * significant bits) masked out */ public function getClockSeq(): Hexadecimal; /** * Returns the high field of the clock sequence multiplexed with the variant */ public function getClockSeqHiAndReserved(): Hexadecimal; /** * Returns the low field of the clock sequence */ public function getClockSeqLow(): Hexadecimal; /** * Returns the node field */ public function getNode(): Hexadecimal; /** * Returns the high field of the timestamp multiplexed with the version */ public function getTimeHiAndVersion(): Hexadecimal; /** * Returns the low field of the timestamp */ public function getTimeLow(): Hexadecimal; /** * Returns the middle field of the timestamp */ public function getTimeMid(): Hexadecimal; /** * Returns the full 60-bit timestamp, without the version */ public function getTimestamp(): Hexadecimal; /** * Returns the variant * * The variant number describes the layout of the UUID. The variant * number has the following meaning: * * - 0 - Reserved for NCS backward compatibility * - 2 - The RFC 4122 variant * - 6 - Reserved, Microsoft Corporation backward compatibility * - 7 - Reserved for future definition * * For RFC 4122 variant UUIDs, this value should always be the integer `2`. * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public function getVariant(): int; /** * Returns the version * * The version number describes how the UUID was generated and has the * following meaning: * * 1. Time-based UUID * 2. DCE security UUID * 3. Name-based UUID hashed with MD5 * 4. Randomly generated UUID * 5. Name-based UUID hashed with SHA-1 * * This returns `null` if the UUID is not an RFC 4122 variant, since version * is only meaningful for this variant. * * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public function getVersion(): ?int; /** * Returns true if these fields represent a nil UUID * * The nil UUID is special form of UUID that is specified to have all 128 * bits set to zero. */ public function isNil(): bool; } PK!Rfc4122/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Uuid; use function bin2hex; use function dechex; use function hexdec; use function sprintf; use function str_pad; use function strlen; use function substr; use function unpack; use const STR_PAD_LEFT; /** * RFC 4122 variant UUIDs are comprised of a set of named fields * * Internally, this class represents the fields together as a 16-byte binary * string. * * @psalm-immutable */ final class Fields implements FieldsInterface { use NilTrait; use SerializableFieldsTrait; use VariantTrait; use VersionTrait; /** * @var string */ private $bytes; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes * @throws InvalidArgumentException if the byte string does not represent an RFC 4122 UUID * @throws InvalidArgumentException if the byte string does not contain a valid version */ public function __construct(string $bytes) { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($bytes) . ' bytes' ); } $this->bytes = $bytes; if (!$this->isCorrectVariant()) { throw new InvalidArgumentException( 'The byte string received does not conform to the RFC 4122 variant' ); } if (!$this->isCorrectVersion()) { throw new InvalidArgumentException( 'The byte string received does not contain a valid RFC 4122 version' ); } } public function getBytes(): string { return $this->bytes; } public function getClockSeq(): Hexadecimal { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getTimeHiAndVersion(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); } public function getTimeLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); } public function getTimeMid(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); } /** * Returns the full 60-bit timestamp, without the version * * For version 2 UUIDs, the time_low field is the local identifier and * should not be returned as part of the time. For this reason, we set the * bottom 32 bits of the timestamp to 0's. As a result, there is some loss * of fidelity of the timestamp, for version 2 UUIDs. The timestamp can be * off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9 seconds, and * 496730 microseconds). * * For version 6 UUIDs, the timestamp order is reversed from the typical RFC * 4122 order (the time bits are in the correct bit order, so that it is * monotonically increasing). In returning the timestamp value, we put the * bits in the order: time_low + time_mid + time_hi. */ public function getTimestamp(): Hexadecimal { switch ($this->getVersion()) { case Uuid::UUID_TYPE_DCE_SECURITY: $timestamp = sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), '' ); break; case Uuid::UUID_TYPE_PEABODY: $timestamp = sprintf( '%08s%04s%03x', $this->getTimeLow()->toString(), $this->getTimeMid()->toString(), hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff ); break; default: $timestamp = sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() ); } return new Hexadecimal($timestamp); } public function getVersion(): ?int { if ($this->isNil()) { return null; } /** @var array $parts */ $parts = unpack('n*', $this->bytes); return (int) $parts[4] >> 12; } private function isCorrectVariant(): bool { if ($this->isNil()) { return true; } return $this->getVariant() === Uuid::RFC_4122; } } PK!Rfc4122/UuidV4.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Random, or version 4, UUIDs are randomly or pseudo-randomly generated 128-bit * integers * * @psalm-immutable */ final class UuidV4 extends Uuid implements UuidInterface { /** * Creates a version 4 (random) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_RANDOM) { throw new InvalidArgumentException( 'Fields used to create a UuidV4 must represent a ' . 'version 4 (random) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PK!cM??Rfc4122/NilUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; /** * The nil UUID is special form of UUID that is specified to have all 128 bits * set to zero * * @psalm-immutable */ final class NilUuid extends Uuid implements UuidInterface { } PK!w=Rfc4122/UuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\UuidInterface as BaseUuidInterface; /** * Also known as a Leach-Salz variant UUID, an RFC 4122 variant UUID is a * universally unique identifier defined by RFC 4122 * * @link https://tools.ietf.org/html/rfc4122 RFC 4122 * * @psalm-immutable */ interface UuidInterface extends BaseUuidInterface { } PK!ïhA Rfc4122/VariantTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Exception\InvalidBytesException; use Ramsey\Uuid\Uuid; use function decbin; use function str_pad; use function strlen; use function strpos; use function substr; use function unpack; use const STR_PAD_LEFT; /** * Provides common functionality for handling the variant, as defined by RFC 4122 * * @psalm-immutable */ trait VariantTrait { /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns the variant identifier, according to RFC 4122, for the given bytes * * The following values may be returned: * * - `0` -- Reserved, NCS backward compatibility. * - `2` -- The variant specified in RFC 4122. * - `6` -- Reserved, Microsoft Corporation backward compatibility. * - `7` -- Reserved for future definition. * * @link https://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant * * @return int The variant identifier, according to RFC 4122 */ public function getVariant(): int { if (strlen($this->getBytes()) !== 16) { throw new InvalidBytesException('Invalid number of bytes'); } /** @var array $parts */ $parts = unpack('n*', $this->getBytes()); // $parts[5] is a 16-bit, unsigned integer containing the variant bits // of the UUID. We convert this integer into a string containing a // binary representation, padded to 16 characters. We analyze the first // three characters (three most-significant bits) to determine the // variant. $binary = str_pad( decbin((int) $parts[5]), 16, '0', STR_PAD_LEFT ); $msb = substr($binary, 0, 3); if ($msb === '111') { $variant = Uuid::RESERVED_FUTURE; } elseif ($msb === '110') { $variant = Uuid::RESERVED_MICROSOFT; } elseif (strpos($msb, '10') === 0) { $variant = Uuid::RFC_4122; } else { $variant = Uuid::RESERVED_NCS; } return $variant; } } PK!7M}fR1R1FeatureSet.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use Ramsey\Uuid\Builder\FallbackBuilder; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Codec\GuidStringCodec; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Generator\DceSecurityGenerator; use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; use Ramsey\Uuid\Generator\NameGeneratorFactory; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\PeclUuidNameGenerator; use Ramsey\Uuid\Generator\PeclUuidRandomGenerator; use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; use Ramsey\Uuid\Generator\RandomGeneratorFactory; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorFactory; use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; use Ramsey\Uuid\Provider\Node\RandomNodeProvider; use Ramsey\Uuid\Provider\Node\SystemNodeProvider; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\SystemTimeProvider; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Ramsey\Uuid\Validator\GenericValidator; use Ramsey\Uuid\Validator\ValidatorInterface; use const PHP_INT_SIZE; /** * FeatureSet detects and exposes available features in the current environment * * A feature set is used by UuidFactory to determine the available features and * capabilities of the environment. */ class FeatureSet { /** * @var bool */ private $disableBigNumber = false; /** * @var bool */ private $disable64Bit = false; /** * @var bool */ private $ignoreSystemNode = false; /** * @var bool */ private $enablePecl = false; /** * @var UuidBuilderInterface */ private $builder; /** * @var CodecInterface */ private $codec; /** * @var DceSecurityGeneratorInterface */ private $dceSecurityGenerator; /** * @var NameGeneratorInterface */ private $nameGenerator; /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var RandomGeneratorInterface */ private $randomGenerator; /** * @var TimeGeneratorInterface */ private $timeGenerator; /** * @var TimeProviderInterface */ private $timeProvider; /** * @var ValidatorInterface */ private $validator; /** * @var CalculatorInterface */ private $calculator; /** * @param bool $useGuids True build UUIDs using the GuidStringCodec * @param bool $force32Bit True to force the use of 32-bit functionality * (primarily for testing purposes) * @param bool $forceNoBigNumber True to disable the use of moontoast/math * (primarily for testing purposes) * @param bool $ignoreSystemNode True to disable attempts to check for the * system node ID (primarily for testing purposes) * @param bool $enablePecl True to enable the use of the PeclUuidTimeGenerator * to generate version 1 UUIDs */ public function __construct( bool $useGuids = false, bool $force32Bit = false, bool $forceNoBigNumber = false, bool $ignoreSystemNode = false, bool $enablePecl = false ) { $this->disableBigNumber = $forceNoBigNumber; $this->disable64Bit = $force32Bit; $this->ignoreSystemNode = $ignoreSystemNode; $this->enablePecl = $enablePecl; $this->setCalculator(new BrickMathCalculator()); $this->builder = $this->buildUuidBuilder($useGuids); $this->codec = $this->buildCodec($useGuids); $this->nodeProvider = $this->buildNodeProvider(); $this->nameGenerator = $this->buildNameGenerator(); $this->randomGenerator = $this->buildRandomGenerator(); $this->setTimeProvider(new SystemTimeProvider()); $this->setDceSecurityProvider(new SystemDceSecurityProvider()); $this->validator = new GenericValidator(); } /** * Returns the builder configured for this environment */ public function getBuilder(): UuidBuilderInterface { return $this->builder; } /** * Returns the calculator configured for this environment */ public function getCalculator(): CalculatorInterface { return $this->calculator; } /** * Returns the codec configured for this environment */ public function getCodec(): CodecInterface { return $this->codec; } /** * Returns the DCE Security generator configured for this environment */ public function getDceSecurityGenerator(): DceSecurityGeneratorInterface { return $this->dceSecurityGenerator; } /** * Returns the name generator configured for this environment */ public function getNameGenerator(): NameGeneratorInterface { return $this->nameGenerator; } /** * Returns the node provider configured for this environment */ public function getNodeProvider(): NodeProviderInterface { return $this->nodeProvider; } /** * Returns the number converter configured for this environment */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * Returns the random generator configured for this environment */ public function getRandomGenerator(): RandomGeneratorInterface { return $this->randomGenerator; } /** * Returns the time converter configured for this environment */ public function getTimeConverter(): TimeConverterInterface { return $this->timeConverter; } /** * Returns the time generator configured for this environment */ public function getTimeGenerator(): TimeGeneratorInterface { return $this->timeGenerator; } /** * Returns the validator configured for this environment */ public function getValidator(): ValidatorInterface { return $this->validator; } /** * Sets the calculator to use in this environment */ public function setCalculator(CalculatorInterface $calculator): void { $this->calculator = $calculator; $this->numberConverter = $this->buildNumberConverter($calculator); $this->timeConverter = $this->buildTimeConverter($calculator); /** @psalm-suppress RedundantPropertyInitializationCheck */ if (isset($this->timeProvider)) { $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); } } /** * Sets the DCE Security provider to use in this environment */ public function setDceSecurityProvider(DceSecurityProviderInterface $dceSecurityProvider): void { $this->dceSecurityGenerator = $this->buildDceSecurityGenerator($dceSecurityProvider); } /** * Sets the node provider to use in this environment */ public function setNodeProvider(NodeProviderInterface $nodeProvider): void { $this->nodeProvider = $nodeProvider; $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); } /** * Sets the time provider to use in this environment */ public function setTimeProvider(TimeProviderInterface $timeProvider): void { $this->timeProvider = $timeProvider; $this->timeGenerator = $this->buildTimeGenerator($timeProvider); } /** * Set the validator to use in this environment */ public function setValidator(ValidatorInterface $validator): void { $this->validator = $validator; } /** * Returns a codec configured for this environment * * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec */ private function buildCodec(bool $useGuids = false): CodecInterface { if ($useGuids) { return new GuidStringCodec($this->builder); } return new StringCodec($this->builder); } /** * Returns a DCE Security generator configured for this environment */ private function buildDceSecurityGenerator( DceSecurityProviderInterface $dceSecurityProvider ): DceSecurityGeneratorInterface { return new DceSecurityGenerator( $this->numberConverter, $this->timeGenerator, $dceSecurityProvider ); } /** * Returns a node provider configured for this environment */ private function buildNodeProvider(): NodeProviderInterface { if ($this->ignoreSystemNode) { return new RandomNodeProvider(); } return new FallbackNodeProvider([ new SystemNodeProvider(), new RandomNodeProvider(), ]); } /** * Returns a number converter configured for this environment */ private function buildNumberConverter(CalculatorInterface $calculator): NumberConverterInterface { return new GenericNumberConverter($calculator); } /** * Returns a random generator configured for this environment */ private function buildRandomGenerator(): RandomGeneratorInterface { if ($this->enablePecl) { return new PeclUuidRandomGenerator(); } return (new RandomGeneratorFactory())->getGenerator(); } /** * Returns a time generator configured for this environment * * @param TimeProviderInterface $timeProvider The time provider to use with * the time generator */ private function buildTimeGenerator(TimeProviderInterface $timeProvider): TimeGeneratorInterface { if ($this->enablePecl) { return new PeclUuidTimeGenerator(); } return (new TimeGeneratorFactory( $this->nodeProvider, $this->timeConverter, $timeProvider ))->getGenerator(); } /** * Returns a name generator configured for this environment */ private function buildNameGenerator(): NameGeneratorInterface { if ($this->enablePecl) { return new PeclUuidNameGenerator(); } return (new NameGeneratorFactory())->getGenerator(); } /** * Returns a time converter configured for this environment */ private function buildTimeConverter(CalculatorInterface $calculator): TimeConverterInterface { $genericConverter = new GenericTimeConverter($calculator); if ($this->is64BitSystem()) { return new PhpTimeConverter($calculator, $genericConverter); } return $genericConverter; } /** * Returns a UUID builder configured for this environment * * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec */ private function buildUuidBuilder(bool $useGuids = false): UuidBuilderInterface { if ($useGuids) { return new GuidBuilder($this->numberConverter, $this->timeConverter); } return new FallbackBuilder([ new Rfc4122UuidBuilder($this->numberConverter, $this->timeConverter), new NonstandardUuidBuilder($this->numberConverter, $this->timeConverter), ]); } /** * Returns true if the PHP build is 64-bit */ private function is64BitSystem(): bool { return PHP_INT_SIZE === 8 && !$this->disable64Bit; } } PK!5GL Type/Time.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Type\Integer as IntegerObject; use ValueError; use stdClass; use function json_decode; use function json_encode; use function sprintf; /** * A value object representing a timestamp * * This class exists for type-safety purposes, to ensure that timestamps used * by ramsey/uuid are truly timestamp integers and not some other kind of string * or integer. * * @psalm-immutable */ final class Time implements TypeInterface { /** * @var IntegerObject */ private $seconds; /** * @var IntegerObject */ private $microseconds; /** * @param mixed $seconds * @param mixed $microseconds */ public function __construct($seconds, $microseconds = 0) { $this->seconds = new IntegerObject($seconds); $this->microseconds = new IntegerObject($microseconds); } public function getSeconds(): IntegerObject { return $this->seconds; } public function getMicroseconds(): IntegerObject { return $this->microseconds; } public function toString(): string { return $this->seconds->toString() . '.' . $this->microseconds->toString(); } public function __toString(): string { return $this->toString(); } /** * @return string[] */ public function jsonSerialize(): array { return [ 'seconds' => $this->getSeconds()->toString(), 'microseconds' => $this->getMicroseconds()->toString(), ]; } public function serialize(): string { return (string) json_encode($this); } /** * @return array{seconds: string, microseconds: string} */ public function __serialize(): array { return [ 'seconds' => $this->getSeconds()->toString(), 'microseconds' => $this->getMicroseconds()->toString(), ]; } /** * Constructs the object from a serialized string representation * * @param string $serialized The serialized string representation of the object * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress UnusedMethodCall */ public function unserialize($serialized): void { /** @var stdClass $time */ $time = json_decode($serialized); if (!isset($time->seconds) || !isset($time->microseconds)) { throw new UnsupportedOperationException( 'Attempted to unserialize an invalid value' ); } $this->__construct($time->seconds, $time->microseconds); } /** * @param array{seconds: string, microseconds: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['seconds']) || !isset($data['microseconds'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->__construct($data['seconds'], $data['microseconds']); } } PK!{{Type/NumberInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; /** * NumberInterface ensures consistency in numeric values returned by ramsey/uuid * * @psalm-immutable */ interface NumberInterface extends TypeInterface { /** * Returns true if this number is less than zero */ public function isNegative(): bool; } PK! Type/Hexadecimal.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function ctype_xdigit; use function sprintf; use function strpos; use function strtolower; use function substr; /** * A value object representing a hexadecimal number * * This class exists for type-safety purposes, to ensure that hexadecimal numbers * returned from ramsey/uuid methods as strings are truly hexadecimal and not some * other kind of string. * * @psalm-immutable */ final class Hexadecimal implements TypeInterface { /** * @var string */ private $value; /** * @param string $value The hexadecimal value to store */ public function __construct(string $value) { $value = strtolower($value); if (strpos($value, '0x') === 0) { $value = substr($value, 2); } if (!ctype_xdigit($value)) { throw new InvalidArgumentException( 'Value must be a hexadecimal number' ); } $this->value = $value; } public function toString(): string { return $this->value; } public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $serialized The serialized string representation of the object * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress UnusedMethodCall */ public function unserialize($serialized): void { $this->__construct($serialized); } /** * @param array{string: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } } PK!˄kդ Type/Decimal.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function is_numeric; use function sprintf; /** * A value object representing a decimal * * This class exists for type-safety purposes, to ensure that decimals * returned from ramsey/uuid methods as strings are truly decimals and not some * other kind of string. * * To support values as true decimals and not as floats or doubles, we store the * decimals as strings. * * @psalm-immutable */ final class Decimal implements NumberInterface { /** * @var string */ private $value; /** * @var bool */ private $isNegative = false; /** * @param mixed $value The decimal value to store */ public function __construct($value) { $value = (string) $value; if (!is_numeric($value)) { throw new InvalidArgumentException( 'Value must be a signed decimal or a string containing only ' . 'digits 0-9 and, optionally, a decimal point or sign (+ or -)' ); } // Remove the leading +-symbol. if (strpos($value, '+') === 0) { $value = substr($value, 1); } // For cases like `-0` or `-0.0000`, convert the value to `0`. if (abs((float) $value) === 0.0) { $value = '0'; } if (strpos($value, '-') === 0) { $this->isNegative = true; } $this->value = $value; } public function isNegative(): bool { return $this->isNegative; } public function toString(): string { return $this->value; } public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $serialized The serialized string representation of the object * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress UnusedMethodCall */ public function unserialize($serialized): void { $this->__construct($serialized); } /** * @param array{string: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } } PK!{ޕType/TypeInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use JsonSerializable; use Serializable; /** * TypeInterface ensures consistency in typed values returned by ramsey/uuid * * @psalm-immutable */ interface TypeInterface extends JsonSerializable, Serializable { public function toString(): string; public function __toString(): string; } PK!,Type/Integer.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function ctype_digit; use function ltrim; use function sprintf; use function strpos; use function substr; /** * A value object representing an integer * * This class exists for type-safety purposes, to ensure that integers * returned from ramsey/uuid methods as strings are truly integers and not some * other kind of string. * * To support large integers beyond PHP_INT_MAX and PHP_INT_MIN on both 64-bit * and 32-bit systems, we store the integers as strings. * * @psalm-immutable */ final class Integer implements NumberInterface { /** * @psalm-var numeric-string */ private $value; /** * @var bool */ private $isNegative = false; /** * @param mixed $value The integer value to store */ public function __construct($value) { $value = (string) $value; $sign = '+'; // If the value contains a sign, remove it for ctype_digit() check. if (strpos($value, '-') === 0 || strpos($value, '+') === 0) { $sign = substr($value, 0, 1); $value = substr($value, 1); } if (!ctype_digit($value)) { throw new InvalidArgumentException( 'Value must be a signed integer or a string containing only ' . 'digits 0-9 and, optionally, a sign (+ or -)' ); } // Trim any leading zeros. $value = ltrim($value, '0'); // Set to zero if the string is empty after trimming zeros. if ($value === '') { $value = '0'; } // Add the negative sign back to the value. if ($sign === '-' && $value !== '0') { $value = $sign . $value; $this->isNegative = true; } /** @psalm-var numeric-string $numericValue */ $numericValue = $value; $this->value = $numericValue; } public function isNegative(): bool { return $this->isNegative; } /** * @psalm-return numeric-string */ public function toString(): string { return $this->value; } public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $serialized The serialized string representation of the object * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress UnusedMethodCall */ public function unserialize($serialized): void { $this->__construct($serialized); } /** * @param array{string: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } } PK!gDooNonstandard/UuidV6.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Uuid; use Throwable; use function hex2bin; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * Ordered-time, or version 6, UUIDs include timestamp, clock sequence, and node * values that are combined into a 128-bit unsigned integer * * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs * * @psalm-immutable */ final class UuidV6 extends Uuid implements UuidInterface { /** * Creates a version 6 (time-based) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_PEABODY) { throw new InvalidArgumentException( 'Fields used to create a UuidV6 must represent a ' . 'version 6 (ordered-time) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } /** * Returns a DateTimeInterface object representing the timestamp associated * with the UUID * * @return DateTimeImmutable A PHP DateTimeImmutable instance representing * the timestamp of a version 6 UUID */ public function getDateTime(): DateTimeInterface { $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Converts this UUID into an instance of a version 1 UUID */ public function toUuidV1(): UuidV1 { $hex = $this->getHex()->toString(); $hex = substr($hex, 7, 5) . substr($hex, 13, 3) . substr($hex, 3, 4) . '1' . substr($hex, 0, 3) . substr($hex, 16); /** @var LazyUuidFromString $uuid */ $uuid = Uuid::fromBytes((string) hex2bin($hex)); return $uuid->toUuidV1(); } /** * Converts a version 1 UUID into an instance of a version 6 UUID */ public static function fromUuidV1(UuidV1 $uuidV1): UuidV6 { $hex = $uuidV1->getHex()->toString(); $hex = substr($hex, 13, 3) . substr($hex, 8, 4) . substr($hex, 0, 5) . '6' . substr($hex, 5, 3) . substr($hex, 16); /** @var LazyUuidFromString $uuid */ $uuid = Uuid::fromBytes((string) hex2bin($hex)); return $uuid->toUuidV6(); } } PK! Nonstandard/UuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; use Throwable; /** * Nonstandard\UuidBuilder builds instances of Nonstandard\Uuid * * @psalm-immutable */ class UuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeConverterInterface */ private $timeConverter; /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Nonstandard\Uuid * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( NumberConverterInterface $numberConverter, TimeConverterInterface $timeConverter ) { $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter; } /** * Builds and returns a Nonstandard\Uuid * * @param CodecInterface $codec The codec to use for building this instance * @param string $bytes The byte string from which to construct a UUID * * @return Uuid The Nonstandard\UuidBuilder returns an instance of * Nonstandard\Uuid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { return new Uuid( $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): Fields { return new Fields($bytes); } } PK!>B #Nonstandard/Uuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Uuid as BaseUuid; /** * Nonstandard\Uuid is a UUID that doesn't conform to RFC 4122 * * @psalm-immutable */ final class Uuid extends BaseUuid { public function __construct( Fields $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PK!. . Nonstandard/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\VariantTrait; use Ramsey\Uuid\Type\Hexadecimal; use function bin2hex; use function dechex; use function hexdec; use function sprintf; use function str_pad; use function strlen; use function substr; use const STR_PAD_LEFT; /** * Nonstandard UUID fields do not conform to the RFC 4122 standard * * Since some systems may create nonstandard UUIDs, this implements the * Rfc4122\FieldsInterface, so that functionality of a nonstandard UUID is not * degraded, in the event these UUIDs are expected to contain RFC 4122 fields. * * Internally, this class represents the fields together as a 16-byte binary * string. * * @psalm-immutable */ final class Fields implements FieldsInterface { use SerializableFieldsTrait; use VariantTrait; /** * @var string */ private $bytes; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes */ public function __construct(string $bytes) { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($bytes) . ' bytes' ); } $this->bytes = $bytes; } public function getBytes(): string { return $this->bytes; } public function getClockSeq(): Hexadecimal { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getTimeHiAndVersion(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); } public function getTimeLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); } public function getTimeMid(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); } public function getTimestamp(): Hexadecimal { return new Hexadecimal(sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() )); } public function getVersion(): ?int { return null; } public function isNil(): bool { return false; } } PK!=DD!Exception/TimeSourceException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the source of time encountered an error */ class TimeSourceException extends PhpRuntimeException implements UuidExceptionInterface { } PK!TTException/DateTimeException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the PHP DateTime extension encountered an exception/error */ class DateTimeException extends PhpRuntimeException implements UuidExceptionInterface { } PK!3tN#Exception/RandomSourceException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the source of random data encountered an error * * This exception is used mostly to indicate that random_bytes() or random_int() * threw an exception. However, it may be used for other sources of random data. */ class RandomSourceException extends PhpRuntimeException implements UuidExceptionInterface { } PK!o cc"Exception/DceSecurityException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate an exception occurred while dealing with DCE Security * (version 2) UUIDs */ class DceSecurityException extends PhpRuntimeException implements UuidExceptionInterface { } PK!veDD&Exception/BuilderNotFoundException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that no suitable builder could be found */ class BuilderNotFoundException extends PhpRuntimeException implements UuidExceptionInterface { } PK!N(Exception/InvalidUuidStringException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; /** * Thrown to indicate that the string received is not a valid UUID * * The InvalidArgumentException that this extends is the ramsey/uuid version * of this exception. It exists in the same namespace as this class. */ class InvalidUuidStringException extends InvalidArgumentException implements UuidExceptionInterface { } PK!R$Exception/UuidExceptionInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use Throwable; interface UuidExceptionInterface extends Throwable { } PK!ouRR#Exception/InvalidBytesException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the bytes being operated on are invalid in some way */ class InvalidBytesException extends PhpRuntimeException implements UuidExceptionInterface { } PK!YYException/NameException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that an error occurred while attempting to hash a * namespace and name */ class NameException extends PhpRuntimeException implements UuidExceptionInterface { } PK!. SSException/NodeException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that attempting to fetch or create a node ID encountered an error */ class NodeException extends PhpRuntimeException implements UuidExceptionInterface { } PK!H%_BB(Exception/UnableToBuildUuidException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate a builder is unable to build a UUID */ class UnableToBuildUuidException extends PhpRuntimeException implements UuidExceptionInterface { } PK!UuidFactoryInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Validator\ValidatorInterface; /** * UuidFactoryInterface defines common functionality all `UuidFactory` instances * must implement */ interface UuidFactoryInterface { /** * Returns the validator to use for the factory * * @psalm-mutation-free */ public function getValidator(): ValidatorInterface; /** * Returns a version 1 (time-based) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID */ public function uuid1($node = null, ?int $clockSeq = null): UuidInterface; /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 2 UUID */ public function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface; /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 3 UUID * * @psalm-pure */ public function uuid3($ns, string $name): UuidInterface; /** * Returns a version 4 (random) UUID * * @return UuidInterface A UuidInterface instance that represents a * version 4 UUID */ public function uuid4(): UuidInterface; /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 5 UUID * * @psalm-pure */ public function uuid5($ns, string $name): UuidInterface; /** * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 6 UUID */ public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface; /** * Creates a UUID from a byte string * * @param string $bytes A binary string * * @return UuidInterface A UuidInterface instance created from a binary * string representation * * @psalm-pure */ public function fromBytes(string $bytes): UuidInterface; /** * Creates a UUID from the string standard representation * * @param string $uuid A hexadecimal string * * @return UuidInterface A UuidInterface instance created from a hexadecimal * string representation * * @psalm-pure */ public function fromString(string $uuid): UuidInterface; /** * Creates a UUID from a 128-bit integer string * * @param string $integer String representation of 128-bit integer * * @return UuidInterface A UuidInterface instance created from the string * representation of a 128-bit integer * * @psalm-pure */ public function fromInteger(string $integer): UuidInterface; /** * Creates a UUID from a DateTimeInterface instance * * @param DateTimeInterface $dateTime The date and time * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID created from a DateTimeInterface instance */ public function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface; } PK!GU~~Math/CalculatorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Math; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\NumberInterface; /** * A calculator performs arithmetic operations on numbers * * @psalm-immutable */ interface CalculatorInterface { /** * Returns the sum of all the provided parameters * * @param NumberInterface $augend The first addend (the integer being added to) * @param NumberInterface ...$addends The additional integers to a add to the augend * * @return NumberInterface The sum of all the parameters */ public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface; /** * Returns the difference of all the provided parameters * * @param NumberInterface $minuend The integer being subtracted from * @param NumberInterface ...$subtrahends The integers to subtract from the minuend * * @return NumberInterface The difference after subtracting all parameters */ public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface; /** * Returns the product of all the provided parameters * * @param NumberInterface $multiplicand The integer to be multiplied * @param NumberInterface ...$multipliers The factors by which to multiply the multiplicand * * @return NumberInterface The product of multiplying all the provided parameters */ public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface; /** * Returns the quotient of the provided parameters divided left-to-right * * @param int $roundingMode The RoundingMode constant to use for this operation * @param int $scale The scale to use for this operation * @param NumberInterface $dividend The integer to be divided * @param NumberInterface ...$divisors The integers to divide $dividend by, in * the order in which the division operations should take place * (left-to-right) * * @return NumberInterface The quotient of dividing the provided parameters left-to-right */ public function divide( int $roundingMode, int $scale, NumberInterface $dividend, NumberInterface ...$divisors ): NumberInterface; /** * Converts a value from an arbitrary base to a base-10 integer value * * @param string $value The value to convert * @param int $base The base to convert from (i.e., 2, 16, 32, etc.) * * @return IntegerObject The base-10 integer value of the converted value */ public function fromBase(string $value, int $base): IntegerObject; /** * Converts a base-10 integer value to an arbitrary base * * @param IntegerObject $value The integer value to convert * @param int $base The base to convert to (i.e., 2, 16, 32, etc.) * * @return string The value represented in the specified base */ public function toBase(IntegerObject $value, int $base): string; /** * Converts an Integer instance to a Hexadecimal instance */ public function toHexadecimal(IntegerObject $value): Hexadecimal; /** * Converts a Hexadecimal instance to an Integer instance */ public function toInteger(Hexadecimal $value): IntegerObject; } PK!1}lMath/BrickMathCalculator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Math; use Brick\Math\BigDecimal; use Brick\Math\BigInteger; use Brick\Math\Exception\MathException; use Brick\Math\RoundingMode as BrickMathRounding; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Type\Decimal; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\NumberInterface; /** * A calculator using the brick/math library for arbitrary-precision arithmetic * * @psalm-immutable */ final class BrickMathCalculator implements CalculatorInterface { private const ROUNDING_MODE_MAP = [ RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY, RoundingMode::UP => BrickMathRounding::UP, RoundingMode::DOWN => BrickMathRounding::DOWN, RoundingMode::CEILING => BrickMathRounding::CEILING, RoundingMode::FLOOR => BrickMathRounding::FLOOR, RoundingMode::HALF_UP => BrickMathRounding::HALF_UP, RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN, RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING, RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR, RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN, ]; public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface { $sum = BigInteger::of($augend->toString()); foreach ($addends as $addend) { $sum = $sum->plus($addend->toString()); } return new IntegerObject((string) $sum); } public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface { $difference = BigInteger::of($minuend->toString()); foreach ($subtrahends as $subtrahend) { $difference = $difference->minus($subtrahend->toString()); } return new IntegerObject((string) $difference); } public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface { $product = BigInteger::of($multiplicand->toString()); foreach ($multipliers as $multiplier) { $product = $product->multipliedBy($multiplier->toString()); } return new IntegerObject((string) $product); } public function divide( int $roundingMode, int $scale, NumberInterface $dividend, NumberInterface ...$divisors ): NumberInterface { $brickRounding = $this->getBrickRoundingMode($roundingMode); $quotient = BigDecimal::of($dividend->toString()); foreach ($divisors as $divisor) { $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding); } if ($scale === 0) { return new IntegerObject((string) $quotient->toBigInteger()); } return new Decimal((string) $quotient); } public function fromBase(string $value, int $base): IntegerObject { try { return new IntegerObject((string) BigInteger::fromBase($value, $base)); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } public function toBase(IntegerObject $value, int $base): string { try { return BigInteger::of($value->toString())->toBase($base); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } public function toHexadecimal(IntegerObject $value): Hexadecimal { return new Hexadecimal($this->toBase($value, 16)); } public function toInteger(Hexadecimal $value): IntegerObject { return $this->fromBase($value->toString(), 16); } /** * Maps ramsey/uuid rounding modes to those used by brick/math */ private function getBrickRoundingMode(int $roundingMode): int { return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0; } } PK!ZjMath/RoundingMode.phpnu[= 0.5; otherwise, behaves * as for DOWN. Note that this is the rounding mode commonly taught at * school. */ public const HALF_UP = 5; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round down. * * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves * as for DOWN. */ public const HALF_DOWN = 6; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round towards positive infinity. * * If the result is positive, behaves as for HALF_UP; if negative, behaves * as for HALF_DOWN. */ public const HALF_CEILING = 7; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round towards negative infinity. * * If the result is positive, behaves as for HALF_DOWN; if negative, behaves * as for HALF_UP. */ public const HALF_FLOOR = 8; /** * Rounds towards the "nearest neighbor" unless both neighbors are * equidistant, in which case rounds towards the even neighbor. * * Behaves as for HALF_UP if the digit to the left of the discarded fraction * is odd; behaves as for HALF_DOWN if it's even. * * Note that this is the rounding mode that statistically minimizes * cumulative error when applied repeatedly over a sequence of calculations. * It is sometimes known as "Banker's rounding", and is chiefly used in the * USA. */ public const HALF_EVEN = 9; } PK!WԤ"66UuidFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Validator\ValidatorInterface; use function bin2hex; use function hex2bin; use function pack; use function str_pad; use function strtolower; use function substr; use function substr_replace; use function unpack; use const STR_PAD_LEFT; class UuidFactory implements UuidFactoryInterface { /** * @var CodecInterface */ private $codec; /** * @var DceSecurityGeneratorInterface */ private $dceSecurityGenerator; /** * @var NameGeneratorInterface */ private $nameGenerator; /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var NumberConverterInterface */ private $numberConverter; /** * @var RandomGeneratorInterface */ private $randomGenerator; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var TimeGeneratorInterface */ private $timeGenerator; /** * @var UuidBuilderInterface */ private $uuidBuilder; /** * @var ValidatorInterface */ private $validator; /** @var bool whether the feature set was provided from outside, or we can operate under "default" assumptions */ private $isDefaultFeatureSet; /** * @param FeatureSet $features A set of available features in the current environment */ public function __construct(?FeatureSet $features = null) { $this->isDefaultFeatureSet = $features === null; $features = $features ?: new FeatureSet(); $this->codec = $features->getCodec(); $this->dceSecurityGenerator = $features->getDceSecurityGenerator(); $this->nameGenerator = $features->getNameGenerator(); $this->nodeProvider = $features->getNodeProvider(); $this->numberConverter = $features->getNumberConverter(); $this->randomGenerator = $features->getRandomGenerator(); $this->timeConverter = $features->getTimeConverter(); $this->timeGenerator = $features->getTimeGenerator(); $this->uuidBuilder = $features->getBuilder(); $this->validator = $features->getValidator(); } /** * Returns the codec used by this factory */ public function getCodec(): CodecInterface { return $this->codec; } /** * Sets the codec to use for this factory * * @param CodecInterface $codec A UUID encoder-decoder */ public function setCodec(CodecInterface $codec): void { $this->isDefaultFeatureSet = false; $this->codec = $codec; } /** * Returns the name generator used by this factory */ public function getNameGenerator(): NameGeneratorInterface { return $this->nameGenerator; } /** * Sets the name generator to use for this factory * * @param NameGeneratorInterface $nameGenerator A generator to generate * binary data, based on a namespace and name */ public function setNameGenerator(NameGeneratorInterface $nameGenerator): void { $this->isDefaultFeatureSet = false; $this->nameGenerator = $nameGenerator; } /** * Returns the node provider used by this factory */ public function getNodeProvider(): NodeProviderInterface { return $this->nodeProvider; } /** * Returns the random generator used by this factory */ public function getRandomGenerator(): RandomGeneratorInterface { return $this->randomGenerator; } /** * Returns the time generator used by this factory */ public function getTimeGenerator(): TimeGeneratorInterface { return $this->timeGenerator; } /** * Sets the time generator to use for this factory * * @param TimeGeneratorInterface $generator A generator to generate binary * data, based on the time */ public function setTimeGenerator(TimeGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->timeGenerator = $generator; } /** * Returns the DCE Security generator used by this factory */ public function getDceSecurityGenerator(): DceSecurityGeneratorInterface { return $this->dceSecurityGenerator; } /** * Sets the DCE Security generator to use for this factory * * @param DceSecurityGeneratorInterface $generator A generator to generate * binary data, based on a local domain and local identifier */ public function setDceSecurityGenerator(DceSecurityGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->dceSecurityGenerator = $generator; } /** * Returns the number converter used by this factory */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * Sets the random generator to use for this factory * * @param RandomGeneratorInterface $generator A generator to generate binary * data, based on some random input */ public function setRandomGenerator(RandomGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->randomGenerator = $generator; } /** * Sets the number converter to use for this factory * * @param NumberConverterInterface $converter A converter to use for working * with large integers (i.e. integers greater than PHP_INT_MAX) */ public function setNumberConverter(NumberConverterInterface $converter): void { $this->isDefaultFeatureSet = false; $this->numberConverter = $converter; } /** * Returns the UUID builder used by this factory */ public function getUuidBuilder(): UuidBuilderInterface { return $this->uuidBuilder; } /** * Sets the UUID builder to use for this factory * * @param UuidBuilderInterface $builder A builder for constructing instances * of UuidInterface */ public function setUuidBuilder(UuidBuilderInterface $builder): void { $this->isDefaultFeatureSet = false; $this->uuidBuilder = $builder; } /** * @psalm-mutation-free */ public function getValidator(): ValidatorInterface { return $this->validator; } /** * Sets the validator to use for this factory * * @param ValidatorInterface $validator A validator to use for validating * whether a string is a valid UUID */ public function setValidator(ValidatorInterface $validator): void { $this->isDefaultFeatureSet = false; $this->validator = $validator; } /** * @psalm-pure */ public function fromBytes(string $bytes): UuidInterface { return $this->codec->decodeBytes($bytes); } /** * @psalm-pure */ public function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); return $this->codec->decode($uuid); } /** * @psalm-pure */ public function fromInteger(string $integer): UuidInterface { $hex = $this->numberConverter->toHex($integer); $hex = str_pad($hex, 32, '0', STR_PAD_LEFT); return $this->fromString($hex); } public function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { $timeProvider = new FixedTimeProvider( new Time($dateTime->format('U'), $dateTime->format('u')) ); $timeGenerator = new DefaultTimeGenerator( $this->nodeProvider, $this->timeConverter, $timeProvider ); $nodeHex = $node ? $node->toString() : null; $bytes = $timeGenerator->generate($nodeHex, $clockSeq); return $this->uuidFromBytesAndVersion($bytes, 1); } /** * @inheritDoc */ public function uuid1($node = null, ?int $clockSeq = null): UuidInterface { $bytes = $this->timeGenerator->generate($node, $clockSeq); return $this->uuidFromBytesAndVersion($bytes, 1); } public function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { $bytes = $this->dceSecurityGenerator->generate( $localDomain, $localIdentifier, $node, $clockSeq ); return $this->uuidFromBytesAndVersion($bytes, 2); } /** * @inheritDoc * @psalm-pure */ public function uuid3($ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, 3, 'md5'); } public function uuid4(): UuidInterface { $bytes = $this->randomGenerator->generate(16); return $this->uuidFromBytesAndVersion($bytes, 4); } /** * @inheritDoc * @psalm-pure */ public function uuid5($ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, 5, 'sha1'); } public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface { $nodeHex = $node ? $node->toString() : null; $bytes = $this->timeGenerator->generate($nodeHex, $clockSeq); // Rearrange the bytes, according to the UUID version 6 specification. $v6 = $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5] . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3]; $v6 = bin2hex($v6); // Drop the first four bits, while adding an empty four bits for the // version field. This allows us to reconstruct the correct time from // the bytes of this UUID. $v6Bytes = hex2bin(substr($v6, 1, 12) . '0' . substr($v6, -3)); $v6Bytes .= substr($bytes, 8); return $this->uuidFromBytesAndVersion($v6Bytes, 6); } /** * Returns a Uuid created from the provided byte string * * Uses the configured builder and codec and the provided byte string to * construct a Uuid object. * * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface An instance of UuidInterface, created from the * provided bytes * * @psalm-pure */ public function uuid(string $bytes): UuidInterface { return $this->uuidBuilder->build($this->codec, $bytes); } /** * Returns a version 3 or 5 namespaced Uuid * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to hash together with the namespace * @param int $version The version of UUID to create (3 or 5) * @param string $hashAlgorithm The hashing algorithm to use when hashing * together the namespace and name * * @return UuidInterface An instance of UuidInterface, created by hashing * together the provided namespace and name * * @psalm-pure */ private function uuidFromNsAndName($ns, string $name, int $version, string $hashAlgorithm): UuidInterface { if (!($ns instanceof UuidInterface)) { $ns = $this->fromString($ns); } $bytes = $this->nameGenerator->generate($ns, $name, $hashAlgorithm); return $this->uuidFromBytesAndVersion(substr($bytes, 0, 16), $version); } /** * Returns an RFC 4122 variant Uuid, created from the provided bytes and version * * @param string $bytes The byte string to convert to a UUID * @param int $version The RFC 4122 version to apply to the UUID * * @return UuidInterface An instance of UuidInterface, created from the * byte string and version * * @psalm-pure */ private function uuidFromBytesAndVersion(string $bytes, int $version): UuidInterface { /** @var array $unpackedTime */ $unpackedTime = unpack('n*', substr($bytes, 6, 2)); $timeHi = (int) $unpackedTime[1]; $timeHiAndVersion = pack('n*', BinaryUtils::applyVersion($timeHi, $version)); /** @var array $unpackedClockSeq */ $unpackedClockSeq = unpack('n*', substr($bytes, 8, 2)); $clockSeqHi = (int) $unpackedClockSeq[1]; $clockSeqHiAndReserved = pack('n*', BinaryUtils::applyVariant($clockSeqHi)); $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); if ($this->isDefaultFeatureSet) { return LazyUuidFromString::fromBytes($bytes); } return $this->uuid($bytes); } } PK!ix$Converter/TimeConverterInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; /** * A time converter converts timestamps into representations that may be used * in UUIDs * * @psalm-immutable */ interface TimeConverterInterface { /** * Uses the provided seconds and micro-seconds to calculate the count of * 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582, for * RFC 4122 variant UUIDs * * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 RFC 4122, § 4.2.2: Generation Details * * @param string $seconds A string representation of the number of seconds * since the Unix epoch for the time to calculate * @param string $microseconds A string representation of the micro-seconds * associated with the time to calculate * * @return Hexadecimal The full UUID timestamp as a Hexadecimal value * * @psalm-pure */ public function calculateTime(string $seconds, string $microseconds): Hexadecimal; /** * Converts a timestamp extracted from a UUID to a Unix timestamp * * @param Hexadecimal $uuidTimestamp A hexadecimal representation of a UUID * timestamp; a UUID timestamp is a count of 100-nanosecond intervals * since UTC 00:00:00.00, 15 October 1582. * * @return Time An instance of {@see Time} * * @psalm-pure */ public function convertTime(Hexadecimal $uuidTimestamp): Time; } PK!ja**&Converter/NumberConverterInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter; /** * A number converter converts UUIDs from hexadecimal characters into * representations of integers and vice versa * * @psalm-immutable */ interface NumberConverterInterface { /** * Converts a hexadecimal number into an string integer representation of * the number * * The integer representation returned is a string representation of the * integer, to accommodate unsigned integers greater than PHP_INT_MAX. * * @param string $hex The hexadecimal string representation to convert * * @return string String representation of an integer * * @psalm-return numeric-string * * @psalm-pure */ public function fromHex(string $hex): string; /** * Converts a string integer representation into a hexadecimal string * representation of the number * * @param string $number A string integer representation to convert; this * must be a numeric string to accommodate unsigned integers greater * than PHP_INT_MAX. * * @return string Hexadecimal string * * @psalm-return non-empty-string * * @psalm-pure */ public function toHex(string $number): string; } PK!YkB'Converter/Time/GenericTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Math\RoundingMode; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use function explode; use function str_pad; use const STR_PAD_LEFT; /** * GenericTimeConverter uses the provided calculator to calculate and convert * time values * * @psalm-immutable */ class GenericTimeConverter implements TimeConverterInterface { /** * The number of 100-nanosecond intervals from the Gregorian calendar epoch * to the Unix epoch. */ private const GREGORIAN_TO_UNIX_INTERVALS = '122192928000000000'; /** * The number of 100-nanosecond intervals in one second. */ private const SECOND_INTERVALS = '10000000'; /** * The number of 100-nanosecond intervals in one microsecond. */ private const MICROSECOND_INTERVALS = '10'; /** * @var CalculatorInterface */ private $calculator; public function __construct(CalculatorInterface $calculator) { $this->calculator = $calculator; } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { $timestamp = new Time($seconds, $microseconds); // Convert the seconds into a count of 100-nanosecond intervals. $sec = $this->calculator->multiply( $timestamp->getSeconds(), new IntegerObject(self::SECOND_INTERVALS) ); // Convert the microseconds into a count of 100-nanosecond intervals. $usec = $this->calculator->multiply( $timestamp->getMicroseconds(), new IntegerObject(self::MICROSECOND_INTERVALS) ); // Combine the seconds and microseconds intervals and add the count of // 100-nanosecond intervals from the Gregorian calendar epoch to the // Unix epoch. This gives us the correct count of 100-nanosecond // intervals since the Gregorian calendar epoch for the given seconds // and microseconds. /** @var IntegerObject $uuidTime */ $uuidTime = $this->calculator->add( $sec, $usec, new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) ); $uuidTimeHex = str_pad( $this->calculator->toHexadecimal($uuidTime)->toString(), 16, '0', STR_PAD_LEFT ); return new Hexadecimal($uuidTimeHex); } public function convertTime(Hexadecimal $uuidTimestamp): Time { // From the total, subtract the number of 100-nanosecond intervals from // the Gregorian calendar epoch to the Unix epoch. This gives us the // number of 100-nanosecond intervals from the Unix epoch, which also // includes the microtime. $epochNanoseconds = $this->calculator->subtract( $this->calculator->toInteger($uuidTimestamp), new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) ); // Convert the 100-nanosecond intervals into seconds and microseconds. $unixTimestamp = $this->calculator->divide( RoundingMode::HALF_UP, 6, $epochNanoseconds, new IntegerObject(self::SECOND_INTERVALS) ); $split = explode('.', (string) $unixTimestamp, 2); return new Time($split[0], $split[1] ?? 0); } } PK!s(gg(Converter/Time/DegradedTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; /** * @deprecated DegradedTimeConverter is no longer necessary for converting * time on 32-bit systems. Transition to {@see GenericTimeConverter}. * * @psalm-immutable */ class DegradedTimeConverter extends BigNumberTimeConverter { } PK!E'11#Converter/Time/PhpTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use function count; use function dechex; use function explode; use function is_float; use function is_int; use function str_pad; use function strlen; use function substr; use const STR_PAD_LEFT; use const STR_PAD_RIGHT; /** * PhpTimeConverter uses built-in PHP functions and standard math operations * available to the PHP programming language to provide facilities for * converting parts of time into representations that may be used in UUIDs * * @psalm-immutable */ class PhpTimeConverter implements TimeConverterInterface { /** * The number of 100-nanosecond intervals from the Gregorian calendar epoch * to the Unix epoch. */ private const GREGORIAN_TO_UNIX_INTERVALS = 0x01b21dd213814000; /** * The number of 100-nanosecond intervals in one second. */ private const SECOND_INTERVALS = 10000000; /** * The number of 100-nanosecond intervals in one microsecond. */ private const MICROSECOND_INTERVALS = 10; /** * @var CalculatorInterface */ private $calculator; /** * @var TimeConverterInterface */ private $fallbackConverter; /** * @var int */ private $phpPrecision; public function __construct( ?CalculatorInterface $calculator = null, ?TimeConverterInterface $fallbackConverter = null ) { if ($calculator === null) { $calculator = new BrickMathCalculator(); } if ($fallbackConverter === null) { $fallbackConverter = new GenericTimeConverter($calculator); } $this->calculator = $calculator; $this->fallbackConverter = $fallbackConverter; $this->phpPrecision = (int) ini_get('precision'); } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { $seconds = new IntegerObject($seconds); $microseconds = new IntegerObject($microseconds); // Calculate the count of 100-nanosecond intervals since the Gregorian // calendar epoch for the given seconds and microseconds. $uuidTime = ((int) $seconds->toString() * self::SECOND_INTERVALS) + ((int) $microseconds->toString() * self::MICROSECOND_INTERVALS) + self::GREGORIAN_TO_UNIX_INTERVALS; // Check to see whether we've overflowed the max/min integer size. // If so, we will default to a different time converter. /** @psalm-suppress RedundantCondition */ if (!is_int($uuidTime)) { return $this->fallbackConverter->calculateTime( $seconds->toString(), $microseconds->toString() ); } return new Hexadecimal(str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT)); } public function convertTime(Hexadecimal $uuidTimestamp): Time { $timestamp = $this->calculator->toInteger($uuidTimestamp); // Convert the 100-nanosecond intervals into seconds and microseconds. $splitTime = $this->splitTime( ((int) $timestamp->toString() - self::GREGORIAN_TO_UNIX_INTERVALS) / self::SECOND_INTERVALS ); if (count($splitTime) === 0) { return $this->fallbackConverter->convertTime($uuidTimestamp); } return new Time($splitTime['sec'], $splitTime['usec']); } /** * @param int|float $time The time to split into seconds and microseconds * * @return string[] */ private function splitTime($time): array { $split = explode('.', (string) $time, 2); // If the $time value is a float but $split only has 1 element, then the // float math was rounded up to the next second, so we want to return // an empty array to allow use of the fallback converter. if (is_float($time) && count($split) === 1) { return []; } if (count($split) === 1) { return [ 'sec' => $split[0], 'usec' => '0', ]; } // If the microseconds are less than six characters AND the length of // the number is greater than or equal to the PHP precision, then it's // possible that we lost some precision for the microseconds. Return an // empty array, so that we can choose to use the fallback converter. if (strlen($split[1]) < 6 && strlen((string) $time) >= $this->phpPrecision) { return []; } $microseconds = $split[1]; // Ensure the microseconds are no longer than 6 digits. If they are, // truncate the number to the first 6 digits and round up, if needed. if (strlen($microseconds) > 6) { $roundingDigit = (int) substr($microseconds, 6, 1); $microseconds = (int) substr($microseconds, 0, 6); if ($roundingDigit >= 5) { $microseconds++; } } return [ 'sec' => $split[0], 'usec' => str_pad((string) $microseconds, 6, '0', STR_PAD_RIGHT), ]; } } PK!AHH)Converter/Time/BigNumberTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; /** * Previously used to integrate moontoast/math as a bignum arithmetic library, * BigNumberTimeConverter is deprecated in favor of GenericTimeConverter * * @deprecated Transition to {@see GenericTimeConverter}. * * @psalm-immutable */ class BigNumberTimeConverter implements TimeConverterInterface { /** * @var TimeConverterInterface */ private $converter; public function __construct() { $this->converter = new GenericTimeConverter(new BrickMathCalculator()); } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { return $this->converter->calculateTime($seconds, $microseconds); } public function convertTime(Hexadecimal $uuidTimestamp): Time { return $this->converter->convertTime($uuidTimestamp); } } PK!bk6""'Converter/Number/BigNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; /** * Previously used to integrate moontoast/math as a bignum arithmetic library, * BigNumberConverter is deprecated in favor of GenericNumberConverter * * @deprecated Transition to {@see GenericNumberConverter}. * * @psalm-immutable */ class BigNumberConverter implements NumberConverterInterface { /** * @var NumberConverterInterface */ private $converter; public function __construct() { $this->converter = new GenericNumberConverter(new BrickMathCalculator()); } /** * @inheritDoc * @psalm-pure */ public function fromHex(string $hex): string { return $this->converter->fromHex($hex); } /** * @inheritDoc * @psalm-pure */ public function toHex(string $number): string { return $this->converter->toHex($number); } } PK!vWnn,Converter/Number/DegradedNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; /** * @deprecated DegradedNumberConverter is no longer necessary for converting * numbers on 32-bit systems. Transition to {@see GenericNumberConverter}. * * @psalm-immutable */ class DegradedNumberConverter extends BigNumberConverter { } PK!NII+Converter/Number/GenericNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * GenericNumberConverter uses the provided calculator to convert decimal * numbers to and from hexadecimal values * * @psalm-immutable */ class GenericNumberConverter implements NumberConverterInterface { /** * @var CalculatorInterface */ private $calculator; public function __construct(CalculatorInterface $calculator) { $this->calculator = $calculator; } /** * @inheritDoc * @psalm-pure * @psalm-return numeric-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function fromHex(string $hex): string { return $this->calculator->fromBase($hex, 16)->toString(); } /** * @inheritDoc * @psalm-pure * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function toHex(string $number): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $this->calculator->toBase(new IntegerObject($number), 16); } } PK!Թ-//Codec/GuidStringCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Guid\Guid; use Ramsey\Uuid\UuidInterface; use function bin2hex; use function substr; /** * GuidStringCodec encodes and decodes globally unique identifiers (GUID) * * @see Guid * * @psalm-immutable */ class GuidStringCodec extends StringCodec { public function decode(string $encodedUuid): UuidInterface { $bytes = $this->getBytes($encodedUuid); return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } public function decodeBytes(string $bytes): UuidInterface { // Specifically call parent::decode to preserve correct byte order return parent::decode(bin2hex($bytes)); } /** * Swaps bytes according to the GUID rules */ private function swapBytes(string $bytes): string { return $bytes[3] . $bytes[2] . $bytes[1] . $bytes[0] . $bytes[5] . $bytes[4] . $bytes[7] . $bytes[6] . substr($bytes, 8); } } PK!SNNCodec/StringCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function hex2bin; use function implode; use function str_replace; use function strlen; use function substr; /** * StringCodec encodes and decodes RFC 4122 UUIDs * * @link http://tools.ietf.org/html/rfc4122 * * @psalm-immutable */ class StringCodec implements CodecInterface { /** * @var UuidBuilderInterface */ private $builder; /** * Constructs a StringCodec * * @param UuidBuilderInterface $builder The builder to use when encoding UUIDs */ public function __construct(UuidBuilderInterface $builder) { $this->builder = $builder; } public function encode(UuidInterface $uuid): string { /** @var FieldsInterface $fields */ $fields = $uuid->getFields(); return $fields->getTimeLow()->toString() . '-' . $fields->getTimeMid()->toString() . '-' . $fields->getTimeHiAndVersion()->toString() . '-' . $fields->getClockSeqHiAndReserved()->toString() . $fields->getClockSeqLow()->toString() . '-' . $fields->getNode()->toString(); } /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $uuid->getFields()->getBytes(); } /** * @throws InvalidUuidStringException * * @inheritDoc */ public function decode(string $encodedUuid): UuidInterface { return $this->builder->build($this, $this->getBytes($encodedUuid)); } public function decodeBytes(string $bytes): UuidInterface { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( '$bytes string should contain 16 characters.' ); } return $this->builder->build($this, $bytes); } /** * Returns the UUID builder */ protected function getBuilder(): UuidBuilderInterface { return $this->builder; } /** * Returns a byte string of the UUID */ protected function getBytes(string $encodedUuid): string { $parsedUuid = str_replace( ['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}', '-'], '', $encodedUuid ); $components = [ substr($parsedUuid, 0, 8), substr($parsedUuid, 8, 4), substr($parsedUuid, 12, 4), substr($parsedUuid, 16, 4), substr($parsedUuid, 20), ]; if (!Uuid::isValid(implode('-', $components))) { throw new InvalidUuidStringException( 'Invalid UUID string: ' . $encodedUuid ); } return (string) hex2bin($parsedUuid); } } PK!LCodec/OrderedTimeCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function strlen; use function substr; /** * OrderedTimeCodec encodes and decodes a UUID, optimizing the byte order for * more efficient storage * * For binary representations of version 1 UUID, this codec may be used to * reorganize the time fields, making the UUID closer to sequential when storing * the bytes. According to Percona, this optimization can improve database * INSERTs and SELECTs using the UUID column as a key. * * The string representation of the UUID will remain unchanged. Only the binary * representation is reordered. * * **PLEASE NOTE:** Binary representations of UUIDs encoded with this codec must * be decoded with this codec. Decoding using another codec can result in * malformed UUIDs. * * @link https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ Storing UUID Values in MySQL * * @psalm-immutable */ class OrderedTimeCodec extends StringCodec { /** * Returns a binary string representation of a UUID, with the timestamp * fields rearranged for optimized storage * * @inheritDoc * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { if ( !($uuid->getFields() instanceof Rfc4122FieldsInterface) || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME ) { throw new InvalidArgumentException( 'Expected RFC 4122 version 1 (time-based) UUID' ); } $bytes = $uuid->getFields()->getBytes(); /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5] . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3] . substr($bytes, 8); } /** * Returns a UuidInterface derived from an ordered-time binary string * representation * * @throws InvalidArgumentException if $bytes is an invalid length * * @inheritDoc */ public function decodeBytes(string $bytes): UuidInterface { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( '$bytes string should contain 16 characters.' ); } // Rearrange the bytes to their original order. $rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7] . $bytes[2] . $bytes[3] . $bytes[0] . $bytes[1] . substr($bytes, 8); $uuid = parent::decodeBytes($rearrangedBytes); if ( !($uuid->getFields() instanceof Rfc4122FieldsInterface) || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME ) { throw new UnsupportedOperationException( 'Attempting to decode a non-time-based UUID using ' . 'OrderedTimeCodec' ); } return $uuid; } } PK!hCodec/CodecInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\UuidInterface; /** * A codec encodes and decodes a UUID according to defined rules * * @psalm-immutable */ interface CodecInterface { /** * Returns a hexadecimal string representation of a UuidInterface * * @param UuidInterface $uuid The UUID for which to create a hexadecimal * string representation * * @return string Hexadecimal string representation of a UUID * * @psalm-return non-empty-string */ public function encode(UuidInterface $uuid): string; /** * Returns a binary string representation of a UuidInterface * * @param UuidInterface $uuid The UUID for which to create a binary string * representation * * @return string Binary string representation of a UUID * * @psalm-return non-empty-string */ public function encodeBinary(UuidInterface $uuid): string; /** * Returns a UuidInterface derived from a hexadecimal string representation * * @param string $encodedUuid The hexadecimal string representation to * convert into a UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a hexadecimal * string representation */ public function decode(string $encodedUuid): UuidInterface; /** * Returns a UuidInterface derived from a binary string representation * * @param string $bytes The binary string representation to convert into a * UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a binary string * representation */ public function decodeBytes(string $bytes): UuidInterface; } PK!{N N !Codec/TimestampFirstCombCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\UuidInterface; use function bin2hex; use function sprintf; use function substr; use function substr_replace; /** * TimestampFirstCombCodec encodes and decodes COMBs, with the timestamp as the * first 48 bits * * In contrast with the TimestampLastCombCodec, the TimestampFirstCombCodec * adds the timestamp to the first 48 bits of the COMB. To generate a * timestamp-first COMB, set the TimestampFirstCombCodec as the codec, along * with the CombGenerator as the random generator. * * ``` php * $factory = new UuidFactory(); * * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $timestampFirstComb = $factory->uuid4(); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys * * @psalm-immutable */ class TimestampFirstCombCodec extends StringCodec { /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encode(UuidInterface $uuid): string { $bytes = $this->swapBytes($uuid->getFields()->getBytes()); return sprintf( '%08s-%04s-%04s-%04s-%012s', bin2hex(substr($bytes, 0, 4)), bin2hex(substr($bytes, 4, 2)), bin2hex(substr($bytes, 6, 2)), bin2hex(substr($bytes, 8, 2)), bin2hex(substr($bytes, 10)) ); } /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $this->swapBytes($uuid->getFields()->getBytes()); } /** * @throws InvalidUuidStringException * * @inheritDoc */ public function decode(string $encodedUuid): UuidInterface { $bytes = $this->getBytes($encodedUuid); return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } public function decodeBytes(string $bytes): UuidInterface { return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } /** * Swaps bytes according to the timestamp-first COMB rules */ private function swapBytes(string $bytes): string { $first48Bits = substr($bytes, 0, 6); $last48Bits = substr($bytes, -6); $bytes = substr_replace($bytes, $last48Bits, 0, 6); $bytes = substr_replace($bytes, $first48Bits, -6); return $bytes; } } PK!8Gb=NN Codec/TimestampLastCombCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; /** * TimestampLastCombCodec encodes and decodes COMBs, with the timestamp as the * last 48 bits * * The CombGenerator when used with the StringCodec (and, by proxy, the * TimestampLastCombCodec) adds the timestamp to the last 48 bits of the COMB. * The TimestampLastCombCodec is provided for the sake of consistency. In * practice, it is identical to the standard StringCodec but, it may be used * with the CombGenerator for additional context when reading code. * * Consider the following code. By default, the codec used by UuidFactory is the * StringCodec, but here, we explicitly set the TimestampLastCombCodec. It is * redundant, but it is clear that we intend this COMB to be generated with the * timestamp appearing at the end. * * ``` php * $factory = new UuidFactory(); * * $factory->setCodec(new TimestampLastCombCodec($factory->getUuidBuilder())); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $timestampLastComb = $factory->uuid4(); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys * * @psalm-immutable */ class TimestampLastCombCodec extends StringCodec { } PK!fT66"Provider/TimeProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Type\Time; /** * A time provider retrieves the current time */ interface TimeProviderInterface { /** * Returns a time object */ public function getTime(): Time; } PK!d)Provider/DceSecurityProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * A DCE provider provides access to local domain identifiers for version 2, * DCE Security, UUIDs * * @see UuidV2 */ interface DceSecurityProviderInterface { /** * Returns a user identifier for the system * * @link https://en.wikipedia.org/wiki/User_identifier User identifier */ public function getUid(): IntegerObject; /** * Returns a group identifier for the system * * @link https://en.wikipedia.org/wiki/Group_identifier Group identifier */ public function getGid(): IntegerObject; } PK!v$Provider/Time/SystemTimeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Time; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Time; use function gettimeofday; /** * SystemTimeProvider retrieves the current time using built-in PHP functions */ class SystemTimeProvider implements TimeProviderInterface { public function getTime(): Time { $time = gettimeofday(); return new Time($time['sec'], $time['usec']); } } PK!f>_#Provider/Time/FixedTimeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Time; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; /** * FixedTimeProvider uses an known time to provide the time * * This provider allows the use of a previously-generated, or known, time * when generating time-based UUIDs. */ class FixedTimeProvider implements TimeProviderInterface { /** * @var Time */ private $fixedTime; public function __construct(Time $time) { $this->fixedTime = $time; } /** * Sets the `usec` component of the time * * @param int|string|IntegerObject $value The `usec` value to set */ public function setUsec($value): void { $this->fixedTime = new Time($this->fixedTime->getSeconds(), $value); } /** * Sets the `sec` component of the time * * @param int|string|IntegerObject $value The `sec` value to set */ public function setSec($value): void { $this->fixedTime = new Time($value, $this->fixedTime->getMicroseconds()); } public function getTime(): Time { return $this->fixedTime; } } PK! 'ztt$Provider/Node/StaticNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use function dechex; use function hexdec; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * StaticNodeProvider provides a static node value with the multicast bit set * * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host */ class StaticNodeProvider implements NodeProviderInterface { /** * @var Hexadecimal */ private $node; /** * @param Hexadecimal $node The static node value to use */ public function __construct(Hexadecimal $node) { if (strlen($node->toString()) > 12) { throw new InvalidArgumentException( 'Static node value cannot be greater than 12 hexadecimal characters' ); } $this->node = $this->setMulticastBit($node); } public function getNode(): Hexadecimal { return $this->node; } /** * Set the multicast bit for the static node value */ private function setMulticastBit(Hexadecimal $node): Hexadecimal { $nodeHex = str_pad($node->toString(), 12, '0', STR_PAD_LEFT); $firstOctet = substr($nodeHex, 0, 2); $firstOctet = str_pad( dechex(hexdec($firstOctet) | 0x01), 2, '0', STR_PAD_LEFT ); return new Hexadecimal($firstOctet . substr($nodeHex, 2)); } } PK!wk6ZZ(Provider/Node/NodeProviderCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Collection\AbstractCollection; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * A collection of NodeProviderInterface objects * * @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from * a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced * at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use * more generic types like `iterable` instead. * * @extends AbstractCollection */ class NodeProviderCollection extends AbstractCollection { public function getType(): string { return NodeProviderInterface::class; } /** * Re-constructs the object from its serialized form * * @param string $serialized The serialized PHP string to unserialize into * a UuidInterface instance * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress RedundantConditionGivenDocblockType */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, [ 'allowed_classes' => [ Hexadecimal::class, RandomNodeProvider::class, StaticNodeProvider::class, SystemNodeProvider::class, ], ]); $this->data = array_filter( $data, function ($unserialized): bool { return $unserialized instanceof NodeProviderInterface; } ); } } PK!o[[$Provider/Node/SystemNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use function array_filter; use function array_map; use function array_walk; use function count; use function ob_get_clean; use function ob_start; use function preg_match; use function preg_match_all; use function reset; use function str_replace; use function strpos; use function strtolower; use function strtoupper; use function substr; use const GLOB_NOSORT; use const PREG_PATTERN_ORDER; /** * SystemNodeProvider retrieves the system node ID, if possible * * The system node ID, or host ID, is often the same as the MAC address for a * network interface on the host. */ class SystemNodeProvider implements NodeProviderInterface { /** * Pattern to match nodes in ifconfig and ipconfig output. */ private const IFCONFIG_PATTERN = '/[^:]([0-9a-f]{2}([:-])[0-9a-f]{2}(\2[0-9a-f]{2}){4})[^:]/i'; /** * Pattern to match nodes in sysfs stream output. */ private const SYSFS_PATTERN = '/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i'; public function getNode(): Hexadecimal { $node = $this->getNodeFromSystem(); if ($node === '') { throw new NodeException( 'Unable to fetch a node for this system' ); } return new Hexadecimal($node); } /** * Returns the system node, if it can find it */ protected function getNodeFromSystem(): string { static $node = null; if ($node !== null) { return (string) $node; } // First, try a Linux-specific approach. $node = $this->getSysfs(); if ($node === '') { // Search ifconfig output for MAC addresses & return the first one. $node = $this->getIfconfig(); } $node = str_replace([':', '-'], '', $node); return $node; } /** * Returns the network interface configuration for the system * * @codeCoverageIgnore */ protected function getIfconfig(): string { $disabledFunctions = strtolower((string) ini_get('disable_functions')); if (strpos($disabledFunctions, 'passthru') !== false) { return ''; } ob_start(); switch (strtoupper(substr(constant('PHP_OS'), 0, 3))) { case 'WIN': passthru('ipconfig /all 2>&1'); break; case 'DAR': passthru('ifconfig 2>&1'); break; case 'FRE': passthru('netstat -i -f link 2>&1'); break; case 'LIN': default: passthru('netstat -ie 2>&1'); break; } $ifconfig = (string) ob_get_clean(); $node = ''; if (preg_match_all(self::IFCONFIG_PATTERN, $ifconfig, $matches, PREG_PATTERN_ORDER)) { $node = $matches[1][0] ?? ''; } return $node; } /** * Returns MAC address from the first system interface via the sysfs interface */ protected function getSysfs(): string { $mac = ''; if (strtoupper(constant('PHP_OS')) === 'LINUX') { $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT); if ($addressPaths === false || count($addressPaths) === 0) { return ''; } $macs = []; array_walk($addressPaths, function (string $addressPath) use (&$macs): void { if (is_readable($addressPath)) { $macs[] = file_get_contents($addressPath); } }); $macs = array_map('trim', $macs); // Remove invalid entries. $macs = array_filter($macs, function (string $address) { return $address !== '00:00:00:00:00:00' && preg_match(self::SYSFS_PATTERN, $address); }); $mac = reset($macs); } return (string) $mac; } } PK!K4&Provider/Node/FallbackNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * FallbackNodeProvider retrieves the system node ID by stepping through a list * of providers until a node ID can be obtained */ class FallbackNodeProvider implements NodeProviderInterface { /** * @var iterable */ private $nodeProviders; /** * @param iterable $providers Array of node providers */ public function __construct(iterable $providers) { $this->nodeProviders = $providers; } public function getNode(): Hexadecimal { $lastProviderException = null; foreach ($this->nodeProviders as $provider) { try { return $provider->getNode(); } catch (NodeException $exception) { $lastProviderException = $exception; continue; } } throw new NodeException( 'Unable to find a suitable node provider', 0, $lastProviderException ); } } PK! $Provider/Node/RandomNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\RandomSourceException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Throwable; use function bin2hex; use function dechex; use function hex2bin; use function hexdec; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * RandomNodeProvider generates a random node ID * * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host */ class RandomNodeProvider implements NodeProviderInterface { public function getNode(): Hexadecimal { try { $nodeBytes = random_bytes(6); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } // Split the node bytes for math on 32-bit systems. $nodeMsb = substr($nodeBytes, 0, 3); $nodeLsb = substr($nodeBytes, 3); // Set the multicast bit; see RFC 4122, section 4.5. $nodeMsb = hex2bin( str_pad( dechex(hexdec(bin2hex($nodeMsb)) | 0x010000), 6, '0', STR_PAD_LEFT ) ); // Recombine the node bytes. $node = $nodeMsb . $nodeLsb; return new Hexadecimal(str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT)); } } PK!`"Provider/NodeProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Type\Hexadecimal; /** * A node provider retrieves or generates a node ID */ interface NodeProviderInterface { /** * Returns a node ID * * @return Hexadecimal The node ID as a hexadecimal string */ public function getNode(): Hexadecimal; } PK!*Provider/Dce/SystemDceSecurityProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Dce; use Ramsey\Uuid\Exception\DceSecurityException; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use function escapeshellarg; use function preg_split; use function str_getcsv; use function strpos; use function strrpos; use function strtolower; use function strtoupper; use function substr; use function trim; use const PREG_SPLIT_NO_EMPTY; /** * SystemDceSecurityProvider retrieves the user or group identifiers from the system */ class SystemDceSecurityProvider implements DceSecurityProviderInterface { /** * @throws DceSecurityException if unable to get a user identifier * * @inheritDoc */ public function getUid(): IntegerObject { static $uid = null; if ($uid instanceof IntegerObject) { return $uid; } if ($uid === null) { $uid = $this->getSystemUid(); } if ($uid === '') { throw new DceSecurityException( 'Unable to get a user identifier using the system DCE ' . 'Security provider; please provide a custom identifier or ' . 'use a different provider' ); } $uid = new IntegerObject($uid); return $uid; } /** * @throws DceSecurityException if unable to get a group identifier * * @inheritDoc */ public function getGid(): IntegerObject { static $gid = null; if ($gid instanceof IntegerObject) { return $gid; } if ($gid === null) { $gid = $this->getSystemGid(); } if ($gid === '') { throw new DceSecurityException( 'Unable to get a group identifier using the system DCE ' . 'Security provider; please provide a custom identifier or ' . 'use a different provider' ); } $gid = new IntegerObject($gid); return $gid; } /** * Returns the UID from the system */ private function getSystemUid(): string { if (!$this->hasShellExec()) { return ''; } switch ($this->getOs()) { case 'WIN': return $this->getWindowsUid(); case 'DAR': case 'FRE': case 'LIN': default: return trim((string) shell_exec('id -u')); } } /** * Returns the GID from the system */ private function getSystemGid(): string { if (!$this->hasShellExec()) { return ''; } switch ($this->getOs()) { case 'WIN': return $this->getWindowsGid(); case 'DAR': case 'FRE': case 'LIN': default: return trim((string) shell_exec('id -g')); } } /** * Returns true if shell_exec() is available for use */ private function hasShellExec(): bool { $disabledFunctions = strtolower((string) ini_get('disable_functions')); return strpos($disabledFunctions, 'shell_exec') === false; } /** * Returns the PHP_OS string */ private function getOs(): string { return strtoupper(substr(constant('PHP_OS'), 0, 3)); } /** * Returns the user identifier for a user on a Windows system * * Windows does not have the same concept as an effective POSIX UID for the * running script. Instead, each user is uniquely identified by an SID * (security identifier). The SID includes three 32-bit unsigned integers * that make up a unique domain identifier, followed by an RID (relative * identifier) that we will use as the UID. The primary caveat is that this * UID may not be unique to the system, since it is, instead, unique to the * domain. * * @link https://www.lifewire.com/what-is-an-sid-number-2626005 What Is an SID Number? * @link https://bit.ly/30vE7NM Well-known SID Structures * @link https://bit.ly/2FWcYKJ Well-known security identifiers in Windows operating systems * @link https://www.windows-commandline.com/get-sid-of-user/ Get SID of user */ private function getWindowsUid(): string { $response = shell_exec('whoami /user /fo csv /nh'); if ($response === null) { return ''; } $sid = str_getcsv(trim((string) $response))[1] ?? ''; if (($lastHyphen = strrpos($sid, '-')) === false) { return ''; } return trim(substr($sid, $lastHyphen + 1)); } /** * Returns a group identifier for a user on a Windows system * * Since Windows does not have the same concept as an effective POSIX GID * for the running script, we will get the local group memberships for the * user running the script. Then, we will get the SID (security identifier) * for the first group that appears in that list. Finally, we will return * the RID (relative identifier) for the group and use that as the GID. * * @link https://www.windows-commandline.com/list-of-user-groups-command-line/ List of user groups command line */ private function getWindowsGid(): string { $response = shell_exec('net user %username% | findstr /b /i "Local Group Memberships"'); if ($response === null) { return ''; } /** @var string[] $userGroups */ $userGroups = preg_split('/\s{2,}/', (string) $response, -1, PREG_SPLIT_NO_EMPTY); $firstGroup = trim($userGroups[1] ?? '', "* \t\n\r\0\x0B"); if ($firstGroup === '') { return ''; } $response = shell_exec('wmic group get name,sid | findstr /b /i ' . escapeshellarg($firstGroup)); if ($response === null) { return ''; } /** @var string[] $userGroup */ $userGroup = preg_split('/\s{2,}/', (string) $response, -1, PREG_SPLIT_NO_EMPTY); $sid = $userGroup[1] ?? ''; if (($lastHyphen = strrpos($sid, '-')) === false) { return ''; } return trim(substr($sid, $lastHyphen + 1)); } } PK! functions.phpnu[ * @license http://opensource.org/licenses/MIT MIT * phpcs:disable Squiz.Functions.GlobalFunction */ declare(strict_types=1); namespace Ramsey\Uuid; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * Returns a version 1 (time-based) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return non-empty-string Version 1 UUID as a string */ function v1($node = null, ?int $clockSeq = null): string { return Uuid::uuid1($node, $clockSeq)->toString(); } /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return non-empty-string Version 2 UUID as a string */ function v2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string { return Uuid::uuid2($localDomain, $localIdentifier, $node, $clockSeq)->toString(); } /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * * @return non-empty-string Version 3 UUID as a string * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ function v3($ns, string $name): string { return Uuid::uuid3($ns, $name)->toString(); } /** * Returns a version 4 (random) UUID * * @return non-empty-string Version 4 UUID as a string */ function v4(): string { return Uuid::uuid4()->toString(); } /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * * @return non-empty-string Version 5 UUID as a string * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ function v5($ns, string $name): string { return Uuid::uuid5($ns, $name)->toString(); } /** * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return non-empty-string Version 6 UUID as a string */ function v6(?Hexadecimal $node = null, ?int $clockSeq = null): string { return Uuid::uuid6($node, $clockSeq)->toString(); } PK!F7,"Fields/SerializableFieldsTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Fields; use ValueError; use function base64_decode; use function sprintf; use function strlen; /** * Provides common serialization functionality to fields * * @psalm-immutable */ trait SerializableFieldsTrait { /** * @param string $bytes The bytes that comprise the fields */ abstract public function __construct(string $bytes); /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns a string representation of object */ public function serialize(): string { return $this->getBytes(); } /** * @return array{bytes: string} */ public function __serialize(): array { return ['bytes' => $this->getBytes()]; } /** * Constructs the object from a serialized string representation * * @param string $serialized The serialized string representation of the object * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress UnusedMethodCall */ public function unserialize($serialized): void { if (strlen($serialized) === 16) { $this->__construct($serialized); } else { $this->__construct(base64_decode($serialized)); } } /** * @param array{bytes: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['bytes'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['bytes']); } } PK!t4Fields/FieldsInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Fields; use Serializable; /** * UUIDs are comprised of unsigned integers, the bytes of which are separated * into fields and arranged in a particular layout defined by the specification * for the variant * * @psalm-immutable */ interface FieldsInterface extends Serializable { /** * Returns the bytes that comprise the fields */ public function getBytes(): string; } PK!KWWUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use ValueError; use function assert; use function bin2hex; use function preg_match; use function sprintf; use function str_replace; use function strcmp; use function strlen; use function strtolower; use function substr; /** * Uuid provides constants and static methods for working with and generating UUIDs * * @psalm-immutable */ class Uuid implements UuidInterface { use DeprecatedUuidMethodsTrait; /** * When this namespace is specified, the name string is a fully-qualified * domain name * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is a URL * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an ISO OID * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an X.500 DN in DER * or a text output format * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; /** * The nil UUID is a special form of UUID that is specified to have all 128 * bits set to zero * * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID */ public const NIL = '00000000-0000-0000-0000-000000000000'; /** * Variant: reserved, NCS backward compatibility * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_NCS = 0; /** * Variant: the UUID layout specified in RFC 4122 * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RFC_4122 = 2; /** * Variant: reserved, Microsoft Corporation backward compatibility * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_MICROSOFT = 6; /** * Variant: reserved for future definition * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_FUTURE = 7; /** * @deprecated Use {@see ValidatorInterface::getPattern()} instead. */ public const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; /** * Version 1 (time-based) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_TIME = 1; /** * Version 2 (DCE Security) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_DCE_SECURITY = 2; /** * @deprecated Use {@see Uuid::UUID_TYPE_DCE_SECURITY} instead. */ public const UUID_TYPE_IDENTIFIER = 2; /** * Version 3 (name-based and hashed with MD5) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_HASH_MD5 = 3; /** * Version 4 (random) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_RANDOM = 4; /** * Version 5 (name-based and hashed with SHA1) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_HASH_SHA1 = 5; /** * Version 6 (ordered-time) UUID * * This is named `UUID_TYPE_PEABODY`, since the specification is still in * draft form, and the primary author/editor's name is Brad Peabody. * * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs */ public const UUID_TYPE_PEABODY = 6; /** * DCE Security principal domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_PERSON = 0; /** * DCE Security group domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_GROUP = 1; /** * DCE Security organization domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_ORG = 2; /** * DCE Security domain string names * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_NAMES = [ self::DCE_DOMAIN_PERSON => 'person', self::DCE_DOMAIN_GROUP => 'group', self::DCE_DOMAIN_ORG => 'org', ]; /** * @var UuidFactoryInterface|null */ private static $factory = null; /** * @var bool flag to detect if the UUID factory was replaced internally, which disables all optimizations * for the default/happy path internal scenarios */ private static $factoryReplaced = false; /** * @var CodecInterface */ protected $codec; /** * The fields that make up this UUID * * @var Rfc4122FieldsInterface */ protected $fields; /** * @var NumberConverterInterface */ protected $numberConverter; /** * @var TimeConverterInterface */ protected $timeConverter; /** * Creates a universally unique identifier (UUID) from an array of fields * * Unless you're making advanced use of this library to generate identifiers * that deviate from RFC 4122, you probably do not want to instantiate a * UUID directly. Use the static methods, instead: * * ``` * use Ramsey\Uuid\Uuid; * * $timeBasedUuid = Uuid::uuid1(); * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); * $randomUuid = Uuid::uuid4(); * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); * ``` * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { $this->fields = $fields; $this->codec = $codec; $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter; } /** * @psalm-return non-empty-string */ public function __toString(): string { return $this->toString(); } /** * Converts the UUID to a string for JSON serialization */ public function jsonSerialize(): string { return $this->toString(); } /** * Converts the UUID to a string for PHP serialization */ public function serialize(): string { return $this->getFields()->getBytes(); } /** * @return array{bytes: string} */ public function __serialize(): array { return ['bytes' => $this->serialize()]; } /** * Re-constructs the object from its serialized form * * @param string $serialized The serialized PHP string to unserialize into * a UuidInterface instance * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function unserialize($serialized): void { if (strlen($serialized) === 16) { /** @var Uuid $uuid */ $uuid = self::getFactory()->fromBytes($serialized); } else { /** @var Uuid $uuid */ $uuid = self::getFactory()->fromString($serialized); } $this->codec = $uuid->codec; $this->numberConverter = $uuid->numberConverter; $this->fields = $uuid->fields; $this->timeConverter = $uuid->timeConverter; } /** * @param array{bytes: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['bytes'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['bytes']); } public function compareTo(UuidInterface $other): int { $compare = strcmp($this->toString(), $other->toString()); if ($compare < 0) { return -1; } if ($compare > 0) { return 1; } return 0; } public function equals(?object $other): bool { if (!$other instanceof UuidInterface) { return false; } return $this->compareTo($other) === 0; } /** * @psalm-return non-empty-string */ public function getBytes(): string { return $this->codec->encodeBinary($this); } public function getFields(): FieldsInterface { return $this->fields; } public function getHex(): Hexadecimal { return new Hexadecimal(str_replace('-', '', $this->toString())); } public function getInteger(): IntegerObject { return new IntegerObject($this->numberConverter->fromHex($this->getHex()->toString())); } public function getUrn(): string { return 'urn:uuid:' . $this->toString(); } /** * @psalm-return non-empty-string */ public function toString(): string { return $this->codec->encode($this); } /** * Returns the factory used to create UUIDs */ public static function getFactory(): UuidFactoryInterface { if (self::$factory === null) { self::$factory = new UuidFactory(); } return self::$factory; } /** * Sets the factory used to create UUIDs * * @param UuidFactoryInterface $factory A factory that will be used by this * class to create UUIDs */ public static function setFactory(UuidFactoryInterface $factory): void { // Note: non-strict equality is intentional here. If the factory is configured differently, every assumption // around purity is broken, and we have to internally decide everything differently. // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator self::$factoryReplaced = ($factory != new UuidFactory()); self::$factory = $factory; } /** * Creates a UUID from a byte string * * @param string $bytes A binary string * * @return UuidInterface A UuidInterface instance created from a binary * string representation * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function fromBytes(string $bytes): UuidInterface { if (! self::$factoryReplaced && strlen($bytes) === 16) { $base16Uuid = bin2hex($bytes); // Note: we are calling `fromString` internally because we don't know if the given `$bytes` is a valid UUID return self::fromString( substr($base16Uuid, 0, 8) . '-' . substr($base16Uuid, 8, 4) . '-' . substr($base16Uuid, 12, 4) . '-' . substr($base16Uuid, 16, 4) . '-' . substr($base16Uuid, 20, 12) ); } return self::getFactory()->fromBytes($bytes); } /** * Creates a UUID from the string standard representation * * @param string $uuid A hexadecimal string * * @return UuidInterface A UuidInterface instance created from a hexadecimal * string representation * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); if (! self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) { assert($uuid !== ''); return new LazyUuidFromString($uuid); } return self::getFactory()->fromString($uuid); } /** * Creates a UUID from a DateTimeInterface instance * * @param DateTimeInterface $dateTime The date and time * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID created from a DateTimeInterface instance */ public static function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->fromDateTime($dateTime, $node, $clockSeq); } /** * Creates a UUID from a 128-bit integer string * * @param string $integer String representation of 128-bit integer * * @return UuidInterface A UuidInterface instance created from the string * representation of a 128-bit integer * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ public static function fromInteger(string $integer): UuidInterface { return self::getFactory()->fromInteger($integer); } /** * Returns true if the provided string is a valid UUID * * @param string $uuid A string to validate as a UUID * * @return bool True if the string is a valid UUID, false otherwise * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ public static function isValid(string $uuid): bool { return self::getFactory()->getValidator()->validate($uuid); } /** * Returns a version 1 (time-based) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID */ public static function uuid1($node = null, ?int $clockSeq = null): UuidInterface { return self::getFactory()->uuid1($node, $clockSeq); } /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes (in a version 2 UUID, the lower 8 bits of this number * are replaced with the domain). * * @return UuidInterface A UuidInterface instance that represents a * version 2 UUID */ public static function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->uuid2($localDomain, $localIdentifier, $node, $clockSeq); } /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 3 UUID * * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ public static function uuid3($ns, string $name): UuidInterface { return self::getFactory()->uuid3($ns, $name); } /** * Returns a version 4 (random) UUID * * @return UuidInterface A UuidInterface instance that represents a * version 4 UUID */ public static function uuid4(): UuidInterface { return self::getFactory()->uuid4(); } /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 5 UUID * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function uuid5($ns, string $name): UuidInterface { return self::getFactory()->uuid5($ns, $name); } /** * Returns a version 6 (ordered-time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return UuidInterface A UuidInterface instance that represents a * version 6 UUID */ public static function uuid6( ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->uuid6($node, $clockSeq); } } PK!. Builder/UuidBuilderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\UuidInterface; /** * A UUID builder builds instances of UuidInterface * * @psalm-immutable */ interface UuidBuilderInterface { /** * Builds and returns a UuidInterface * * @param CodecInterface $codec The codec to use for building this UuidInterface instance * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface Implementations may choose to return more specific * instances of UUIDs that implement UuidInterface * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface; } PK!Builder/DegradedUuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\Time\DegradedTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\DegradedUuid; use Ramsey\Uuid\Rfc4122\Fields as Rfc4122Fields; use Ramsey\Uuid\UuidInterface; /** * @deprecated DegradedUuid instances are no longer necessary to support 32-bit * systems. Transition to {@see DefaultUuidBuilder}. * * @psalm-immutable */ class DegradedUuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeConverterInterface */ private $timeConverter; /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the DegradedUuid * @param TimeConverterInterface|null $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( NumberConverterInterface $numberConverter, ?TimeConverterInterface $timeConverter = null ) { $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter ?: new DegradedTimeConverter(); } /** * Builds and returns a DegradedUuid * * @param CodecInterface $codec The codec to use for building this DegradedUuid instance * @param string $bytes The byte string from which to construct a UUID * * @return DegradedUuid The DegradedUuidBuild returns an instance of Ramsey\Uuid\DegradedUuid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { return new DegradedUuid( new Rfc4122Fields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } } PK!SG Builder/BuilderCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Collection\AbstractCollection; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Traversable; /** * A collection of UuidBuilderInterface objects * * @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from * a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced * at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use * more generic types like `iterable` instead. * * @extends AbstractCollection */ class BuilderCollection extends AbstractCollection { public function getType(): string { return UuidBuilderInterface::class; } /** * @psalm-mutation-free * @psalm-suppress ImpureMethodCall * @psalm-suppress InvalidTemplateParam */ public function getIterator(): Traversable { return parent::getIterator(); } /** * Re-constructs the object from its serialized form * * @param string $serialized The serialized PHP string to unserialize into * a UuidInterface instance * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress RedundantConditionGivenDocblockType */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, [ 'allowed_classes' => [ BrickMathCalculator::class, GenericNumberConverter::class, GenericTimeConverter::class, GuidBuilder::class, NonstandardUuidBuilder::class, PhpTimeConverter::class, Rfc4122UuidBuilder::class, ], ]); $this->data = array_filter( $data, function ($unserialized): bool { return $unserialized instanceof UuidBuilderInterface; } ); } } PK!_foBuilder/FallbackBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Exception\BuilderNotFoundException; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; /** * FallbackBuilder builds a UUID by stepping through a list of UUID builders * until a UUID can be constructed without exceptions * * @psalm-immutable */ class FallbackBuilder implements UuidBuilderInterface { /** * @var iterable */ private $builders; /** * @param iterable $builders An array of UUID builders */ public function __construct(iterable $builders) { $this->builders = $builders; } /** * Builds and returns a UuidInterface instance using the first builder that * succeeds * * @param CodecInterface $codec The codec to use for building this instance * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface an instance of a UUID object * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { $lastBuilderException = null; foreach ($this->builders as $builder) { try { return $builder->build($codec, $bytes); } catch (UnableToBuildUuidException $exception) { $lastBuilderException = $exception; continue; } } throw new BuilderNotFoundException( 'Could not find a suitable builder for the provided codec and fields', 0, $lastBuilderException ); } } PK!688Builder/DefaultUuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; /** * @deprecated Transition to {@see Rfc4122UuidBuilder}. * * @psalm-immutable */ class DefaultUuidBuilder extends Rfc4122UuidBuilder { } PK! , Guid/GuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; use Throwable; /** * GuidBuilder builds instances of Guid * * @see Guid * * @psalm-immutable */ class GuidBuilder implements UuidBuilderInterface { /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeConverterInterface */ private $timeConverter; /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Guid * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( NumberConverterInterface $numberConverter, TimeConverterInterface $timeConverter ) { $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter; } /** * Builds and returns a Guid * * @param CodecInterface $codec The codec to use for building this Guid instance * @param string $bytes The byte string from which to construct a UUID * * @return Guid The GuidBuilder returns an instance of Ramsey\Uuid\Guid\Guid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { return new Guid( $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): Fields { return new Fields($bytes); } } PK!o Guid/Guid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Uuid; /** * Guid represents a UUID with "native" (little-endian) byte order * * From Wikipedia: * * > The first three fields are unsigned 32- and 16-bit integers and are subject * > to swapping, while the last two fields consist of uninterpreted bytes, not * > subject to swapping. This byte swapping applies even for versions 3, 4, and * > 5, where the canonical fields do not correspond to the content of the UUID. * * The first three fields of a GUID are encoded in little-endian byte order, * while the last three fields are in network (big-endian) byte order. This is * according to the history of the Microsoft definition of a GUID. * * According to the .NET Guid.ToByteArray method documentation: * * > Note that the order of bytes in the returned byte array is different from * > the string representation of a Guid value. The order of the beginning * > four-byte group and the next two two-byte groups is reversed, whereas the * > order of the last two-byte group and the closing six-byte group is the * > same. * * @link https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants UUID Variants on Wikipedia * @link https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid Windows GUID structure * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid .NET Guid Struct * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray .NET Guid.ToByteArray Method * * @psalm-immutable */ final class Guid extends Uuid { public function __construct( Fields $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PK!MGuid/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\NilTrait; use Ramsey\Uuid\Rfc4122\VariantTrait; use Ramsey\Uuid\Rfc4122\VersionTrait; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Uuid; use function bin2hex; use function dechex; use function hexdec; use function pack; use function sprintf; use function str_pad; use function strlen; use function substr; use function unpack; use const STR_PAD_LEFT; /** * GUIDs are comprised of a set of named fields, according to RFC 4122 * * @see Guid * * @psalm-immutable */ final class Fields implements FieldsInterface { use NilTrait; use SerializableFieldsTrait; use VariantTrait; use VersionTrait; /** * @var string */ private $bytes; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes * @throws InvalidArgumentException if the byte string does not represent a GUID * @throws InvalidArgumentException if the byte string does not contain a valid version */ public function __construct(string $bytes) { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($bytes) . ' bytes' ); } $this->bytes = $bytes; if (!$this->isCorrectVariant()) { throw new InvalidArgumentException( 'The byte string received does not conform to the RFC ' . '4122 or Microsoft Corporation variants' ); } if (!$this->isCorrectVersion()) { throw new InvalidArgumentException( 'The byte string received does not contain a valid version' ); } } public function getBytes(): string { return $this->bytes; } public function getTimeLow(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v*', hexdec(bin2hex(substr($this->bytes, 2, 2))), hexdec(bin2hex(substr($this->bytes, 0, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimeMid(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v', hexdec(bin2hex(substr($this->bytes, 4, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimeHiAndVersion(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v', hexdec(bin2hex(substr($this->bytes, 6, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimestamp(): Hexadecimal { return new Hexadecimal(sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() )); } public function getClockSeq(): Hexadecimal { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getVersion(): ?int { if ($this->isNil()) { return null; } /** @var array $parts */ $parts = unpack('n*', $this->bytes); return ((int) $parts[4] >> 4) & 0x00f; } private function isCorrectVariant(): bool { if ($this->isNil()) { return true; } $variant = $this->getVariant(); return $variant === Uuid::RFC_4122 || $variant === Uuid::RESERVED_MICROSOFT; } } PK!Q. vvDeprecatedUuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; /** * This interface encapsulates deprecated methods for ramsey/uuid; this * interface and its methods will be removed in ramsey/uuid 5.0.0. * * @psalm-immutable */ interface DeprecatedUuidInterface { /** * @deprecated This method will be removed in 5.0.0. There is no alternative * recommendation, so plan accordingly. */ public function getNumberConverter(): NumberConverterInterface; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. * * @return string[] */ public function getFieldsHex(): array; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}. */ public function getClockSeqHiAndReservedHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}. */ public function getClockSeqLowHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}. */ public function getClockSequenceHex(): string; /** * @deprecated In ramsey/uuid version 5.0.0, this will be removed from the * interface. It is available at {@see UuidV1::getDateTime()}. */ public function getDateTime(): DateTimeInterface; /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBitsHex(): string; /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBitsHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()}. */ public function getNodeHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}. */ public function getTimeHiAndVersionHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()}. */ public function getTimeLowHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()}. */ public function getTimeMidHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()}. */ public function getTimestampHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. */ public function getVariant(): ?int; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. */ public function getVersion(): ?int; } PK!+Generator/DceSecurityGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * A DCE Security generator generates strings of binary data based on a local * domain, local identifier, node ID, clock sequence, and the current time * * @see UuidV2 */ interface DceSecurityGeneratorInterface { /** * Generate a binary string from a local domain, local identifier, node ID, * clock sequence, and current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return string A binary string */ public function generate( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string; } PK!q''%Generator/PeclUuidRandomGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use function uuid_create; use function uuid_parse; use const UUID_TYPE_RANDOM; /** * PeclUuidRandomGenerator generates strings of random binary data using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidRandomGenerator implements RandomGeneratorInterface { public function generate(int $length): string { $uuid = uuid_create(UUID_TYPE_RANDOM); return uuid_parse($uuid); } } PK!*Y #Generator/PeclUuidNameGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\NameException; use Ramsey\Uuid\UuidInterface; use function sprintf; use function uuid_generate_md5; use function uuid_generate_sha1; use function uuid_parse; /** * PeclUuidNameGenerator generates strings of binary data from a namespace and a * name, using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidNameGenerator implements NameGeneratorInterface { /** @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { switch ($hashAlgorithm) { case 'md5': $uuid = uuid_generate_md5($ns->toString(), $name); break; case 'sha1': $uuid = uuid_generate_sha1($ns->toString(), $name); break; default: throw new NameException(sprintf( 'Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm )); } return uuid_parse($uuid); } } PK!N"Generator/DefaultNameGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\NameException; use Ramsey\Uuid\UuidInterface; use ValueError; use function hash; /** * DefaultNameGenerator generates strings of binary data based on a namespace, * name, and hashing algorithm */ class DefaultNameGenerator implements NameGeneratorInterface { /** @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { try { /** @var string|bool $bytes */ $bytes = @hash($hashAlgorithm, $ns->getBytes() . $name, true); } catch (ValueError $e) { $bytes = false; // keep same behavior than PHP 7 } if ($bytes === false) { throw new NameException(sprintf( 'Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm )); } return (string) $bytes; } } PK!ng g Generator/CombGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use function bin2hex; use function explode; use function hex2bin; use function microtime; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * CombGenerator generates COMBs (combined UUID/timestamp) * * The CombGenerator, when used with the StringCodec (and, by proxy, the * TimestampLastCombCodec) or the TimestampFirstCombCodec, combines the current * timestamp with a UUID (hence the name "COMB"). The timestamp either appears * as the first or last 48 bits of the COMB, depending on the codec used. * * By default, COMBs will have the timestamp set as the last 48 bits of the * identifier. * * ``` php * $factory = new UuidFactory(); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $comb = $factory->uuid4(); * ``` * * To generate a COMB with the timestamp as the first 48 bits, set the * TimestampFirstCombCodec as the codec. * * ``` php * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys */ class CombGenerator implements RandomGeneratorInterface { public const TIMESTAMP_BYTES = 6; /** * @var RandomGeneratorInterface */ private $randomGenerator; /** * @var NumberConverterInterface */ private $converter; public function __construct( RandomGeneratorInterface $generator, NumberConverterInterface $numberConverter ) { $this->converter = $numberConverter; $this->randomGenerator = $generator; } /** * @throws InvalidArgumentException if $length is not a positive integer * greater than or equal to CombGenerator::TIMESTAMP_BYTES * * @inheritDoc */ public function generate(int $length): string { if ($length < self::TIMESTAMP_BYTES) { throw new InvalidArgumentException( 'Length must be a positive integer greater than or equal to ' . self::TIMESTAMP_BYTES ); } $hash = ''; if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { $hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES); } $lsbTime = str_pad( $this->converter->toHex($this->timestamp()), self::TIMESTAMP_BYTES * 2, '0', STR_PAD_LEFT ); return (string) hex2bin( str_pad( bin2hex($hash), $length - self::TIMESTAMP_BYTES, '0' ) . $lsbTime ); } /** * Returns current timestamp a string integer, precise to 0.00001 seconds */ private function timestamp(): string { $time = explode(' ', microtime(false)); return $time[1] . substr($time[0], 2, 5); } } PK!H7"Generator/TimeGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; /** * TimeGeneratorFactory retrieves a default time generator, based on the * environment */ class TimeGeneratorFactory { /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var TimeProviderInterface */ private $timeProvider; public function __construct( NodeProviderInterface $nodeProvider, TimeConverterInterface $timeConverter, TimeProviderInterface $timeProvider ) { $this->nodeProvider = $nodeProvider; $this->timeConverter = $timeConverter; $this->timeProvider = $timeProvider; } /** * Returns a default time generator, based on the current environment */ public function getGenerator(): TimeGeneratorInterface { return new DefaultTimeGenerator( $this->nodeProvider, $this->timeConverter, $this->timeProvider ); } } PK!!"Generator/NameGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * NameGeneratorFactory retrieves a default name generator, based on the * environment */ class NameGeneratorFactory { /** * Returns a default name generator, based on the current environment */ public function getGenerator(): NameGeneratorInterface { return new DefaultNameGenerator(); } } PK!:Fr22Generator/RandomLibAdapter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use RandomLib\Factory; use RandomLib\Generator; /** * RandomLibAdapter generates strings of random binary data using the * paragonie/random-lib library * * @deprecated This class will be removed in 5.0.0. Use the default * RandomBytesGenerator or implement your own generator that implements * RandomGeneratorInterface. * * @link https://packagist.org/packages/paragonie/random-lib paragonie/random-lib */ class RandomLibAdapter implements RandomGeneratorInterface { /** * @var Generator */ private $generator; /** * Constructs a RandomLibAdapter * * By default, if no Generator is passed in, this creates a high-strength * generator to use when generating random binary data. * * @param Generator|null $generator The generator to use when generating binary data */ public function __construct(?Generator $generator = null) { if ($generator === null) { $factory = new Factory(); $generator = $factory->getHighStrengthGenerator(); } $this->generator = $generator; } public function generate(int $length): string { return $this->generator->generate($length); } } PK!ii#Generator/PeclUuidTimeGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use function uuid_create; use function uuid_parse; use const UUID_TYPE_TIME; /** * PeclUuidTimeGenerator generates strings of binary data for time-base UUIDs, * using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidTimeGenerator implements TimeGeneratorInterface { /** * @inheritDoc */ public function generate($node = null, ?int $clockSeq = null): string { $uuid = uuid_create(UUID_TYPE_TIME); return uuid_parse($uuid); } } PK!Bx$Generator/TimeGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Type\Hexadecimal; /** * A time generator generates strings of binary data based on a node ID, * clock sequence, and the current time */ interface TimeGeneratorInterface { /** * Generate a binary string from a node ID, clock sequence, and current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return string A binary string */ public function generate($node = null, ?int $clockSeq = null): string; } PK!J$Generator/RandomGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * RandomGeneratorFactory retrieves a default random generator, based on the * environment */ class RandomGeneratorFactory { /** * Returns a default random generator, based on the current environment */ public function getGenerator(): RandomGeneratorInterface { return new RandomBytesGenerator(); } } PK!'&xx"Generator/RandomBytesGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\RandomSourceException; use Throwable; /** * RandomBytesGenerator generates strings of random binary data using the * built-in `random_bytes()` PHP function * * @link http://php.net/random_bytes random_bytes() */ class RandomBytesGenerator implements RandomGeneratorInterface { /** * @throws RandomSourceException if random_bytes() throws an exception/error * * @inheritDoc */ public function generate(int $length): string { try { return random_bytes($length); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } } PK!&Generator/RandomGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * A random generator generates strings of random binary data */ interface RandomGeneratorInterface { /** * Generates a string of randomized binary data * * @param int $length The number of bytes of random binary data to generate * * @return string A binary string */ public function generate(int $length): string; } PK!s**$Generator/NameGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\UuidInterface; /** * A name generator generates strings of binary data created by hashing together * a namespace with a name, according to a hashing algorithm */ interface NameGeneratorInterface { /** * Generate a binary string from a namespace and name hashed together with * the specified hashing algorithm * * @param UuidInterface $ns The namespace * @param string $name The name to use for creating a UUID * @param string $hashAlgorithm The hashing algorithm to use * * @return string A binary string * * @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string; } PK!Jll"Generator/DefaultTimeGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\RandomSourceException; use Ramsey\Uuid\Exception\TimeSourceException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Throwable; use function ctype_xdigit; use function dechex; use function hex2bin; use function is_int; use function pack; use function sprintf; use function str_pad; use function strlen; use const STR_PAD_LEFT; /** * DefaultTimeGenerator generates strings of binary data based on a node ID, * clock sequence, and the current time */ class DefaultTimeGenerator implements TimeGeneratorInterface { /** * @var NodeProviderInterface */ private $nodeProvider; /** * @var TimeConverterInterface */ private $timeConverter; /** * @var TimeProviderInterface */ private $timeProvider; public function __construct( NodeProviderInterface $nodeProvider, TimeConverterInterface $timeConverter, TimeProviderInterface $timeProvider ) { $this->nodeProvider = $nodeProvider; $this->timeConverter = $timeConverter; $this->timeProvider = $timeProvider; } /** * @throws InvalidArgumentException if the parameters contain invalid values * @throws RandomSourceException if random_int() throws an exception/error * * @inheritDoc */ public function generate($node = null, ?int $clockSeq = null): string { if ($node instanceof Hexadecimal) { $node = $node->toString(); } $node = $this->getValidNode($node); if ($clockSeq === null) { try { // This does not use "stable storage"; see RFC 4122, Section 4.2.1.1. $clockSeq = random_int(0, 0x3fff); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } $time = $this->timeProvider->getTime(); $uuidTime = $this->timeConverter->calculateTime( $time->getSeconds()->toString(), $time->getMicroseconds()->toString() ); $timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT); if (strlen($timeHex) !== 16) { throw new TimeSourceException(sprintf( 'The generated time of \'%s\' is larger than expected', $timeHex )); } $timeBytes = (string) hex2bin($timeHex); return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7] . $timeBytes[2] . $timeBytes[3] . $timeBytes[0] . $timeBytes[1] . pack('n*', $clockSeq) . $node; } /** * Uses the node provider given when constructing this instance to get * the node ID (usually a MAC address) * * @param string|int|null $node A node value that may be used to override the node provider * * @return string 6-byte binary string representation of the node * * @throws InvalidArgumentException */ private function getValidNode($node): string { if ($node === null) { $node = $this->nodeProvider->getNode(); } // Convert the node to hex, if it is still an integer. if (is_int($node)) { $node = dechex($node); } if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) { throw new InvalidArgumentException('Invalid node value'); } return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT)); } } PK!jZa@"Generator/DceSecurityGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\DceSecurityException; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; use function hex2bin; use function in_array; use function pack; use function str_pad; use function strlen; use function substr_replace; use const STR_PAD_LEFT; /** * DceSecurityGenerator generates strings of binary data based on a local * domain, local identifier, node ID, clock sequence, and the current time */ class DceSecurityGenerator implements DceSecurityGeneratorInterface { private const DOMAINS = [ Uuid::DCE_DOMAIN_PERSON, Uuid::DCE_DOMAIN_GROUP, Uuid::DCE_DOMAIN_ORG, ]; /** * Upper bounds for the clock sequence in DCE Security UUIDs. */ private const CLOCK_SEQ_HIGH = 63; /** * Lower bounds for the clock sequence in DCE Security UUIDs. */ private const CLOCK_SEQ_LOW = 0; /** * @var NumberConverterInterface */ private $numberConverter; /** * @var TimeGeneratorInterface */ private $timeGenerator; /** * @var DceSecurityProviderInterface */ private $dceSecurityProvider; public function __construct( NumberConverterInterface $numberConverter, TimeGeneratorInterface $timeGenerator, DceSecurityProviderInterface $dceSecurityProvider ) { $this->numberConverter = $numberConverter; $this->timeGenerator = $timeGenerator; $this->dceSecurityProvider = $dceSecurityProvider; } public function generate( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string { if (!in_array($localDomain, self::DOMAINS)) { throw new DceSecurityException( 'Local domain must be a valid DCE Security domain' ); } if ($localIdentifier && $localIdentifier->isNegative()) { throw new DceSecurityException( 'Local identifier out of bounds; it must be a value between 0 and 4294967295' ); } if ($clockSeq > self::CLOCK_SEQ_HIGH || $clockSeq < self::CLOCK_SEQ_LOW) { throw new DceSecurityException( 'Clock sequence out of bounds; it must be a value between 0 and 63' ); } switch ($localDomain) { case Uuid::DCE_DOMAIN_ORG: if ($localIdentifier === null) { throw new DceSecurityException( 'A local identifier must be provided for the org domain' ); } break; case Uuid::DCE_DOMAIN_PERSON: if ($localIdentifier === null) { $localIdentifier = $this->dceSecurityProvider->getUid(); } break; case Uuid::DCE_DOMAIN_GROUP: default: if ($localIdentifier === null) { $localIdentifier = $this->dceSecurityProvider->getGid(); } break; } $identifierHex = $this->numberConverter->toHex($localIdentifier->toString()); // The maximum value for the local identifier is 0xffffffff, or // 4294967295. This is 8 hexadecimal digits, so if the length of // hexadecimal digits is greater than 8, we know the value is greater // than 0xffffffff. if (strlen($identifierHex) > 8) { throw new DceSecurityException( 'Local identifier out of bounds; it must be a value between 0 and 4294967295' ); } $domainByte = pack('n', $localDomain)[1]; $identifierBytes = (string) hex2bin(str_pad($identifierHex, 8, '0', STR_PAD_LEFT)); if ($node instanceof Hexadecimal) { $node = $node->toString(); } // Shift the clock sequence 8 bits to the left, so it matches 0x3f00. if ($clockSeq !== null) { $clockSeq = $clockSeq << 8; } $bytes = $this->timeGenerator->generate($node, $clockSeq); // Replace bytes in the time-based UUID with DCE Security values. $bytes = substr_replace($bytes, $identifierBytes, 0, 4); $bytes = substr_replace($bytes, $domainByte, 9, 1); return $bytes; } } PK!3 UuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use JsonSerializable; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Serializable; /** * A UUID is a universally unique identifier adhering to an agreed-upon * representation format and standard for generation * * @psalm-immutable */ interface UuidInterface extends DeprecatedUuidInterface, JsonSerializable, Serializable { /** * Returns -1, 0, or 1 if the UUID is less than, equal to, or greater than * the other UUID * * The first of two UUIDs is greater than the second if the most * significant field in which the UUIDs differ is greater for the first * UUID. * * * Q. What's the value of being able to sort UUIDs? * * A. Use them as keys in a B-Tree or similar mapping. * * @param UuidInterface $other The UUID to compare * * @return int -1, 0, or 1 if the UUID is less than, equal to, or greater than $other */ public function compareTo(UuidInterface $other): int; /** * Returns true if the UUID is equal to the provided object * * The result is true if and only if the argument is not null, is a UUID * object, has the same variant, and contains the same value, bit for bit, * as the UUID. * * @param object|null $other An object to test for equality with this UUID * * @return bool True if the other object is equal to this UUID */ public function equals(?object $other): bool; /** * Returns the binary string representation of the UUID * * @psalm-return non-empty-string */ public function getBytes(): string; /** * Returns the fields that comprise this UUID */ public function getFields(): FieldsInterface; /** * Returns the hexadecimal representation of the UUID */ public function getHex(): Hexadecimal; /** * Returns the integer representation of the UUID */ public function getInteger(): IntegerObject; /** * Returns the string standard representation of the UUID as a URN * * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name Uniform Resource Name * @link https://tools.ietf.org/html/rfc4122#section-3 RFC 4122, § 3: Namespace Registration Template */ public function getUrn(): string; /** * Returns the string standard representation of the UUID * * @psalm-return non-empty-string */ public function toString(): string; /** * Casts the UUID to the string standard representation * * @psalm-return non-empty-string */ public function __toString(): string; } PK!Yi99(Internal/Calculator/NativeCalculator.phpnu[maxDigits = 9; break; case 8: $this->maxDigits = 18; break; default: throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); } } /** * {@inheritdoc} */ public function add(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a + $b; if (is_int($result)) { return (string) $result; } if ($a === '0') { return $b; } if ($b === '0') { return $a; } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig); if ($aNeg) { $result = $this->neg($result); } return $result; } /** * {@inheritdoc} */ public function sub(string $a, string $b) : string { return $this->add($a, $this->neg($b)); } /** * {@inheritdoc} */ public function mul(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a * $b; if (is_int($result)) { return (string) $result; } if ($a === '0' || $b === '0') { return '0'; } if ($a === '1') { return $b; } if ($b === '1') { return $a; } if ($a === '-1') { return $this->neg($b); } if ($b === '-1') { return $this->neg($a); } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $this->doMul($aDig, $bDig); if ($aNeg !== $bNeg) { $result = $this->neg($result); } return $result; } /** * {@inheritdoc} */ public function divQ(string $a, string $b) : string { return $this->divQR($a, $b)[0]; } /** * {@inheritdoc} */ public function divR(string $a, string $b): string { return $this->divQR($a, $b)[1]; } /** * {@inheritdoc} */ public function divQR(string $a, string $b) : array { if ($a === '0') { return ['0', '0']; } if ($a === $b) { return ['1', '0']; } if ($b === '1') { return [$a, '0']; } if ($b === '-1') { return [$this->neg($a), '0']; } /** @psalm-var numeric-string $a */ $na = $a * 1; // cast to number if (is_int($na)) { /** @psalm-var numeric-string $b */ $nb = $b * 1; if (is_int($nb)) { // the only division that may overflow is PHP_INT_MIN / -1, // which cannot happen here as we've already handled a divisor of -1 above. $r = $na % $nb; $q = ($na - $r) / $nb; assert(is_int($q)); return [ (string) $q, (string) $r ]; } } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); [$q, $r] = $this->doDiv($aDig, $bDig); if ($aNeg !== $bNeg) { $q = $this->neg($q); } if ($aNeg) { $r = $this->neg($r); } return [$q, $r]; } /** * {@inheritdoc} */ public function pow(string $a, int $e) : string { if ($e === 0) { return '1'; } if ($e === 1) { return $a; } $odd = $e % 2; $e -= $odd; $aa = $this->mul($a, $a); /** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */ $result = $this->pow($aa, $e / 2); if ($odd === 1) { $result = $this->mul($result, $a); } return $result; } /** * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ * * {@inheritdoc} */ public function modPow(string $base, string $exp, string $mod) : string { // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) if ($base === '0' && $exp === '0' && $mod === '1') { return '0'; } // special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0) if ($exp === '0' && $mod === '1') { return '0'; } $x = $base; $res = '1'; // numbers are positive, so we can use remainder instead of modulo $x = $this->divR($x, $mod); while ($exp !== '0') { if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd $res = $this->divR($this->mul($res, $x), $mod); } $exp = $this->divQ($exp, '2'); $x = $this->divR($this->mul($x, $x), $mod); } return $res; } /** * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html * * {@inheritDoc} */ public function sqrt(string $n) : string { if ($n === '0') { return '0'; } // initial approximation $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); $decreased = false; for (;;) { $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { break; } $decreased = $this->cmp($nx, $x) < 0; $x = $nx; } return $x; } /** * Performs the addition of two non-signed large integers. * * @param string $a The first operand. * @param string $b The second operand. * * @return string */ private function doAdd(string $a, string $b) : string { [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = (string) ($blockA + $blockB + $carry); $sumLength = \strlen($sum); if ($sumLength > $blockLength) { $sum = \substr($sum, 1); $carry = 1; } else { if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $carry = 0; } $result = $sum . $result; if ($i === 0) { break; } } if ($carry === 1) { $result = '1' . $result; } return $result; } /** * Performs the subtraction of two non-signed large integers. * * @param string $a The first operand. * @param string $b The second operand. * * @return string */ private function doSub(string $a, string $b) : string { if ($a === $b) { return '0'; } // Ensure that we always subtract to a positive result: biggest minus smallest. $cmp = $this->doCmp($a, $b); $invert = ($cmp === -1); if ($invert) { $c = $a; $a = $b; $b = $c; } [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; $complement = 10 ** $this->maxDigits; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = $blockA - $blockB - $carry; if ($sum < 0) { $sum += $complement; $carry = 1; } else { $carry = 0; } $sum = (string) $sum; $sumLength = \strlen($sum); if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $result = $sum . $result; if ($i === 0) { break; } } // Carry cannot be 1 when the loop ends, as a > b assert($carry === 0); $result = \ltrim($result, '0'); if ($invert) { $result = $this->neg($result); } return $result; } /** * Performs the multiplication of two non-signed large integers. * * @param string $a The first operand. * @param string $b The second operand. * * @return string */ private function doMul(string $a, string $b) : string { $x = \strlen($a); $y = \strlen($b); $maxDigits = \intdiv($this->maxDigits, 2); $complement = 10 ** $maxDigits; $result = '0'; for ($i = $x - $maxDigits;; $i -= $maxDigits) { $blockALength = $maxDigits; if ($i < 0) { $blockALength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } $blockA = (int) \substr($a, $i, $blockALength); $line = ''; $carry = 0; for ($j = $y - $maxDigits;; $j -= $maxDigits) { $blockBLength = $maxDigits; if ($j < 0) { $blockBLength += $j; /** @psalm-suppress LoopInvalidation */ $j = 0; } $blockB = (int) \substr($b, $j, $blockBLength); $mul = $blockA * $blockB + $carry; $value = $mul % $complement; $carry = ($mul - $value) / $complement; $value = (string) $value; $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); $line = $value . $line; if ($j === 0) { break; } } if ($carry !== 0) { $line = $carry . $line; } $line = \ltrim($line, '0'); if ($line !== '') { $line .= \str_repeat('0', $x - $blockALength - $i); $result = $this->add($result, $line); } if ($i === 0) { break; } } return $result; } /** * Performs the division of two non-signed large integers. * * @param string $a The first operand. * @param string $b The second operand. * * @return string[] The quotient and remainder. */ private function doDiv(string $a, string $b) : array { $cmp = $this->doCmp($a, $b); if ($cmp === -1) { return ['0', $a]; } $x = \strlen($a); $y = \strlen($b); // we now know that a >= b && x >= y $q = '0'; // quotient $r = $a; // remainder $z = $y; // focus length, always $y or $y+1 for (;;) { $focus = \substr($a, 0, $z); $cmp = $this->doCmp($focus, $b); if ($cmp === -1) { if ($z === $x) { // remainder < dividend break; } $z++; } $zeros = \str_repeat('0', $x - $z); $q = $this->add($q, '1' . $zeros); $a = $this->sub($a, $b . $zeros); $r = $a; if ($r === '0') { // remainder == 0 break; } $x = \strlen($a); if ($x < $y) { // remainder < dividend break; } $z = $y; } return [$q, $r]; } /** * Compares two non-signed large numbers. * * @param string $a The first operand. * @param string $b The second operand. * * @return int [-1, 0, 1] */ private function doCmp(string $a, string $b) : int { $x = \strlen($a); $y = \strlen($b); $cmp = $x <=> $y; if ($cmp !== 0) { return $cmp; } return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1] } /** * Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. * * The numbers must only consist of digits, without leading minus sign. * * @param string $a The first operand. * @param string $b The second operand. * * @return array{string, string, int} */ private function pad(string $a, string $b) : array { $x = \strlen($a); $y = \strlen($b); if ($x > $y) { $b = \str_repeat('0', $x - $y) . $b; return [$a, $b, $x]; } if ($x < $y) { $a = \str_repeat('0', $y - $x) . $a; return [$a, $b, $y]; } return [$a, $b, $x]; } } PK!D.|` ` %Internal/Calculator/GmpCalculator.phpnu[= 0) { return \bcmod($a, $b, 0); } return \bcmod($a, $b); } /** * {@inheritdoc} */ public function divQR(string $a, string $b) : array { $q = \bcdiv($a, $b, 0); if (version_compare(PHP_VERSION, '7.2') >= 0) { $r = \bcmod($a, $b, 0); } else { $r = \bcmod($a, $b); } assert($q !== null); assert($r !== null); return [$q, $r]; } /** * {@inheritdoc} */ public function pow(string $a, int $e) : string { return \bcpow($a, (string) $e, 0); } /** * {@inheritdoc} * * @psalm-suppress InvalidNullableReturnType * @psalm-suppress NullableReturnStatement */ public function modPow(string $base, string $exp, string $mod) : string { return \bcpowmod($base, $exp, $mod, 0); } /** * {@inheritDoc} * * @psalm-suppress NullableReturnStatement * @psalm-suppress InvalidNullableReturnType */ public function sqrt(string $n) : string { return \bcsqrt($n, 0); } } PK!rsQQInternal/Calculator.phpnu[init($a, $b); if ($aNeg && ! $bNeg) { return -1; } if ($bNeg && ! $aNeg) { return 1; } $aLen = \strlen($aDig); $bLen = \strlen($bDig); if ($aLen < $bLen) { $result = -1; } elseif ($aLen > $bLen) { $result = 1; } else { $result = $aDig <=> $bDig; } return $aNeg ? -$result : $result; } /** * Adds two numbers. * * @param string $a The augend. * @param string $b The addend. * * @return string The sum. */ abstract public function add(string $a, string $b) : string; /** * Subtracts two numbers. * * @param string $a The minuend. * @param string $b The subtrahend. * * @return string The difference. */ abstract public function sub(string $a, string $b) : string; /** * Multiplies two numbers. * * @param string $a The multiplicand. * @param string $b The multiplier. * * @return string The product. */ abstract public function mul(string $a, string $b) : string; /** * Returns the quotient of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The quotient. */ abstract public function divQ(string $a, string $b) : string; /** * Returns the remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The remainder. */ abstract public function divR(string $a, string $b) : string; /** * Returns the quotient and remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string[] An array containing the quotient and remainder. */ abstract public function divQR(string $a, string $b) : array; /** * Exponentiates a number. * * @param string $a The base number. * @param int $e The exponent, validated as an integer between 0 and MAX_POWER. * * @return string The power. */ abstract public function pow(string $a, int $e) : string; /** * @param string $a * @param string $b The modulus; must not be zero. * * @return string */ public function mod(string $a, string $b) : string { return $this->divR($this->add($this->divR($a, $b), $b), $b); } /** * Returns the modular multiplicative inverse of $x modulo $m. * * If $x has no multiplicative inverse mod m, this method must return null. * * This method can be overridden by the concrete implementation if the underlying library has built-in support. * * @param string $x * @param string $m The modulus; must not be negative or zero. * * @return string|null */ public function modInverse(string $x, string $m) : ?string { if ($m === '1') { return '0'; } $modVal = $x; if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { $modVal = $this->mod($x, $m); } $x = '0'; $y = '0'; $g = $this->gcdExtended($modVal, $m, $x, $y); if ($g !== '1') { return null; } return $this->mod($this->add($this->mod($x, $m), $m), $m); } /** * Raises a number into power with modulo. * * @param string $base The base number; must be positive or zero. * @param string $exp The exponent; must be positive or zero. * @param string $mod The modulus; must be strictly positive. * * @return string The power. */ abstract public function modPow(string $base, string $exp, string $mod) : string; /** * Returns the greatest common divisor of the two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for GCD calculations. * * @param string $a The first number. * @param string $b The second number. * * @return string The GCD, always positive, or zero if both arguments are zero. */ public function gcd(string $a, string $b) : string { if ($a === '0') { return $this->abs($b); } if ($b === '0') { return $this->abs($a); } return $this->gcd($b, $this->divR($a, $b)); } private function gcdExtended(string $a, string $b, string &$x, string &$y) : string { if ($a === '0') { $x = '0'; $y = '1'; return $b; } $x1 = '0'; $y1 = '0'; $gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1); $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); $y = $x1; return $gcd; } /** * Returns the square root of the given number, rounded down. * * The result is the largest x such that x² ≤ n. * The input MUST NOT be negative. * * @param string $n The number. * * @return string The square root. */ abstract public function sqrt(string $n) : string; /** * Converts a number from an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. * @param int $base The base of the number, validated from 2 to 36. * * @return string The converted number, following the Calculator conventions. */ public function fromBase(string $number, int $base) : string { return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); } /** * Converts a number to an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number to convert, following the Calculator conventions. * @param int $base The base to convert to, validated from 2 to 36. * * @return string The converted number, lowercase. */ public function toBase(string $number, int $base) : string { $negative = ($number[0] === '-'); if ($negative) { $number = \substr($number, 1); } $number = $this->toArbitraryBase($number, self::ALPHABET, $base); if ($negative) { return '-' . $number; } return $number; } /** * Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. * * @param string $number The number to convert, validated as a non-empty string, * containing only chars in the given alphabet/base. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base of the number, validated from 2 to alphabet length. * * @return string The number in base 10, following the Calculator conventions. */ final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string { // remove leading "zeros" $number = \ltrim($number, $alphabet[0]); if ($number === '') { return '0'; } // optimize for "one" if ($number === $alphabet[1]) { return '1'; } $result = '0'; $power = '1'; $base = (string) $base; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $index = \strpos($alphabet, $number[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, $base); } } return $result; } /** * Converts a non-negative number to an arbitrary base using a custom alphabet. * * @param string $number The number to convert, positive or zero, following the Calculator conventions. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base to convert to, validated from 2 to alphabet length. * * @return string The converted number in the given alphabet. */ final public function toArbitraryBase(string $number, string $alphabet, int $base) : string { if ($number === '0') { return $alphabet[0]; } $base = (string) $base; $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, $base); $remainder = (int) $remainder; $result .= $alphabet[$remainder]; } return \strrev($result); } /** * Performs a rounded division. * * Rounding is performed when the remainder of the division is not zero. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * @param int $roundingMode The rounding mode. * * @return string * * @throws \InvalidArgumentException If the rounding mode is invalid. * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. */ final public function divRound(string $a, string $b, int $roundingMode) : string { [$quotient, $remainder] = $this->divQR($a, $b); $hasDiscardedFraction = ($remainder !== '0'); $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); $discardedFractionSign = function() use ($remainder, $b) : int { $r = $this->abs($this->mul($remainder, '2')); $b = $this->abs($b); return $this->cmp($r, $b); }; $increment = false; switch ($roundingMode) { case RoundingMode::UNNECESSARY: if ($hasDiscardedFraction) { throw RoundingNecessaryException::roundingNecessary(); } break; case RoundingMode::UP: $increment = $hasDiscardedFraction; break; case RoundingMode::DOWN: break; case RoundingMode::CEILING: $increment = $hasDiscardedFraction && $isPositiveOrZero; break; case RoundingMode::FLOOR: $increment = $hasDiscardedFraction && ! $isPositiveOrZero; break; case RoundingMode::HALF_UP: $increment = $discardedFractionSign() >= 0; break; case RoundingMode::HALF_DOWN: $increment = $discardedFractionSign() > 0; break; case RoundingMode::HALF_CEILING: $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; break; case RoundingMode::HALF_FLOOR: $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; case RoundingMode::HALF_EVEN: $lastDigit = (int) $quotient[-1]; $lastDigitIsEven = ($lastDigit % 2 === 0); $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; default: throw new \InvalidArgumentException('Invalid rounding mode.'); } if ($increment) { return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); } return $quotient; } /** * Calculates bitwise AND of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. * * @param string $a * @param string $b * * @return string */ public function and(string $a, string $b) : string { return $this->bitwise('and', $a, $b); } /** * Calculates bitwise OR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. * * @param string $a * @param string $b * * @return string */ public function or(string $a, string $b) : string { return $this->bitwise('or', $a, $b); } /** * Calculates bitwise XOR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. * * @param string $a * @param string $b * * @return string */ public function xor(string $a, string $b) : string { return $this->bitwise('xor', $a, $b); } /** * Performs a bitwise operation on a decimal number. * * @param string $operator The operator to use, must be "and", "or" or "xor". * @param string $a The left operand. * @param string $b The right operand. * * @return string */ private function bitwise(string $operator, string $a, string $b) : string { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $aBin = $this->toBinary($aDig); $bBin = $this->toBinary($bDig); $aLen = \strlen($aBin); $bLen = \strlen($bBin); if ($aLen > $bLen) { $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; } elseif ($bLen > $aLen) { $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; } if ($aNeg) { $aBin = $this->twosComplement($aBin); } if ($bNeg) { $bBin = $this->twosComplement($bBin); } switch ($operator) { case 'and': $value = $aBin & $bBin; $negative = ($aNeg and $bNeg); break; case 'or': $value = $aBin | $bBin; $negative = ($aNeg or $bNeg); break; case 'xor': $value = $aBin ^ $bBin; $negative = ($aNeg xor $bNeg); break; // @codeCoverageIgnoreStart default: throw new \InvalidArgumentException('Invalid bitwise operator.'); // @codeCoverageIgnoreEnd } if ($negative) { $value = $this->twosComplement($value); } $result = $this->toDecimal($value); return $negative ? $this->neg($result) : $result; } /** * @param string $number A positive, binary number. * * @return string */ private function twosComplement(string $number) : string { $xor = \str_repeat("\xff", \strlen($number)); $number ^= $xor; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $byte = \ord($number[$i]); if (++$byte !== 256) { $number[$i] = \chr($byte); break; } $number[$i] = "\x00"; if ($i === 0) { $number = "\x01" . $number; } } return $number; } /** * Converts a decimal number to a binary string. * * @param string $number The number to convert, positive or zero, only digits. * * @return string */ private function toBinary(string $number) : string { $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, '256'); $result .= \chr((int) $remainder); } return \strrev($result); } /** * Returns the positive decimal representation of a binary number. * * @param string $bytes The bytes representing the number. * * @return string */ private function toDecimal(string $bytes) : string { $result = '0'; $power = '1'; for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { $index = \ord($bytes[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, '256'); } } return $result; } } PK!4݌]]BigDecimal.phpnu[value = $value; $this->scale = $scale; } /** * Creates a BigDecimal of the given value. * * @param BigNumber|int|float|string $value * * @return BigDecimal * * @throws MathException If the value cannot be converted to a BigDecimal. * * @psalm-pure */ public static function of($value) : BigNumber { return parent::of($value)->toBigDecimal(); } /** * Creates a BigDecimal from an unscaled value and a scale. * * Example: `(12345, 3)` will result in the BigDecimal `12.345`. * * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. * @param int $scale The scale of the number, positive or zero. * * @return BigDecimal * * @throws \InvalidArgumentException If the scale is negative. * * @psalm-pure */ public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('The scale cannot be negative.'); } return new BigDecimal((string) BigInteger::of($value), $scale); } /** * Returns a BigDecimal representing zero, with a scale of zero. * * @return BigDecimal * * @psalm-pure */ public static function zero() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $zero */ static $zero; if ($zero === null) { $zero = new BigDecimal('0'); } return $zero; } /** * Returns a BigDecimal representing one, with a scale of zero. * * @return BigDecimal * * @psalm-pure */ public static function one() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $one */ static $one; if ($one === null) { $one = new BigDecimal('1'); } return $one; } /** * Returns a BigDecimal representing ten, with a scale of zero. * * @return BigDecimal * * @psalm-pure */ public static function ten() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $ten */ static $ten; if ($ten === null) { $ten = new BigDecimal('10'); } return $ten; } /** * Returns the sum of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. * * @return BigDecimal The result. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function plus($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } if ($this->value === '0' && $this->scale <= $that->scale) { return $that; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->add($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the difference of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. * * @return BigDecimal The result. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function minus($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->sub($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the product of this number and the given one. * * The result has a scale of `$this->scale + $that->scale`. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. * * @return BigDecimal The result. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. */ public function multipliedBy($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '1' && $that->scale === 0) { return $this; } if ($this->value === '1' && $this->scale === 0) { return $that; } $value = Calculator::get()->mul($this->value, $that->value); $scale = $this->scale + $that->scale; return new BigDecimal($value, $scale); } /** * Returns the result of the division of this number by the given one, at the given scale. * * @param BigNumber|int|float|string $that The divisor. * @param int|null $scale The desired scale, or null to use the scale of this number. * @param int $roundingMode An optional rounding mode. * * @return BigDecimal * * @throws \InvalidArgumentException If the scale or rounding mode is invalid. * @throws MathException If the number is invalid, is zero, or rounding was necessary. */ public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } if ($scale === null) { $scale = $this->scale; } elseif ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { return $this; } $p = $this->valueWithMinScale($that->scale + $scale); $q = $that->valueWithMinScale($this->scale - $scale); $result = Calculator::get()->divRound($p, $q, $roundingMode); return new BigDecimal($result, $scale); } /** * Returns the exact result of the division of this number by the given one. * * The scale of the result is automatically calculated to fit all the fraction digits. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal The result. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, * or the result yields an infinite number of digits. */ public function exactlyDividedBy($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [, $b] = $this->scaleValues($this, $that); $d = \rtrim($b, '0'); $scale = \strlen($b) - \strlen($d); $calculator = Calculator::get(); foreach ([5, 2] as $prime) { for (;;) { $lastDigit = (int) $d[-1]; if ($lastDigit % $prime !== 0) { break; } $d = $calculator->divQ($d, (string) $prime); $scale++; } } return $this->dividedBy($that, $scale)->stripTrailingZeros(); } /** * Returns this number exponentiated to the given value. * * The result has a scale of `$this->scale * $exponent`. * * @param int $exponent The exponent. * * @return BigDecimal The result. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigDecimal { if ($exponent === 0) { return BigDecimal::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent); } /** * Returns the quotient of the division of this number by this given one. * * The quotient has a scale of `0`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal The quotient. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotient($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $quotient = Calculator::get()->divQ($p, $q); return new BigDecimal($quotient, 0); } /** * Returns the remainder of the division of this number by this given one. * * The remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal The remainder. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function remainder($that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $remainder = Calculator::get()->divR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($remainder, $scale); } /** * Returns the quotient and remainder of the division of this number by the given one. * * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal[] An array containing the quotient and the remainder. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotientAndRemainder($that) : array { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); [$quotient, $remainder] = Calculator::get()->divQR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; $quotient = new BigDecimal($quotient, 0); $remainder = new BigDecimal($remainder, $scale); return [$quotient, $remainder]; } /** * Returns the square root of this number, rounded down to the given number of decimals. * * @param int $scale * * @return BigDecimal * * @throws \InvalidArgumentException If the scale is negative. * @throws NegativeNumberException If this number is negative. */ public function sqrt(int $scale) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($this->value === '0') { return new BigDecimal('0', $scale); } if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = $this->value; $addDigits = 2 * $scale - $this->scale; if ($addDigits > 0) { // add zeros $value .= \str_repeat('0', $addDigits); } elseif ($addDigits < 0) { // trim digits if (-$addDigits >= \strlen($this->value)) { // requesting a scale too low, will always yield a zero result return new BigDecimal('0', $scale); } $value = \substr($value, 0, $addDigits); } $value = Calculator::get()->sqrt($value); return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. * * @param int $n * * @return BigDecimal */ public function withPointMovedLeft(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedRight(-$n); } return new BigDecimal($this->value, $this->scale + $n); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the right. * * @param int $n * * @return BigDecimal */ public function withPointMovedRight(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedLeft(-$n); } $value = $this->value; $scale = $this->scale - $n; if ($scale < 0) { if ($value !== '0') { $value .= \str_repeat('0', -$scale); } $scale = 0; } return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. * * @return BigDecimal */ public function stripTrailingZeros() : BigDecimal { if ($this->scale === 0) { return $this; } $trimmedValue = \rtrim($this->value, '0'); if ($trimmedValue === '') { return BigDecimal::zero(); } $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); if ($trimmableZeros === 0) { return $this; } if ($trimmableZeros > $this->scale) { $trimmableZeros = $this->scale; } $value = \substr($this->value, 0, -$trimmableZeros); $scale = $this->scale - $trimmableZeros; return new BigDecimal($value, $scale); } /** * Returns the absolute value of this number. * * @return BigDecimal */ public function abs() : BigDecimal { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the negated value of this number. * * @return BigDecimal */ public function negated() : BigDecimal { return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); } /** * {@inheritdoc} */ public function compareTo($that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { $that = $that->toBigDecimal(); } if ($that instanceof BigDecimal) { [$a, $b] = $this->scaleValues($this, $that); return Calculator::get()->cmp($a, $b); } return - $that->compareTo($this); } /** * {@inheritdoc} */ public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } /** * @return BigInteger */ public function getUnscaledValue() : BigInteger { return BigInteger::create($this->value); } /** * @return int */ public function getScale() : int { return $this->scale; } /** * Returns a string representing the integral part of this decimal number. * * Example: `-123.456` => `-123`. * * @return string */ public function getIntegralPart() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale); } /** * Returns a string representing the fractional part of this decimal number. * * If the scale is zero, an empty string is returned. * * Examples: `-123.456` => '456', `123` => ''. * * @return string */ public function getFractionalPart() : string { if ($this->scale === 0) { return ''; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, -$this->scale); } /** * Returns whether this decimal number has a non-zero fractional part. * * @return bool */ public function hasNonZeroFractionalPart() : bool { return $this->getFractionalPart() !== \str_repeat('0', $this->scale); } /** * {@inheritdoc} */ public function toBigInteger() : BigInteger { $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); return BigInteger::create($zeroScaleDecimal->value); } /** * {@inheritdoc} */ public function toBigDecimal() : BigDecimal { return $this; } /** * {@inheritdoc} */ public function toBigRational() : BigRational { $numerator = BigInteger::create($this->value); $denominator = BigInteger::create('1' . \str_repeat('0', $this->scale)); return BigRational::create($numerator, $denominator, false); } /** * {@inheritdoc} */ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { if ($scale === $this->scale) { return $this; } return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); } /** * {@inheritdoc} */ public function toInt() : int { return $this->toBigInteger()->toInt(); } /** * {@inheritdoc} */ public function toFloat() : float { return (float) (string) $this; } /** * {@inheritdoc} */ public function __toString() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string, scale: int} */ public function __serialize(): array { return ['value' => $this->value, 'scale' => $this->scale]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string, scale: int} $data * * @return void * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; $this->scale = $data['scale']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal * * @return string */ public function serialize() : string { return $this->value . ':' . $this->scale; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param string $value * * @return void * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->value)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } [$value, $scale] = \explode(':', $value); $this->value = $value; $this->scale = (int) $scale; } /** * Puts the internal values of the given decimal numbers on the same scale. * * @param BigDecimal $x The first decimal number. * @param BigDecimal $y The second decimal number. * * @return array{string, string} The scaled integer values of $x and $y. */ private function scaleValues(BigDecimal $x, BigDecimal $y) : array { $a = $x->value; $b = $y->value; if ($b !== '0' && $x->scale > $y->scale) { $b .= \str_repeat('0', $x->scale - $y->scale); } elseif ($a !== '0' && $x->scale < $y->scale) { $a .= \str_repeat('0', $y->scale - $x->scale); } return [$a, $b]; } /** * @param int $scale * * @return string */ private function valueWithMinScale(int $scale) : string { $value = $this->value; if ($this->value !== '0' && $scale > $this->scale) { $value .= \str_repeat('0', $scale - $this->scale); } return $value; } /** * Adds leading zeros if necessary to the unscaled value to represent the full decimal number. * * @return string */ private function getUnscaledValueWithLeadingZeros() : string { $value = $this->value; $targetLength = $this->scale + 1; $negative = ($value[0] === '-'); $length = \strlen($value); if ($negative) { $length--; } if ($length >= $targetLength) { return $this->value; } if ($negative) { $value = \substr($value, 1); } $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); if ($negative) { $value = '-' . $value; } return $value; } } PK!LG[px@x@ BigNumber.phpnu[[\-\+])?' . '(?:' . '(?:' . '(?[0-9]+)?' . '(?\.)?' . '(?[0-9]+)?' . '(?:[eE](?[\-\+]?[0-9]+))?' . ')|(?:' . '(?[0-9]+)' . '\/?' . '(?[0-9]+)' . ')' . ')' . '$/'; /** * Creates a BigNumber of the given value. * * The concrete return type is dependent on the given value, with the following rules: * * - BigNumber instances are returned as is * - integer numbers are returned as BigInteger * - floating point numbers are converted to a string then parsed as such * - strings containing a `/` character are returned as BigRational * - strings containing a `.` character or using an exponential notation are returned as BigDecimal * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger * * @param BigNumber|int|float|string $value * * @return BigNumber * * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @psalm-pure */ public static function of($value) : BigNumber { if ($value instanceof BigNumber) { return $value; } if (\is_int($value)) { return new BigInteger((string) $value); } /** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */ $value = \is_float($value) ? self::floatToString($value) : (string) $value; $throw = static function() use ($value) : void { throw new NumberFormatException(\sprintf( 'The given value "%s" does not represent a valid number.', $value )); }; if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { $throw(); } $getMatch = static function(string $value) use ($matches) : ?string { return isset($matches[$value]) && $matches[$value] !== '' ? $matches[$value] : null; }; $sign = $getMatch('sign'); $numerator = $getMatch('numerator'); $denominator = $getMatch('denominator'); if ($numerator !== null) { assert($denominator !== null); if ($sign !== null) { $numerator = $sign . $numerator; } $numerator = self::cleanUp($numerator); $denominator = self::cleanUp($denominator); if ($denominator === '0') { throw DivisionByZeroException::denominatorMustNotBeZero(); } return new BigRational( new BigInteger($numerator), new BigInteger($denominator), false ); } $point = $getMatch('point'); $integral = $getMatch('integral'); $fractional = $getMatch('fractional'); $exponent = $getMatch('exponent'); if ($integral === null && $fractional === null) { $throw(); } if ($integral === null) { $integral = '0'; } if ($point !== null || $exponent !== null) { $fractional = ($fractional ?? ''); $exponent = ($exponent !== null) ? (int) $exponent : 0; if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { throw new NumberFormatException('Exponent too large.'); } $unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional); $scale = \strlen($fractional) - $exponent; if ($scale < 0) { if ($unscaledValue !== '0') { $unscaledValue .= \str_repeat('0', - $scale); } $scale = 0; } return new BigDecimal($unscaledValue, $scale); } $integral = self::cleanUp(($sign ?? '') . $integral); return new BigInteger($integral); } /** * Safely converts float to string, avoiding locale-dependent issues. * * @see https://github.com/brick/math/pull/20 * * @param float $float * * @return string * * @psalm-pure * @psalm-suppress ImpureFunctionCall */ private static function floatToString(float $float) : string { $currentLocale = \setlocale(LC_NUMERIC, '0'); \setlocale(LC_NUMERIC, 'C'); $result = (string) $float; \setlocale(LC_NUMERIC, $currentLocale); return $result; } /** * Proxy method to access protected constructors from sibling classes. * * @internal * * @param mixed ...$args The arguments to the constructor. * * @return static * * @psalm-pure * @psalm-suppress TooManyArguments * @psalm-suppress UnsafeInstantiation */ protected static function create(... $args) : BigNumber { return new static(... $args); } /** * Returns the minimum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @return static The minimum value. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ public static function min(...$values) : BigNumber { $min = null; foreach ($values as $value) { $value = static::of($value); if ($min === null || $value->isLessThan($min)) { $min = $value; } } if ($min === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $min; } /** * Returns the maximum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @return static The maximum value. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ public static function max(...$values) : BigNumber { $max = null; foreach ($values as $value) { $value = static::of($value); if ($max === null || $value->isGreaterThan($max)) { $max = $value; } } if ($max === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $max; } /** * Returns the sum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible * to an instance of the class this method is called on. * * @return static The sum. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ public static function sum(...$values) : BigNumber { /** @var BigNumber|null $sum */ $sum = null; foreach ($values as $value) { $value = static::of($value); $sum = $sum === null ? $value : self::add($sum, $value); } if ($sum === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $sum; } /** * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. * * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to * concrete classes the responsibility to perform the addition themselves or delegate it to the given number, * depending on their ability to perform the operation. This will also require a version bump because we're * potentially breaking custom BigNumber implementations (if any...) * * @param BigNumber $a * @param BigNumber $b * * @return BigNumber * * @psalm-pure */ private static function add(BigNumber $a, BigNumber $b) : BigNumber { if ($a instanceof BigRational) { return $a->plus($b); } if ($b instanceof BigRational) { return $b->plus($a); } if ($a instanceof BigDecimal) { return $a->plus($b); } if ($b instanceof BigDecimal) { return $b->plus($a); } /** @var BigInteger $a */ return $a->plus($b); } /** * Removes optional leading zeros and + sign from the given number. * * @param string $number The number, validated as a non-empty string of digits with optional leading sign. * * @return string * * @psalm-pure */ private static function cleanUp(string $number) : string { $firstChar = $number[0]; if ($firstChar === '+' || $firstChar === '-') { $number = \substr($number, 1); } $number = \ltrim($number, '0'); if ($number === '') { return '0'; } if ($firstChar === '-') { return '-' . $number; } return $number; } /** * Checks if this number is equal to the given one. * * @param BigNumber|int|float|string $that * * @return bool */ public function isEqualTo($that) : bool { return $this->compareTo($that) === 0; } /** * Checks if this number is strictly lower than the given one. * * @param BigNumber|int|float|string $that * * @return bool */ public function isLessThan($that) : bool { return $this->compareTo($that) < 0; } /** * Checks if this number is lower than or equal to the given one. * * @param BigNumber|int|float|string $that * * @return bool */ public function isLessThanOrEqualTo($that) : bool { return $this->compareTo($that) <= 0; } /** * Checks if this number is strictly greater than the given one. * * @param BigNumber|int|float|string $that * * @return bool */ public function isGreaterThan($that) : bool { return $this->compareTo($that) > 0; } /** * Checks if this number is greater than or equal to the given one. * * @param BigNumber|int|float|string $that * * @return bool */ public function isGreaterThanOrEqualTo($that) : bool { return $this->compareTo($that) >= 0; } /** * Checks if this number equals zero. * * @return bool */ public function isZero() : bool { return $this->getSign() === 0; } /** * Checks if this number is strictly negative. * * @return bool */ public function isNegative() : bool { return $this->getSign() < 0; } /** * Checks if this number is negative or zero. * * @return bool */ public function isNegativeOrZero() : bool { return $this->getSign() <= 0; } /** * Checks if this number is strictly positive. * * @return bool */ public function isPositive() : bool { return $this->getSign() > 0; } /** * Checks if this number is positive or zero. * * @return bool */ public function isPositiveOrZero() : bool { return $this->getSign() >= 0; } /** * Returns the sign of this number. * * @return int -1 if the number is negative, 0 if zero, 1 if positive. */ abstract public function getSign() : int; /** * Compares this number to the given one. * * @param BigNumber|int|float|string $that * * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. * * @throws MathException If the number is not valid. */ abstract public function compareTo($that) : int; /** * Converts this number to a BigInteger. * * @return BigInteger The converted number. * * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. */ abstract public function toBigInteger() : BigInteger; /** * Converts this number to a BigDecimal. * * @return BigDecimal The converted number. * * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. */ abstract public function toBigDecimal() : BigDecimal; /** * Converts this number to a BigRational. * * @return BigRational The converted number. */ abstract public function toBigRational() : BigRational; /** * Converts this number to a BigDecimal with the given scale, using rounding if necessary. * * @param int $scale The scale of the resulting `BigDecimal`. * @param int $roundingMode A `RoundingMode` constant. * * @return BigDecimal * * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. * This only applies when RoundingMode::UNNECESSARY is used. */ abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; /** * Returns the exact value of this number as a native integer. * * If this number cannot be converted to a native integer without losing precision, an exception is thrown. * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. * * @return int The converted value. * * @throws MathException If this number cannot be exactly converted to a native integer. */ abstract public function toInt() : int; /** * Returns an approximation of this number as a floating-point value. * * Note that this method can discard information as the precision of a floating-point value * is inherently limited. * * If the number is greater than the largest representable floating point number, positive infinity is returned. * If the number is less than the smallest representable floating point number, negative infinity is returned. * * @return float The converted value. */ abstract public function toFloat() : float; /** * Returns a string representation of this number. * * The output of this method can be parsed by the `of()` factory method; * this will yield an object equal to this one, without any information loss. * * @return string */ abstract public function __toString() : string; /** * {@inheritdoc} */ public function jsonSerialize() : string { return $this->__toString(); } } PK!rwW55BigRational.phpnu[isZero()) { throw DivisionByZeroException::denominatorMustNotBeZero(); } if ($denominator->isNegative()) { $numerator = $numerator->negated(); $denominator = $denominator->negated(); } } $this->numerator = $numerator; $this->denominator = $denominator; } /** * Creates a BigRational of the given value. * * @param BigNumber|int|float|string $value * * @return BigRational * * @throws MathException If the value cannot be converted to a BigRational. * * @psalm-pure */ public static function of($value) : BigNumber { return parent::of($value)->toBigRational(); } /** * Creates a BigRational out of a numerator and a denominator. * * If the denominator is negative, the signs of both the numerator and the denominator * will be inverted to ensure that the denominator is always positive. * * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. * * @return BigRational * * @throws NumberFormatException If an argument does not represent a valid number. * @throws RoundingNecessaryException If an argument represents a non-integer number. * @throws DivisionByZeroException If the denominator is zero. * * @psalm-pure */ public static function nd($numerator, $denominator) : BigRational { $numerator = BigInteger::of($numerator); $denominator = BigInteger::of($denominator); return new BigRational($numerator, $denominator, true); } /** * Returns a BigRational representing zero. * * @return BigRational * * @psalm-pure */ public static function zero() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $zero */ static $zero; if ($zero === null) { $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false); } return $zero; } /** * Returns a BigRational representing one. * * @return BigRational * * @psalm-pure */ public static function one() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $one */ static $one; if ($one === null) { $one = new BigRational(BigInteger::one(), BigInteger::one(), false); } return $one; } /** * Returns a BigRational representing ten. * * @return BigRational * * @psalm-pure */ public static function ten() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $ten */ static $ten; if ($ten === null) { $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false); } return $ten; } /** * @return BigInteger */ public function getNumerator() : BigInteger { return $this->numerator; } /** * @return BigInteger */ public function getDenominator() : BigInteger { return $this->denominator; } /** * Returns the quotient of the division of the numerator by the denominator. * * @return BigInteger */ public function quotient() : BigInteger { return $this->numerator->quotient($this->denominator); } /** * Returns the remainder of the division of the numerator by the denominator. * * @return BigInteger */ public function remainder() : BigInteger { return $this->numerator->remainder($this->denominator); } /** * Returns the quotient and remainder of the division of the numerator by the denominator. * * @return BigInteger[] */ public function quotientAndRemainder() : array { return $this->numerator->quotientAndRemainder($this->denominator); } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. * * @return BigRational The result. * * @throws MathException If the number is not valid. */ public function plus($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. * * @return BigRational The result. * * @throws MathException If the number is not valid. */ public function minus($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. * * @return BigRational The result. * * @throws MathException If the multiplier is not a valid number. */ public function multipliedBy($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->numerator); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. * * @return BigRational The result. * * @throws MathException If the divisor is not a valid number, or is zero. */ public function dividedBy($that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $denominator = $this->denominator->multipliedBy($that->numerator); return new BigRational($numerator, $denominator, true); } /** * Returns this number exponentiated to the given value. * * @param int $exponent The exponent. * * @return BigRational The result. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigRational { if ($exponent === 0) { $one = BigInteger::one(); return new BigRational($one, $one, false); } if ($exponent === 1) { return $this; } return new BigRational( $this->numerator->power($exponent), $this->denominator->power($exponent), false ); } /** * Returns the reciprocal of this BigRational. * * The reciprocal has the numerator and denominator swapped. * * @return BigRational * * @throws DivisionByZeroException If the numerator is zero. */ public function reciprocal() : BigRational { return new BigRational($this->denominator, $this->numerator, true); } /** * Returns the absolute value of this BigRational. * * @return BigRational */ public function abs() : BigRational { return new BigRational($this->numerator->abs(), $this->denominator, false); } /** * Returns the negated value of this BigRational. * * @return BigRational */ public function negated() : BigRational { return new BigRational($this->numerator->negated(), $this->denominator, false); } /** * Returns the simplified value of this BigRational. * * @return BigRational */ public function simplified() : BigRational { $gcd = $this->numerator->gcd($this->denominator); $numerator = $this->numerator->quotient($gcd); $denominator = $this->denominator->quotient($gcd); return new BigRational($numerator, $denominator, false); } /** * {@inheritdoc} */ public function compareTo($that) : int { return $this->minus($that)->getSign(); } /** * {@inheritdoc} */ public function getSign() : int { return $this->numerator->getSign(); } /** * {@inheritdoc} */ public function toBigInteger() : BigInteger { $simplified = $this->simplified(); if (! $simplified->denominator->isEqualTo(1)) { throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.'); } return $simplified->numerator; } /** * {@inheritdoc} */ public function toBigDecimal() : BigDecimal { return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); } /** * {@inheritdoc} */ public function toBigRational() : BigRational { return $this; } /** * {@inheritdoc} */ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); } /** * {@inheritdoc} */ public function toInt() : int { return $this->toBigInteger()->toInt(); } /** * {@inheritdoc} */ public function toFloat() : float { return $this->numerator->toFloat() / $this->denominator->toFloat(); } /** * {@inheritdoc} */ public function __toString() : string { $numerator = (string) $this->numerator; $denominator = (string) $this->denominator; if ($denominator === '1') { return $numerator; } return $this->numerator . '/' . $this->denominator; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{numerator: BigInteger, denominator: BigInteger} */ public function __serialize(): array { return ['numerator' => $this->numerator, 'denominator' => $this->denominator]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{numerator: BigInteger, denominator: BigInteger} $data * * @return void * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->numerator)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->numerator = $data['numerator']; $this->denominator = $data['denominator']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal * * @return string */ public function serialize() : string { return $this->numerator . '/' . $this->denominator; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param string $value * * @return void * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->numerator)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } [$numerator, $denominator] = \explode('/', $value); $this->numerator = BigInteger::of($numerator); $this->denominator = BigInteger::of($denominator); } } PK!9Ndd&Exception/IntegerOverflowException.phpnu[ 126) { $char = \strtoupper(\dechex($ord)); if ($ord < 10) { $char = '0' . $char; } } else { $char = '"' . $char . '"'; } return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char)); } } PK!=gjj%Exception/DivisionByZeroException.phpnu[value = $value; } /** * Creates a BigInteger of the given value. * * @param BigNumber|int|float|string $value * * @return BigInteger * * @throws MathException If the value cannot be converted to a BigInteger. * * @psalm-pure */ public static function of($value) : BigNumber { return parent::of($value)->toBigInteger(); } /** * Creates a number from a string in a given base. * * The string can optionally be prefixed with the `+` or `-` sign. * * Bases greater than 36 are not supported by this method, as there is no clear consensus on which of the lowercase * or uppercase characters should come first. Instead, this method accepts any base up to 36, and does not * differentiate lowercase and uppercase characters, which are considered equal. * * For bases greater than 36, and/or custom alphabets, use the fromArbitraryBase() method. * * @param string $number The number to convert, in the given base. * @param int $base The base of the number, between 2 and 36. * * @return BigInteger * * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. * @throws \InvalidArgumentException If the base is out of range. * * @psalm-pure */ public static function fromBase(string $number, int $base) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is not in range 2 to 36.', $base)); } if ($number[0] === '-') { $sign = '-'; $number = \substr($number, 1); } elseif ($number[0] === '+') { $sign = ''; $number = \substr($number, 1); } else { $sign = ''; } if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $number = \ltrim($number, '0'); if ($number === '') { // The result will be the same in any base, avoid further calculation. return BigInteger::zero(); } if ($number === '1') { // The result will be the same in any base, avoid further calculation. return new BigInteger($sign . '1'); } $pattern = '/[^' . \substr(Calculator::ALPHABET, 0, $base) . ']/'; if (\preg_match($pattern, \strtolower($number), $matches) === 1) { throw new NumberFormatException(\sprintf('"%s" is not a valid character in base %d.', $matches[0], $base)); } if ($base === 10) { // The number is usable as is, avoid further calculation. return new BigInteger($sign . $number); } $result = Calculator::get()->fromBase($number, $base); return new BigInteger($sign . $result); } /** * Parses a string containing an integer in an arbitrary base, using a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers. * * @param string $number The number to parse. * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @return BigInteger * * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. * @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars. * * @psalm-pure */ public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } $pattern = '/[^' . \preg_quote($alphabet, '/') . ']/'; if (\preg_match($pattern, $number, $matches) === 1) { throw NumberFormatException::charNotInAlphabet($matches[0]); } $number = Calculator::get()->fromArbitraryBase($number, $alphabet, $base); return new BigInteger($number); } /** * Translates a string of bytes containing the binary representation of a BigInteger into a BigInteger. * * The input string is assumed to be in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the input is assumed to be in two's-complement representation, and the leading bit is * interpreted as a sign bit. If `$signed` is false, the input is interpreted as an unsigned number, and the * resulting BigInteger will always be positive or zero. * * This method can be used to retrieve a number exported by `toBytes()`, as long as the `$signed` flags match. * * @param string $value The byte string. * @param bool $signed Whether to interpret as a signed number in two's-complement representation with a leading * sign bit. * * @return BigInteger * * @throws NumberFormatException If the string is empty. */ public static function fromBytes(string $value, bool $signed = true) : BigInteger { if ($value === '') { throw new NumberFormatException('The byte string must not be empty.'); } $twosComplement = false; if ($signed) { $x = \ord($value[0]); if (($twosComplement = ($x >= 0x80))) { $value = ~$value; } } $number = self::fromBase(\bin2hex($value), 16); if ($twosComplement) { return $number->plus(1)->negated(); } return $number; } /** * Generates a pseudo-random number in the range 0 to 2^numBits - 1. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param callable(int): string $randomBytesGenerator * * @param int $numBits The number of bits. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a * string of random bytes of the given length. Defaults to the * `random_bytes()` function. * * @return BigInteger * * @throws \InvalidArgumentException If $numBits is negative. */ public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null) : BigInteger { if ($numBits < 0) { throw new \InvalidArgumentException('The number of bits cannot be negative.'); } if ($numBits === 0) { return BigInteger::zero(); } if ($randomBytesGenerator === null) { $randomBytesGenerator = 'random_bytes'; } $byteLength = \intdiv($numBits - 1, 8) + 1; $extraBits = ($byteLength * 8 - $numBits); $bitmask = \chr(0xFF >> $extraBits); $randomBytes = $randomBytesGenerator($byteLength); $randomBytes[0] = $randomBytes[0] & $bitmask; return self::fromBytes($randomBytes, false); } /** * Generates a pseudo-random number between `$min` and `$max`. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, * and returns a string of random bytes of the given length. * Defaults to the `random_bytes()` function. * * @return BigInteger * * @throws MathException If one of the parameters cannot be converted to a BigInteger, * or `$min` is greater than `$max`. */ public static function randomRange($min, $max, ?callable $randomBytesGenerator = null) : BigInteger { $min = BigInteger::of($min); $max = BigInteger::of($max); if ($min->isGreaterThan($max)) { throw new MathException('$min cannot be greater than $max.'); } if ($min->isEqualTo($max)) { return $min; } $diff = $max->minus($min); $bitLength = $diff->getBitLength(); // try until the number is in range (50% to 100% chance of success) do { $randomNumber = self::randomBits($bitLength, $randomBytesGenerator); } while ($randomNumber->isGreaterThan($diff)); return $randomNumber->plus($min); } /** * Returns a BigInteger representing zero. * * @return BigInteger * * @psalm-pure */ public static function zero() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $zero */ static $zero; if ($zero === null) { $zero = new BigInteger('0'); } return $zero; } /** * Returns a BigInteger representing one. * * @return BigInteger * * @psalm-pure */ public static function one() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $one */ static $one; if ($one === null) { $one = new BigInteger('1'); } return $one; } /** * Returns a BigInteger representing ten. * * @return BigInteger * * @psalm-pure */ public static function ten() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $ten */ static $ten; if ($ten === null) { $ten = new BigInteger('10'); } return $ten; } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger. * * @return BigInteger The result. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function plus($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } if ($this->value === '0') { return $that; } $value = Calculator::get()->add($this->value, $that->value); return new BigInteger($value); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger. * * @return BigInteger The result. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function minus($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } $value = Calculator::get()->sub($this->value, $that->value); return new BigInteger($value); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger. * * @return BigInteger The result. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger. */ public function multipliedBy($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($this->value === '1') { return $that; } $value = Calculator::get()->mul($this->value, $that->value); return new BigInteger($value); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * @param int $roundingMode An optional rounding mode. * * @return BigInteger The result. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero, * or RoundingMode::UNNECESSARY is used and the remainder is not zero. */ public function dividedBy($that, int $roundingMode = RoundingMode::UNNECESSARY) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $result = Calculator::get()->divRound($this->value, $that->value, $roundingMode); return new BigInteger($result); } /** * Returns this number exponentiated to the given value. * * @param int $exponent The exponent. * * @return BigInteger The result. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigInteger { if ($exponent === 0) { return BigInteger::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigInteger(Calculator::get()->pow($this->value, $exponent)); } /** * Returns the quotient of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger * * @throws DivisionByZeroException If the divisor is zero. */ public function quotient($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $quotient = Calculator::get()->divQ($this->value, $that->value); return new BigInteger($quotient); } /** * Returns the remainder of the division of this number by the given one. * * The remainder, when non-zero, has the same sign as the dividend. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger * * @throws DivisionByZeroException If the divisor is zero. */ public function remainder($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return BigInteger::zero(); } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $remainder = Calculator::get()->divR($this->value, $that->value); return new BigInteger($remainder); } /** * Returns the quotient and remainder of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger[] An array containing the quotient and the remainder. * * @throws DivisionByZeroException If the divisor is zero. */ public function quotientAndRemainder($that) : array { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [$quotient, $remainder] = Calculator::get()->divQR($this->value, $that->value); return [ new BigInteger($quotient), new BigInteger($remainder) ]; } /** * Returns the modulo of this number and the given one. * * The modulo operation yields the same result as the remainder operation when both operands are of the same sign, * and may differ when signs are different. * * The result of the modulo operation, when non-zero, has the same sign as the divisor. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger * * @throws DivisionByZeroException If the divisor is zero. */ public function mod($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } $value = Calculator::get()->mod($this->value, $that->value); return new BigInteger($value); } /** * Returns the modular multiplicative inverse of this BigInteger modulo $m. * * @param BigInteger $m * * @return BigInteger * * @throws DivisionByZeroException If $m is zero. * @throws NegativeNumberException If $m is negative. * @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger * is not relatively prime to m). */ public function modInverse(BigInteger $m) : BigInteger { if ($m->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } if ($m->isNegative()) { throw new NegativeNumberException('Modulus must not be negative.'); } if ($m->value === '1') { return BigInteger::zero(); } $value = Calculator::get()->modInverse($this->value, $m->value); if ($value === null) { throw new MathException('Unable to compute the modInverse for the given modulus.'); } return new BigInteger($value); } /** * Returns this number raised into power with modulo. * * This operation only works on positive numbers. * * @param BigNumber|int|float|string $exp The exponent. Must be positive or zero. * @param BigNumber|int|float|string $mod The modulus. Must be strictly positive. * * @return BigInteger * * @throws NegativeNumberException If any of the operands is negative. * @throws DivisionByZeroException If the modulus is zero. */ public function modPow($exp, $mod) : BigInteger { $exp = BigInteger::of($exp); $mod = BigInteger::of($mod); if ($this->isNegative() || $exp->isNegative() || $mod->isNegative()) { throw new NegativeNumberException('The operands cannot be negative.'); } if ($mod->isZero()) { throw DivisionByZeroException::modulusMustNotBeZero(); } $result = Calculator::get()->modPow($this->value, $exp->value, $mod->value); return new BigInteger($result); } /** * Returns the greatest common divisor of this number and the given one. * * The GCD is always positive, unless both operands are zero, in which case it is zero. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. * * @return BigInteger */ public function gcd($that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0' && $this->value[0] !== '-') { return $this; } if ($this->value === '0' && $that->value[0] !== '-') { return $that; } $value = Calculator::get()->gcd($this->value, $that->value); return new BigInteger($value); } /** * Returns the integer square root number of this number, rounded down. * * The result is the largest x such that x² ≤ n. * * @return BigInteger * * @throws NegativeNumberException If this number is negative. */ public function sqrt() : BigInteger { if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = Calculator::get()->sqrt($this->value); return new BigInteger($value); } /** * Returns the absolute value of this number. * * @return BigInteger */ public function abs() : BigInteger { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the inverse of this number. * * @return BigInteger */ public function negated() : BigInteger { return new BigInteger(Calculator::get()->neg($this->value)); } /** * Returns the integer bitwise-and combined with another integer. * * This method returns a negative BigInteger if and only if both operands are negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. * * @return BigInteger */ public function and($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->and($this->value, $that->value)); } /** * Returns the integer bitwise-or combined with another integer. * * This method returns a negative BigInteger if and only if either of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. * * @return BigInteger */ public function or($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->or($this->value, $that->value)); } /** * Returns the integer bitwise-xor combined with another integer. * * This method returns a negative BigInteger if and only if exactly one of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. * * @return BigInteger */ public function xor($that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->xor($this->value, $that->value)); } /** * Returns the bitwise-not of this BigInteger. * * @return BigInteger */ public function not() : BigInteger { return $this->negated()->minus(1); } /** * Returns the integer left shifted by a given number of bits. * * @param int $distance The distance to shift. * * @return BigInteger */ public function shiftedLeft(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedRight(- $distance); } return $this->multipliedBy(BigInteger::of(2)->power($distance)); } /** * Returns the integer right shifted by a given number of bits. * * @param int $distance The distance to shift. * * @return BigInteger */ public function shiftedRight(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedLeft(- $distance); } $operand = BigInteger::of(2)->power($distance); if ($this->isPositiveOrZero()) { return $this->quotient($operand); } return $this->dividedBy($operand, RoundingMode::UP); } /** * Returns the number of bits in the minimal two's-complement representation of this BigInteger, excluding a sign bit. * * For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation. * Computes (ceil(log2(this < 0 ? -this : this+1))). * * @return int */ public function getBitLength() : int { if ($this->value === '0') { return 0; } if ($this->isNegative()) { return $this->abs()->minus(1)->getBitLength(); } return \strlen($this->toBase(2)); } /** * Returns the index of the rightmost (lowest-order) one bit in this BigInteger. * * Returns -1 if this BigInteger contains no one bits. * * @return int */ public function getLowestSetBit() : int { $n = $this; $bitLength = $this->getBitLength(); for ($i = 0; $i <= $bitLength; $i++) { if ($n->isOdd()) { return $i; } $n = $n->shiftedRight(1); } return -1; } /** * Returns whether this number is even. * * @return bool */ public function isEven() : bool { return \in_array($this->value[-1], ['0', '2', '4', '6', '8'], true); } /** * Returns whether this number is odd. * * @return bool */ public function isOdd() : bool { return \in_array($this->value[-1], ['1', '3', '5', '7', '9'], true); } /** * Returns true if and only if the designated bit is set. * * Computes ((this & (1<shiftedRight($n)->isOdd(); } /** * {@inheritdoc} */ public function compareTo($that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { return Calculator::get()->cmp($this->value, $that->value); } return - $that->compareTo($this); } /** * {@inheritdoc} */ public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } /** * {@inheritdoc} */ public function toBigInteger() : BigInteger { return $this; } /** * {@inheritdoc} */ public function toBigDecimal() : BigDecimal { return BigDecimal::create($this->value); } /** * {@inheritdoc} */ public function toBigRational() : BigRational { return BigRational::create($this, BigInteger::one(), false); } /** * {@inheritdoc} */ public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->toBigDecimal()->toScale($scale, $roundingMode); } /** * {@inheritdoc} */ public function toInt() : int { $intValue = (int) $this->value; if ($this->value !== (string) $intValue) { throw IntegerOverflowException::toIntOverflow($this); } return $intValue; } /** * {@inheritdoc} */ public function toFloat() : float { return (float) $this->value; } /** * Returns a string representation of this number in the given base. * * The output will always be lowercase for bases greater than 10. * * @param int $base * * @return string * * @throws \InvalidArgumentException If the base is out of range. */ public function toBase(int $base) : string { if ($base === 10) { return $this->value; } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base)); } return Calculator::get()->toBase($this->value, $base); } /** * Returns a string representation of this number in an arbitrary base with a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers; * a NegativeNumberException will be thrown when attempting to call this method on a negative number. * * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @return string * * @throws NegativeNumberException If this number is negative. * @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars. */ public function toArbitraryBase(string $alphabet) : string { $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } if ($this->value[0] === '-') { throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.'); } return Calculator::get()->toArbitraryBase($this->value, $alphabet, $base); } /** * Returns a string of bytes containing the binary representation of this BigInteger. * * The string is in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the output will be in two's-complement representation, and a sign bit will be prepended to * the output. If `$signed` is false, no sign bit will be prepended, and this method will throw an exception if the * number is negative. * * The string will contain the minimum number of bytes required to represent this BigInteger, including a sign bit * if `$signed` is true. * * This representation is compatible with the `fromBytes()` factory method, as long as the `$signed` flags match. * * @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit. * * @return string * * @throws NegativeNumberException If $signed is false, and the number is negative. */ public function toBytes(bool $signed = true) : string { if (! $signed && $this->isNegative()) { throw new NegativeNumberException('Cannot convert a negative number to a byte string when $signed is false.'); } $hex = $this->abs()->toBase(16); if (\strlen($hex) % 2 !== 0) { $hex = '0' . $hex; } $baseHexLength = \strlen($hex); if ($signed) { if ($this->isNegative()) { $bin = \hex2bin($hex); assert($bin !== false); $hex = \bin2hex(~$bin); $hex = self::fromBase($hex, 16)->plus(1)->toBase(16); $hexLength = \strlen($hex); if ($hexLength < $baseHexLength) { $hex = \str_repeat('0', $baseHexLength - $hexLength) . $hex; } if ($hex[0] < '8') { $hex = 'FF' . $hex; } } else { if ($hex[0] >= '8') { $hex = '00' . $hex; } } } return \hex2bin($hex); } /** * {@inheritdoc} */ public function __toString() : string { return $this->value; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string} */ public function __serialize(): array { return ['value' => $this->value]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string} $data * * @return void * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal * * @return string */ public function serialize() : string { return $this->value; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param string $value * * @return void * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->value)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } $this->value = $value; } } PK!SRoundingMode.phpnu[= 0.5; otherwise, behaves as for DOWN. * Note that this is the rounding mode commonly taught at school. */ public const HALF_UP = 5; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. * * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN. */ public const HALF_DOWN = 6; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity. * * If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN. */ public const HALF_CEILING = 7; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity. * * If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP. */ public const HALF_FLOOR = 8; /** * Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor. * * Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd; * behaves as for HALF_DOWN if it's even. * * Note that this is the rounding mode that statistically minimizes * cumulative error when applied repeatedly over a sequence of calculations. * It is sometimes known as "Banker's rounding", and is chiefly used in the USA. */ public const HALF_EVEN = 9; } PK!' Parser/Entry.phpnu[name = $name; $this->value = $value; } /** * Get the entry name. * * @return string */ public function getName() { return $this->name; } /** * Get the entry value. * * @return \PhpOption\Option<\Dotenv\Parser\Value> */ public function getValue() { /** @var \PhpOption\Option<\Dotenv\Parser\Value> */ return Option::fromValue($this->value); } } PK!*/NNParser/ParserInterface.phpnu[ */ public static function lex(string $content) { static $regex; if ($regex === null) { $regex = '(('.\implode(')|(', self::PATTERNS).'))A'; } $offset = 0; while (isset($content[$offset])) { if (!\preg_match($regex, $content, $matches, 0, $offset)) { throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset])); } $offset += \strlen($matches[0]); yield $matches[0]; } } } PK!Ղ% Parser/Lines.phpnu[map(static function () use ($line) { return self::looksLikeMultilineStop($line, true) === false; })->getOrElse(false); } /** * Determine if the given line can be the start of a multiline variable. * * @param string $line * @param bool $started * * @return bool */ private static function looksLikeMultilineStop(string $line, bool $started) { if ($line === '"') { return true; } return Regex::occurences('/(?=([^\\\\]"))/', \str_replace('\\\\', '', $line))->map(static function (int $count) use ($started) { return $started ? $count > 1 : $count >= 1; })->success()->getOrElse(false); } /** * Determine if the line in the file is a comment or whitespace. * * @param string $line * * @return bool */ private static function isCommentOrWhitespace(string $line) { $line = \trim($line); return $line === '' || (isset($line[0]) && $line[0] === '#'); } } PK!)o)\.\.Parser/EntryParser.phpnu[ */ public static function parse(string $entry) { return self::splitStringIntoParts($entry)->flatMap(static function (array $parts) { [$name, $value] = $parts; return self::parseName($name)->flatMap(static function (string $name) use ($value) { /** @var Result */ $parsedValue = $value === null ? Success::create(null) : self::parseValue($value); return $parsedValue->map(static function (?Value $value) use ($name) { return new Entry($name, $value); }); }); }); } /** * Split the compound string into parts. * * @param string $line * * @return \GrahamCampbell\ResultType\Result */ private static function splitStringIntoParts(string $line) { /** @var array{string,string|null} */ $result = Str::pos($line, '=')->map(static function () use ($line) { return \array_map('trim', \explode('=', $line, 2)); })->getOrElse([$line, null]); if ($result[0] === '') { return Error::create(self::getErrorMessage('an unexpected equals', $line)); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($result); } /** * Parse the given variable name. * * That is, strip the optional quotes and leading "export" from the * variable name. We wrap the answer in a result type. * * @param string $name * * @return \GrahamCampbell\ResultType\Result */ private static function parseName(string $name) { if (Str::len($name) > 8 && Str::substr($name, 0, 6) === 'export' && \ctype_space(Str::substr($name, 6, 1))) { $name = \ltrim(Str::substr($name, 6)); } if (self::isQuotedName($name)) { $name = Str::substr($name, 1, -1); } if (!self::isValidName($name)) { return Error::create(self::getErrorMessage('an invalid name', $name)); } return Success::create($name); } /** * Is the given variable name quoted? * * @param string $name * * @return bool */ private static function isQuotedName(string $name) { if (Str::len($name) < 3) { return false; } $first = Str::substr($name, 0, 1); $last = Str::substr($name, -1, 1); return ($first === '"' && $last === '"') || ($first === '\'' && $last === '\''); } /** * Is the given variable name valid? * * @param string $name * * @return bool */ private static function isValidName(string $name) { return Regex::matches('~\A[a-zA-Z0-9_.]+\z~', $name)->success()->getOrElse(false); } /** * Parse the given variable value. * * This has the effect of stripping quotes and comments, dealing with * special characters, and locating nested variables, but not resolving * them. Formally, we run a finite state automaton with an output tape: a * transducer. We wrap the answer in a result type. * * @param string $value * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Value,string> */ private static function parseValue(string $value) { if (\trim($value) === '') { return Success::create(Value::blank()); } return \array_reduce(\iterator_to_array(Lexer::lex($value)), static function (Result $data, string $token) { return $data->flatMap(static function (array $data) use ($token) { return self::processToken($data[1], $token)->map(static function (array $val) use ($data) { return [$data[0]->append($val[0], $val[1]), $val[2]]; }); }); }, Success::create([Value::blank(), self::INITIAL_STATE]))->flatMap(static function (array $result) { if (in_array($result[1], self::REJECT_STATES, true)) { return Error::create('a missing closing quote'); } return Success::create($result[0]); })->mapError(static function (string $err) use ($value) { return self::getErrorMessage($err, $value); }); } /** * Process the given token. * * @param int $state * @param string $token * * @return \GrahamCampbell\ResultType\Result */ private static function processToken(int $state, string $token) { switch ($state) { case self::INITIAL_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::SINGLE_QUOTED_STATE]); } elseif ($token === '"') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::UNQUOTED_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::UNQUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::UNQUOTED_STATE]); } case self::SINGLE_QUOTED_STATE: if ($token === '\'') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::SINGLE_QUOTED_STATE]); } case self::DOUBLE_QUOTED_STATE: if ($token === '"') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } elseif ($token === '\\') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::ESCAPE_SEQUENCE_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, true, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } case self::ESCAPE_SEQUENCE_STATE: if ($token === '"' || $token === '\\') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } elseif ($token === '$') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([$token, false, self::DOUBLE_QUOTED_STATE]); } else { $first = Str::substr($token, 0, 1); if (\in_array($first, ['f', 'n', 'r', 't', 'v'], true)) { /** @var \GrahamCampbell\ResultType\Result */ return Success::create([\stripcslashes('\\'.$first).Str::substr($token, 1), false, self::DOUBLE_QUOTED_STATE]); } else { /** @var \GrahamCampbell\ResultType\Result */ return Error::create('an unexpected escape sequence'); } } case self::WHITESPACE_STATE: if ($token === '#') { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); } elseif (!\ctype_space($token)) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create('unexpected whitespace'); } else { /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::WHITESPACE_STATE]); } case self::COMMENT_STATE: /** @var \GrahamCampbell\ResultType\Result */ return Success::create(['', false, self::COMMENT_STATE]); default: throw new \Error('Parser entered invalid state.'); } } /** * Generate a friendly error message. * * @param string $cause * @param string $subject * * @return string */ private static function getErrorMessage(string $cause, string $subject) { return \sprintf( 'Encountered %s at [%s].', $cause, \strtok($subject, "\n") ); } } PK!j> 44Parser/Value.phpnu[chars = $chars; $this->vars = $vars; } /** * Create an empty value instance. * * @return \Dotenv\Parser\Value */ public static function blank() { return new self('', []); } /** * Create a new value instance, appending the characters. * * @param string $chars * @param bool $var * * @return \Dotenv\Parser\Value */ public function append(string $chars, bool $var) { return new self( $this->chars.$chars, $var ? \array_merge($this->vars, [Str::len($this->chars)]) : $this->vars ); } /** * Get the string representation of the parsed value. * * @return string */ public function getChars() { return $this->chars; } /** * Get the locations of the variables in the value. * * @return int[] */ public function getVars() { $vars = $this->vars; \rsort($vars); return $vars; } } PK!Parser/Parser.phpnu[mapError(static function () { return 'Could not split into separate lines.'; })->flatMap(static function (array $lines) { return self::process(Lines::process($lines)); })->mapError(static function (string $error) { throw new InvalidFileException(\sprintf('Failed to parse dotenv file. %s', $error)); })->success()->get(); } /** * Convert the raw entries into proper entries. * * @param string[] $entries * * @return \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> */ private static function process(array $entries) { /** @var \GrahamCampbell\ResultType\Result<\Dotenv\Parser\Entry[],string> */ return \array_reduce($entries, static function (Result $result, string $raw) { return $result->flatMap(static function (array $entries) use ($raw) { return EntryParser::parse($raw)->map(static function (Entry $entry) use ($entries) { return \array_merge($entries, [$entry]); }); }); }, Success::create([])); } } PK!% Repository/AdapterRepository.phpnu[reader = $reader; $this->writer = $writer; } /** * Determine if the given environment variable is defined. * * @param string $name * * @return bool */ public function has(string $name) { return $this->reader->read($name)->isDefined(); } /** * Get an environment variable. * * @param string $name * * @return string|null */ public function get(string $name) { return $this->reader->read($name)->getOrElse(null); } /** * Set an environment variable. * * @param string $name * @param string $value * * @return bool */ public function set(string $name, string $value) { return $this->writer->write($name, $value); } /** * Clear an environment variable. * * @param string $name * * @return bool */ public function clear(string $name) { return $this->writer->delete($name); } } PK!"Repository/RepositoryInterface.phpnu[ */ public static function create(); } PK!k""&Repository/Adapter/ReaderInterface.phpnu[ */ public function read(string $name); } PK!==$Repository/Adapter/PutenvAdapter.phpnu[ */ public static function create() { if (self::isSupported()) { /** @var \PhpOption\Option */ return Some::create(new self()); } return None::create(); } /** * Determines if the adapter is supported. * * @return bool */ private static function isSupported() { return \function_exists('getenv') && \function_exists('putenv'); } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromValue(\getenv($name), false)->filter(static function ($value) { return \is_string($value); }); } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { \putenv("$name=$value"); return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { \putenv($name); return true; } } PK!ƛK--)Repository/Adapter/ServerConstAdapter.phpnu[ */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromArraysValue($_SERVER, $name) ->map(static function ($value) { if ($value === false) { return 'false'; } if ($value === true) { return 'true'; } return $value; })->filter(static function ($value) { return \is_string($value); }); } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $_SERVER[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { unset($_SERVER[$name]); return true; } } PK!qE-++$Repository/Adapter/GuardedWriter.phpnu[writer = $writer; $this->allowList = $allowList; } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { // Don't set non-allowed variables if (!$this->isAllowed($name)) { return false; } // Set the value on the inner writer return $this->writer->write($name, $value); } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { // Don't clear non-allowed variables if (!$this->isAllowed($name)) { return false; } // Set the value on the inner writer return $this->writer->delete($name); } /** * Determine if the given variable is allowed. * * @param string $name * * @return bool */ private function isAllowed(string $name) { return \in_array($name, $this->allowList, true); } } PK!G0a &Repository/Adapter/ImmutableWriter.phpnu[ */ private $loaded; /** * Create a new immutable writer instance. * * @param \Dotenv\Repository\Adapter\WriterInterface $writer * @param \Dotenv\Repository\Adapter\ReaderInterface $reader * * @return void */ public function __construct(WriterInterface $writer, ReaderInterface $reader) { $this->writer = $writer; $this->reader = $reader; $this->loaded = []; } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { // Don't overwrite existing environment variables // Ruby's dotenv does this with `ENV[key] ||= value` if ($this->isExternallyDefined($name)) { return false; } // Set the value on the inner writer if (!$this->writer->write($name, $value)) { return false; } // Record that we have loaded the variable $this->loaded[$name] = ''; return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { // Don't clear existing environment variables if ($this->isExternallyDefined($name)) { return false; } // Clear the value on the inner writer if (!$this->writer->delete($name)) { return false; } // Leave the variable as fair game unset($this->loaded[$name]); return true; } /** * Determine if the given variable is externally defined. * * That is, is it an "existing" variable. * * @param string $name * * @return bool */ private function isExternallyDefined(string $name) { return $this->reader->read($name)->isDefined() && !isset($this->loaded[$name]); } } PK!}&Repository/Adapter/EnvConstAdapter.phpnu[ */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromArraysValue($_ENV, $name) ->map(static function ($value) { if ($value === false) { return 'false'; } if ($value === true) { return 'true'; } return $value; })->filter(static function ($value) { return \is_string($value); }); } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $_ENV[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { unset($_ENV[$name]); return true; } } PK!DuJ$Repository/Adapter/ApacheAdapter.phpnu[ */ public static function create() { if (self::isSupported()) { /** @var \PhpOption\Option */ return Some::create(new self()); } return None::create(); } /** * Determines if the adapter is supported. * * This happens if PHP is running as an Apache module. * * @return bool */ private static function isSupported() { return \function_exists('apache_getenv') && \function_exists('apache_setenv'); } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { /** @var \PhpOption\Option */ return Option::fromValue(apache_getenv($name))->filter(static function ($value) { return \is_string($value) && $value !== ''; }); } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { return apache_setenv($name, $value); } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { return apache_setenv($name, ''); } } PK!ؖ""#Repository/Adapter/ArrayAdapter.phpnu[ */ private $variables; /** * Create a new array adapter instance. * * @return void */ private function __construct() { $this->variables = []; } /** * Create a new instance of the adapter, if it is available. * * @return \PhpOption\Option<\Dotenv\Repository\Adapter\AdapterInterface> */ public static function create() { /** @var \PhpOption\Option */ return Some::create(new self()); } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { return Option::fromArraysValue($this->variables, $name); } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { $this->variables[$name] = $value; return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { unset($this->variables[$name]); return true; } } PK!n"Repository/Adapter/MultiWriter.phpnu[writers = $writers; } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { foreach ($this->writers as $writers) { if (!$writers->write($name, $value)) { return false; } } return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { foreach ($this->writers as $writers) { if (!$writers->delete($name)) { return false; } } return true; } } PK!9ݿ"Repository/Adapter/MultiReader.phpnu[readers = $readers; } /** * Read an environment variable, if it exists. * * @param string $name * * @return \PhpOption\Option */ public function read(string $name) { foreach ($this->readers as $reader) { $result = $reader->read($name); if ($result->isDefined()) { return $result; } } return None::create(); } } PK!{{&Repository/Adapter/ReplacingWriter.phpnu[ */ private $seen; /** * Create a new replacement writer instance. * * @param \Dotenv\Repository\Adapter\WriterInterface $writer * @param \Dotenv\Repository\Adapter\ReaderInterface $reader * * @return void */ public function __construct(WriterInterface $writer, ReaderInterface $reader) { $this->writer = $writer; $this->reader = $reader; $this->seen = []; } /** * Write to an environment variable, if possible. * * @param string $name * @param string $value * * @return bool */ public function write(string $name, string $value) { if ($this->exists($name)) { return $this->writer->write($name, $value); } // succeed if nothing to do return true; } /** * Delete an environment variable, if possible. * * @param string $name * * @return bool */ public function delete(string $name) { if ($this->exists($name)) { return $this->writer->delete($name); } // succeed if nothing to do return true; } /** * Does the given environment variable exist. * * Returns true if it currently exists, or existed at any point in the past * that we are aware of. * * @param string $name * * @return bool */ private function exists(string $name) { if (isset($this->seen[$name])) { return true; } if ($this->reader->read($name)->isDefined()) { $this->seen[$name] = ''; return true; } return false; } } PK!h{.&Repository/Adapter/WriterInterface.phpnu[readers = $readers; $this->writers = $writers; $this->immutable = $immutable; $this->allowList = $allowList; } /** * Create a new repository builder instance with no adapters added. * * @return \Dotenv\Repository\RepositoryBuilder */ public static function createWithNoAdapters() { return new self(); } /** * Create a new repository builder instance with the default adapters added. * * @return \Dotenv\Repository\RepositoryBuilder */ public static function createWithDefaultAdapters() { $adapters = \iterator_to_array(self::defaultAdapters()); return new self($adapters, $adapters); } /** * Return the array of default adapters. * * @return \Generator<\Dotenv\Repository\Adapter\AdapterInterface> */ private static function defaultAdapters() { foreach (self::DEFAULT_ADAPTERS as $adapter) { $instance = $adapter::create(); if ($instance->isDefined()) { yield $instance->get(); } } } /** * Determine if the given name if of an adapaterclass. * * @param string $name * * @return bool */ private static function isAnAdapterClass(string $name) { if (!\class_exists($name)) { return false; } return (new ReflectionClass($name))->implementsInterface(AdapterInterface::class); } /** * Creates a repository builder with the given reader added. * * Accepts either a reader instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. * * @param \Dotenv\Repository\Adapter\ReaderInterface|string $reader * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addReader($reader) { if (!(\is_string($reader) && self::isAnAdapterClass($reader)) && !($reader instanceof ReaderInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', ReaderInterface::class, AdapterInterface::class ) ); } $optional = Some::create($reader)->flatMap(static function ($reader) { return \is_string($reader) ? $reader::create() : Some::create($reader); }); $readers = \array_merge($this->readers, \iterator_to_array($optional)); return new self($readers, $this->writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with the given writer added. * * Accepts either a writer instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. * * @param \Dotenv\Repository\Adapter\WriterInterface|string $writer * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addWriter($writer) { if (!(\is_string($writer) && self::isAnAdapterClass($writer)) && !($writer instanceof WriterInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', WriterInterface::class, AdapterInterface::class ) ); } $optional = Some::create($writer)->flatMap(static function ($writer) { return \is_string($writer) ? $writer::create() : Some::create($writer); }); $writers = \array_merge($this->writers, \iterator_to_array($optional)); return new self($this->readers, $writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with the given adapter added. * * Accepts either an adapter instance, or a class-string for an adapter. If * the adapter is not supported, then we silently skip adding it. We will * add the adapter as both a reader and a writer. * * @param \Dotenv\Repository\Adapter\WriterInterface|string $adapter * * @throws \InvalidArgumentException * * @return \Dotenv\Repository\RepositoryBuilder */ public function addAdapter($adapter) { if (!(\is_string($adapter) && self::isAnAdapterClass($adapter)) && !($adapter instanceof AdapterInterface)) { throw new InvalidArgumentException( \sprintf( 'Expected either an instance of %s or a class-string implementing %s', WriterInterface::class, AdapterInterface::class ) ); } $optional = Some::create($adapter)->flatMap(static function ($adapter) { return \is_string($adapter) ? $adapter::create() : Some::create($adapter); }); $readers = \array_merge($this->readers, \iterator_to_array($optional)); $writers = \array_merge($this->writers, \iterator_to_array($optional)); return new self($readers, $writers, $this->immutable, $this->allowList); } /** * Creates a repository builder with mutability enabled. * * @return \Dotenv\Repository\RepositoryBuilder */ public function immutable() { return new self($this->readers, $this->writers, true, $this->allowList); } /** * Creates a repository builder with the given allow list. * * @param string[]|null $allowList * * @return \Dotenv\Repository\RepositoryBuilder */ public function allowList(array $allowList = null) { return new self($this->readers, $this->writers, $this->immutable, $allowList); } /** * Creates a new repository instance. * * @return \Dotenv\Repository\RepositoryInterface */ public function make() { $reader = new MultiReader($this->readers); $writer = new MultiWriter($this->writers); if ($this->immutable) { $writer = new ImmutableWriter($writer, $reader); } if ($this->allowList !== null) { $writer = new GuardedWriter($writer, $this->allowList); } return new AdapterRepository($reader, $writer); } } PK!{ { Util/Regex.phpnu[ */ public static function matches(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { return @\preg_match($pattern, $subject) === 1; }, $subject); } /** * Perform a preg match all, wrapping up the result. * * @param string $pattern * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ public static function occurences(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { return (int) @\preg_match_all($pattern, $subject); }, $subject); } /** * Perform a preg replace callback, wrapping up the result. * * @param string $pattern * @param callable $callback * @param string $subject * @param int|null $limit * * @return \GrahamCampbell\ResultType\Result */ public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = null) { return self::pregAndWrap(static function (string $subject) use ($pattern, $callback, $limit) { return (string) @\preg_replace_callback($pattern, $callback, $subject, $limit ?? -1); }, $subject); } /** * Perform a preg split, wrapping up the result. * * @param string $pattern * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ public static function split(string $pattern, string $subject) { return self::pregAndWrap(static function (string $subject) use ($pattern) { /** @var string[] */ return (array) @\preg_split($pattern, $subject); }, $subject); } /** * Perform a preg operation, wrapping up the result. * * @template V * * @param callable(string):V $operation * @param string $subject * * @return \GrahamCampbell\ResultType\Result */ private static function pregAndWrap(callable $operation, string $subject) { $result = $operation($subject); if (\preg_last_error() !== \PREG_NO_ERROR) { return Error::create(\preg_last_error_msg()); } return Success::create($result); } } PK!j Util/Str.phpnu[ */ public static function utf8(string $input, string $encoding = null) { if ($encoding !== null && !\in_array($encoding, \mb_list_encodings(), true)) { /** @var \GrahamCampbell\ResultType\Result */ return Error::create( \sprintf('Illegal character encoding [%s] specified.', $encoding) ); } $converted = $encoding === null ? @\mb_convert_encoding($input, 'UTF-8') : @\mb_convert_encoding($input, 'UTF-8', $encoding); /** * this is for support UTF-8 with BOM encoding * @see https://en.wikipedia.org/wiki/Byte_order_mark * @see https://github.com/vlucas/phpdotenv/issues/500 */ if (\substr($converted, 0, 3) == "\xEF\xBB\xBF") { $converted = \substr($converted, 3); } /** @var \GrahamCampbell\ResultType\Result */ return Success::create($converted); } /** * Search for a given substring of the input. * * @param string $haystack * @param string $needle * * @return \PhpOption\Option */ public static function pos(string $haystack, string $needle) { /** @var \PhpOption\Option */ return Option::fromValue(\mb_strpos($haystack, $needle, 0, 'UTF-8'), false); } /** * Grab the specified substring of the input. * * @param string $input * @param int $start * @param int|null $length * * @return string */ public static function substr(string $input, int $start, int $length = null) { return \mb_substr($input, $start, $length, 'UTF-8'); } /** * Compute the length of the given string. * * @param string $input * * @return int */ public static function len(string $input) { return \mb_strlen($input, 'UTF-8'); } } PK!"Exception/InvalidFileException.phpnu[repository = $repository; $this->variables = $variables; } /** * Assert that each variable is present. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function required() { return $this->assert( static function (?string $value) { return $value !== null; }, 'is missing' ); } /** * Assert that each variable is not empty. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function notEmpty() { return $this->assertNullable( static function (string $value) { return Str::len(\trim($value)) > 0; }, 'is empty' ); } /** * Assert that each specified variable is an integer. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function isInteger() { return $this->assertNullable( static function (string $value) { return \ctype_digit($value); }, 'is not an integer' ); } /** * Assert that each specified variable is a boolean. * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function isBoolean() { return $this->assertNullable( static function (string $value) { if ($value === '') { return false; } return \filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) !== null; }, 'is not a boolean' ); } /** * Assert that each variable is amongst the given choices. * * @param string[] $choices * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function allowedValues(array $choices) { return $this->assertNullable( static function (string $value) use ($choices) { return \in_array($value, $choices, true); }, \sprintf('is not one of [%s]', \implode(', ', $choices)) ); } /** * Assert that each variable matches the given regular expression. * * @param string $regex * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function allowedRegexValues(string $regex) { return $this->assertNullable( static function (string $value) use ($regex) { return Regex::matches($regex, $value)->success()->getOrElse(false); }, \sprintf('does not match "%s"', $regex) ); } /** * Assert that the callback returns true for each variable. * * @param callable(?string):bool $callback * @param string $message * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function assert(callable $callback, string $message) { $failing = []; foreach ($this->variables as $variable) { if ($callback($this->repository->get($variable)) === false) { $failing[] = \sprintf('%s %s', $variable, $message); } } if (\count($failing) > 0) { throw new ValidationException(\sprintf( 'One or more environment variables failed assertions: %s.', \implode(', ', $failing) )); } return $this; } /** * Assert that the callback returns true for each variable. * * Skip checking null variable values. * * @param callable(string):bool $callback * @param string $message * * @throws \Dotenv\Exception\ValidationException * * @return \Dotenv\Validator */ public function assertNullable(callable $callback, string $message) { return $this->assert( static function (?string $value) use ($callback) { if ($value === null) { return true; } return $callback($value); }, $message ); } } PK!=mLoader/LoaderInterface.phpnu[ */ public function load(RepositoryInterface $repository, array $entries); } PK!͐1Loader/Resolver.phpnu[getVars(), static function (string $s, int $i) use ($repository) { return Str::substr($s, 0, $i).self::resolveVariable($repository, Str::substr($s, $i)); }, $value->getChars()); } /** * Resolve a single nested variable. * * @param \Dotenv\Repository\RepositoryInterface $repository * @param string $str * * @return string */ private static function resolveVariable(RepositoryInterface $repository, string $str) { return Regex::replaceCallback( '/\A\${([a-zA-Z0-9_.]+)}/', static function (array $matches) use ($repository) { return Option::fromValue($repository->get($matches[1])) ->getOrElse($matches[0]); }, $str, 1 )->success()->getOrElse($str); } } PK!jLoader/Loader.phpnu[ */ public function load(RepositoryInterface $repository, array $entries) { return \array_reduce($entries, static function (array $vars, Entry $entry) use ($repository) { $name = $entry->getName(); $value = $entry->getValue()->map(static function (Value $value) use ($repository) { return Resolver::resolve($repository, $value); }); if ($value->isDefined()) { $inner = $value->get(); if ($repository->set($name, $inner)) { return \array_merge($vars, [$name => $inner]); } } else { if ($repository->clear($name)) { return \array_merge($vars, [$name => null]); } } return $vars; }, []); } } PK!#dB B Dotenv.phpnu[store = $store; $this->parser = $parser; $this->loader = $loader; $this->repository = $repository; } /** * Create a new dotenv instance. * * @param \Dotenv\Repository\RepositoryInterface $repository * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames(); foreach ((array) $paths as $path) { $builder = $builder->addPath($path); } foreach ((array) $names as $name) { $builder = $builder->addName($name); } if ($shortCircuit) { $builder = $builder->shortCircuit(); } return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository); } /** * Create a new mutable dotenv instance with default repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters()->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new mutable dotenv instance with default repository with the putenv adapter. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createUnsafeMutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters() ->addAdapter(PutenvAdapter::class) ->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new immutable dotenv instance with default repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new immutable dotenv instance with default repository with the putenv adapter. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createUnsafeImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithDefaultAdapters() ->addAdapter(PutenvAdapter::class) ->immutable() ->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Create a new dotenv instance with an array backed repository. * * @param string|string[] $paths * @param string|string[]|null $names * @param bool $shortCircuit * @param string|null $fileEncoding * * @return \Dotenv\Dotenv */ public static function createArrayBacked($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null) { $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding); } /** * Parse the given content and resolve nested variables. * * This method behaves just like load(), only without mutating your actual * environment. We do this by using an array backed repository. * * @param string $content * * @throws \Dotenv\Exception\InvalidFileException * * @return array */ public static function parse(string $content) { $repository = RepositoryBuilder::createWithNoAdapters()->addAdapter(ArrayAdapter::class)->make(); $phpdotenv = new self(new StringStore($content), new Parser(), new Loader(), $repository); return $phpdotenv->load(); } /** * Read and load environment file(s). * * @throws \Dotenv\Exception\InvalidPathException|\Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException * * @return array */ public function load() { $entries = $this->parser->parse($this->store->read()); return $this->loader->load($this->repository, $entries); } /** * Read and load environment file(s), silently failing if no files can be read. * * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidFileException * * @return array */ public function safeLoad() { try { return $this->load(); } catch (InvalidPathException $e) { // suppressing exception return []; } } /** * Required ensures that the specified variables exist, and returns a new validator object. * * @param string|string[] $variables * * @return \Dotenv\Validator */ public function required($variables) { return (new Validator($this->repository, (array) $variables))->required(); } /** * Returns a new validator object that won't check if the specified variables exist. * * @param string|string[] $variables * * @return \Dotenv\Validator */ public function ifPresent($variables) { return new Validator($this->repository, (array) $variables); } } PK!bx5Store/File/Paths.phpnu[ */ public static function read(array $filePaths, bool $shortCircuit = true, string $fileEncoding = null) { $output = []; foreach ($filePaths as $filePath) { $content = self::readFromFile($filePath, $fileEncoding); if ($content->isDefined()) { $output[$filePath] = $content->get(); if ($shortCircuit) { break; } } } return $output; } /** * Read the given file. * * @param string $path * @param string|null $encoding * * @throws \Dotenv\Exception\InvalidEncodingException * * @return \PhpOption\Option */ private static function readFromFile(string $path, string $encoding = null) { /** @var Option */ $content = Option::fromValue(@\file_get_contents($path), false); return $content->flatMap(static function (string $content) use ($encoding) { return Str::utf8($content, $encoding)->mapError(static function (string $error) { throw new InvalidEncodingException($error); })->success(); }); } } PK!׾kb b Store/StoreBuilder.phpnu[paths = $paths; $this->names = $names; $this->shortCircuit = $shortCircuit; $this->fileEncoding = $fileEncoding; } /** * Create a new store builder instance with no names. * * @return \Dotenv\Store\StoreBuilder */ public static function createWithNoNames() { return new self(); } /** * Create a new store builder instance with the default name. * * @return \Dotenv\Store\StoreBuilder */ public static function createWithDefaultName() { return new self([], [self::DEFAULT_NAME]); } /** * Creates a store builder with the given path added. * * @param string $path * * @return \Dotenv\Store\StoreBuilder */ public function addPath(string $path) { return new self(\array_merge($this->paths, [$path]), $this->names, $this->shortCircuit, $this->fileEncoding); } /** * Creates a store builder with the given name added. * * @param string $name * * @return \Dotenv\Store\StoreBuilder */ public function addName(string $name) { return new self($this->paths, \array_merge($this->names, [$name]), $this->shortCircuit, $this->fileEncoding); } /** * Creates a store builder with short circuit mode enabled. * * @return \Dotenv\Store\StoreBuilder */ public function shortCircuit() { return new self($this->paths, $this->names, true, $this->fileEncoding); } /** * Creates a store builder with the specified file encoding. * * @param string|null $fileEncoding * * @return \Dotenv\Store\StoreBuilder */ public function fileEncoding(string $fileEncoding = null) { return new self($this->paths, $this->names, $this->shortCircuit, $fileEncoding); } /** * Creates a new store instance. * * @return \Dotenv\Store\StoreInterface */ public function make() { return new FileStore( Paths::filePaths($this->paths, $this->names), $this->shortCircuit, $this->fileEncoding ); } } PK!o_Store/FileStore.phpnu[filePaths = $filePaths; $this->shortCircuit = $shortCircuit; $this->fileEncoding = $fileEncoding; } /** * Read the content of the environment file(s). * * @throws \Dotenv\Exception\InvalidEncodingException|\Dotenv\Exception\InvalidPathException * * @return string */ public function read() { if ($this->filePaths === []) { throw new InvalidPathException('At least one environment file path must be provided.'); } $contents = Reader::read($this->filePaths, $this->shortCircuit, $this->fileEncoding); if (\count($contents) > 0) { return \implode("\n", $contents); } throw new InvalidPathException( \sprintf('Unable to read any of the environment file(s) at [%s].', \implode(', ', $this->filePaths)) ); } } PK!I<<Store/StoreInterface.phpnu[content = $content; } /** * Read the content of the environment file(s). * * @return string */ public function read() { return $this->content; } } PK! PhpOption/None.phpnu[ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace PhpOption; use EmptyIterator; /** * @extends Option */ final class None extends Option { /** @var None|null */ private static $instance; /** * @return None */ public static function create(): self { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } public function get() { throw new \RuntimeException('None has no value.'); } public function getOrCall($callable) { return $callable(); } public function getOrElse($default) { return $default; } public function getOrThrow(\Exception $ex) { throw $ex; } public function isEmpty(): bool { return true; } public function isDefined(): bool { return false; } public function orElse(Option $else) { return $else; } public function ifDefined($callable) { // Just do nothing in that case. } public function forAll($callable) { return $this; } public function map($callable) { return $this; } public function flatMap($callable) { return $this; } public function filter($callable) { return $this; } public function filterNot($callable) { return $this; } public function select($value) { return $this; } public function reject($value) { return $this; } public function getIterator(): EmptyIterator { return new EmptyIterator(); } public function foldLeft($initialValue, $callable) { return $initialValue; } public function foldRight($initialValue, $callable) { return $initialValue; } private function __construct() { } } PK!˶ PhpOption/Some.phpnu[ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace PhpOption; use ArrayIterator; /** * @template T * * @extends Option */ final class Some extends Option { /** @var T */ private $value; /** * @param T $value */ public function __construct($value) { $this->value = $value; } /** * @template U * * @param U $value * * @return Some */ public static function create($value): self { return new self($value); } public function isDefined(): bool { return true; } public function isEmpty(): bool { return false; } public function get() { return $this->value; } public function getOrElse($default) { return $this->value; } public function getOrCall($callable) { return $this->value; } public function getOrThrow(\Exception $ex) { return $this->value; } public function orElse(Option $else) { return $this; } public function ifDefined($callable) { $this->forAll($callable); } public function forAll($callable) { $callable($this->value); return $this; } public function map($callable) { return new self($callable($this->value)); } public function flatMap($callable) { /** @var mixed */ $rs = $callable($this->value); if (!$rs instanceof Option) { throw new \RuntimeException('Callables passed to flatMap() must return an Option. Maybe you should use map() instead?'); } return $rs; } public function filter($callable) { if (true === $callable($this->value)) { return $this; } return None::create(); } public function filterNot($callable) { if (false === $callable($this->value)) { return $this; } return None::create(); } public function select($value) { if ($this->value === $value) { return $this; } return None::create(); } public function reject($value) { if ($this->value === $value) { return None::create(); } return $this; } /** * @return ArrayIterator */ public function getIterator(): ArrayIterator { return new ArrayIterator([$this->value]); } public function foldLeft($initialValue, $callable) { return $callable($initialValue, $this->value); } public function foldRight($initialValue, $callable) { return $callable($this->value, $initialValue); } } PK!APhpOption/LazyOption.phpnu[ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace PhpOption; use Traversable; /** * @template T * * @extends Option */ final class LazyOption extends Option { /** @var callable(mixed...):(Option) */ private $callback; /** @var array */ private $arguments; /** @var Option|null */ private $option; /** * @template S * @param callable(mixed...):(Option) $callback * @param array $arguments * * @return LazyOption */ public static function create($callback, array $arguments = []): self { return new self($callback, $arguments); } /** * @param callable(mixed...):(Option) $callback * @param array $arguments */ public function __construct($callback, array $arguments = []) { if (!is_callable($callback)) { throw new \InvalidArgumentException('Invalid callback given'); } $this->callback = $callback; $this->arguments = $arguments; } public function isDefined(): bool { return $this->option()->isDefined(); } public function isEmpty(): bool { return $this->option()->isEmpty(); } public function get() { return $this->option()->get(); } public function getOrElse($default) { return $this->option()->getOrElse($default); } public function getOrCall($callable) { return $this->option()->getOrCall($callable); } public function getOrThrow(\Exception $ex) { return $this->option()->getOrThrow($ex); } public function orElse(Option $else) { return $this->option()->orElse($else); } public function ifDefined($callable) { $this->option()->forAll($callable); } public function forAll($callable) { return $this->option()->forAll($callable); } public function map($callable) { return $this->option()->map($callable); } public function flatMap($callable) { return $this->option()->flatMap($callable); } public function filter($callable) { return $this->option()->filter($callable); } public function filterNot($callable) { return $this->option()->filterNot($callable); } public function select($value) { return $this->option()->select($value); } public function reject($value) { return $this->option()->reject($value); } /** * @return Traversable */ public function getIterator(): Traversable { return $this->option()->getIterator(); } public function foldLeft($initialValue, $callable) { return $this->option()->foldLeft($initialValue, $callable); } public function foldRight($initialValue, $callable) { return $this->option()->foldRight($initialValue, $callable); } /** * @return Option */ private function option(): Option { if (null === $this->option) { /** @var mixed */ $option = call_user_func_array($this->callback, $this->arguments); if ($option instanceof Option) { $this->option = $option; } else { throw new \RuntimeException(sprintf('Expected instance of %s', Option::class)); } } return $this->option; } } PK!$@44PhpOption/Option.phpnu[ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace PhpOption; use ArrayAccess; use IteratorAggregate; /** * @template T * * @implements IteratorAggregate */ abstract class Option implements IteratorAggregate { /** * Creates an option given a return value. * * This is intended for consuming existing APIs and allows you to easily * convert them to an option. By default, we treat ``null`` as the None * case, and everything else as Some. * * @template S * * @param S $value The actual return value. * @param S $noneValue The value which should be considered "None"; null by * default. * * @return Option */ public static function fromValue($value, $noneValue = null) { if ($value === $noneValue) { return None::create(); } return new Some($value); } /** * Creates an option from an array's value. * * If the key does not exist in the array, the array is not actually an * array, or the array's value at the given key is null, None is returned. * Otherwise, Some is returned wrapping the value at the given key. * * @template S * * @param array|ArrayAccess|null $array A potential array or \ArrayAccess value. * @param string $key The key to check. * * @return Option */ public static function fromArraysValue($array, $key) { if (!(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) { return None::create(); } return new Some($array[$key]); } /** * Creates a lazy-option with the given callback. * * This is also a helper constructor for lazy-consuming existing APIs where * the return value is not yet an option. By default, we treat ``null`` as * None case, and everything else as Some. * * @template S * * @param callable $callback The callback to evaluate. * @param array $arguments The arguments for the callback. * @param S $noneValue The value which should be considered "None"; * null by default. * * @return LazyOption */ public static function fromReturn($callback, array $arguments = [], $noneValue = null) { return new LazyOption(static function () use ($callback, $arguments, $noneValue) { /** @var mixed */ $return = call_user_func_array($callback, $arguments); if ($return === $noneValue) { return None::create(); } return new Some($return); }); } /** * Option factory, which creates new option based on passed value. * * If value is already an option, it simply returns. If value is callable, * LazyOption with passed callback created and returned. If Option * returned from callback, it returns directly. On other case value passed * to Option::fromValue() method. * * @template S * * @param Option|callable|S $value * @param S $noneValue Used when $value is mixed or * callable, for None-check. * * @return Option|LazyOption */ public static function ensure($value, $noneValue = null) { if ($value instanceof self) { return $value; } elseif (is_callable($value)) { return new LazyOption(static function () use ($value, $noneValue) { /** @var mixed */ $return = $value(); if ($return instanceof self) { return $return; } else { return self::fromValue($return, $noneValue); } }); } else { return self::fromValue($value, $noneValue); } } /** * Lift a function so that it accepts Option as parameters. * * We return a new closure that wraps the original callback. If any of the * parameters passed to the lifted function is empty, the function will * return a value of None. Otherwise, we will pass all parameters to the * original callback and return the value inside a new Option, unless an * Option is returned from the function, in which case, we use that. * * @template S * * @param callable $callback * @param mixed $noneValue * * @return callable */ public static function lift($callback, $noneValue = null) { return static function () use ($callback, $noneValue) { /** @var array */ $args = func_get_args(); $reduced_args = array_reduce( $args, /** @param bool $status */ static function ($status, self $o) { return $o->isEmpty() ? true : $status; }, false ); // if at least one parameter is empty, return None if ($reduced_args) { return None::create(); } $args = array_map( /** @return T */ static function (self $o) { // it is safe to do so because the fold above checked // that all arguments are of type Some /** @var T */ return $o->get(); }, $args ); return self::ensure(call_user_func_array($callback, $args), $noneValue); }; } /** * Returns the value if available, or throws an exception otherwise. * * @throws \RuntimeException If value is not available. * * @return T */ abstract public function get(); /** * Returns the value if available, or the default value if not. * * @template S * * @param S $default * * @return T|S */ abstract public function getOrElse($default); /** * Returns the value if available, or the results of the callable. * * This is preferable over ``getOrElse`` if the computation of the default * value is expensive. * * @template S * * @param callable():S $callable * * @return T|S */ abstract public function getOrCall($callable); /** * Returns the value if available, or throws the passed exception. * * @param \Exception $ex * * @return T */ abstract public function getOrThrow(\Exception $ex); /** * Returns true if no value is available, false otherwise. * * @return bool */ abstract public function isEmpty(); /** * Returns true if a value is available, false otherwise. * * @return bool */ abstract public function isDefined(); /** * Returns this option if non-empty, or the passed option otherwise. * * This can be used to try multiple alternatives, and is especially useful * with lazy evaluating options: * * ```php * $repo->findSomething() * ->orElse(new LazyOption(array($repo, 'findSomethingElse'))) * ->orElse(new LazyOption(array($repo, 'createSomething'))); * ``` * * @param Option $else * * @return Option */ abstract public function orElse(self $else); /** * This is similar to map() below except that the return value has no meaning; * the passed callable is simply executed if the option is non-empty, and * ignored if the option is empty. * * In all cases, the return value of the callable is discarded. * * ```php * $comment->getMaybeFile()->ifDefined(function($file) { * // Do something with $file here. * }); * ``` * * If you're looking for something like ``ifEmpty``, you can use ``getOrCall`` * and ``getOrElse`` in these cases. * * @deprecated Use forAll() instead. * * @param callable(T):mixed $callable * * @return void */ abstract public function ifDefined($callable); /** * This is similar to map() except that the return value of the callable has no meaning. * * The passed callable is simply executed if the option is non-empty, and ignored if the * option is empty. This method is preferred for callables with side-effects, while map() * is intended for callables without side-effects. * * @param callable(T):mixed $callable * * @return Option */ abstract public function forAll($callable); /** * Applies the callable to the value of the option if it is non-empty, * and returns the return value of the callable wrapped in Some(). * * If the option is empty, then the callable is not applied. * * ```php * (new Some("foo"))->map('strtoupper')->get(); // "FOO" * ``` * * @template S * * @param callable(T):S $callable * * @return Option */ abstract public function map($callable); /** * Applies the callable to the value of the option if it is non-empty, and * returns the return value of the callable directly. * * In contrast to ``map``, the return value of the callable is expected to * be an Option itself; it is not automatically wrapped in Some(). * * @template S * * @param callable(T):Option $callable must return an Option * * @return Option */ abstract public function flatMap($callable); /** * If the option is empty, it is returned immediately without applying the callable. * * If the option is non-empty, the callable is applied, and if it returns true, * the option itself is returned; otherwise, None is returned. * * @param callable(T):bool $callable * * @return Option */ abstract public function filter($callable); /** * If the option is empty, it is returned immediately without applying the callable. * * If the option is non-empty, the callable is applied, and if it returns false, * the option itself is returned; otherwise, None is returned. * * @param callable(T):bool $callable * * @return Option */ abstract public function filterNot($callable); /** * If the option is empty, it is returned immediately. * * If the option is non-empty, and its value does not equal the passed value * (via a shallow comparison ===), then None is returned. Otherwise, the * Option is returned. * * In other words, this will filter all but the passed value. * * @param T $value * * @return Option */ abstract public function select($value); /** * If the option is empty, it is returned immediately. * * If the option is non-empty, and its value does equal the passed value (via * a shallow comparison ===), then None is returned; otherwise, the Option is * returned. * * In other words, this will let all values through except the passed value. * * @param T $value * * @return Option */ abstract public function reject($value); /** * Binary operator for the initial value and the option's value. * * If empty, the initial value is returned. If non-empty, the callable * receives the initial value and the option's value as arguments. * * ```php * * $some = new Some(5); * $none = None::create(); * $result = $some->foldLeft(1, function($a, $b) { return $a + $b; }); // int(6) * $result = $none->foldLeft(1, function($a, $b) { return $a + $b; }); // int(1) * * // This can be used instead of something like the following: * $option = Option::fromValue($integerOrNull); * $result = 1; * if ( ! $option->isEmpty()) { * $result += $option->get(); * } * ``` * * @template S * * @param S $initialValue * @param callable(S, T):S $callable * * @return S */ abstract public function foldLeft($initialValue, $callable); /** * foldLeft() but with reversed arguments for the callable. * * @template S * * @param S $initialValue * @param callable(T, S):S $callable * * @return S */ abstract public function foldRight($initialValue, $callable); } PK!c YY NullValue.phpnu[ */ private $keyUniverse; /** * Array representation of this map. The ith element is the value to which universe[i] is currently mapped, or null * if it isn't mapped to anything, or NullValue if it's mapped to null. * * @var array */ private $values; /** * @var int */ private $size = 0; /** * Creates a new enum map. * * @param string $keyType the type of the keys, must extend AbstractEnum * @param string $valueType the type of the values * @param bool $allowNullValues whether to allow null values * @throws IllegalArgumentException when key type does not extend AbstractEnum */ public function __construct(string $keyType, string $valueType, bool $allowNullValues) { if (! is_subclass_of($keyType, AbstractEnum::class)) { throw new IllegalArgumentException(sprintf( 'Class %s does not extend %s', $keyType, AbstractEnum::class )); } $this->keyType = $keyType; $this->valueType = $valueType; $this->allowNullValues = $allowNullValues; $this->keyUniverse = $keyType::values(); $this->values = array_fill(0, count($this->keyUniverse), null); } /** * Checks whether the map types match the supplied ones. * * You should call this method when an EnumMap is passed to you and you want to ensure that it's made up of the * correct types. * * @throws ExpectationException when supplied key type mismatches local key type * @throws ExpectationException when supplied value type mismatches local value type * @throws ExpectationException when the supplied map allows null values, abut should not */ public function expect(string $keyType, string $valueType, bool $allowNullValues) : void { if ($keyType !== $this->keyType) { throw new ExpectationException(sprintf( 'Callee expected an EnumMap with key type %s, but got %s', $keyType, $this->keyType )); } if ($valueType !== $this->valueType) { throw new ExpectationException(sprintf( 'Callee expected an EnumMap with value type %s, but got %s', $keyType, $this->keyType )); } if ($allowNullValues !== $this->allowNullValues) { throw new ExpectationException(sprintf( 'Callee expected an EnumMap with nullable flag %s, but got %s', ($allowNullValues ? 'true' : 'false'), ($this->allowNullValues ? 'true' : 'false') )); } } /** * Returns the number of key-value mappings in this map. */ public function size() : int { return $this->size; } /** * Returns true if this map maps one or more keys to the specified value. */ public function containsValue($value) : bool { return in_array($this->maskNull($value), $this->values, true); } /** * Returns true if this map contains a mapping for the specified key. */ public function containsKey(AbstractEnum $key) : bool { $this->checkKeyType($key); return null !== $this->values[$key->ordinal()]; } /** * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. * * More formally, if this map contains a mapping from a key to a value, then this method returns the value; * otherwise it returns null (there can be at most one such mapping). * * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also * possible that hte map explicitly maps the key to null. The {@see self::containsKey()} operation may be used to * distinguish these two cases. * * @return mixed */ public function get(AbstractEnum $key) { $this->checkKeyType($key); return $this->unmaskNull($this->values[$key->ordinal()]); } /** * Associates the specified value with the specified key in this map. * * If the map previously contained a mapping for this key, the old value is replaced. * * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key. * (a null return can also indicate that the map previously associated null with the specified key.) * @throws IllegalArgumentException when the passed values does not match the internal value type */ public function put(AbstractEnum $key, $value) { $this->checkKeyType($key); if (! $this->isValidValue($value)) { throw new IllegalArgumentException(sprintf('Value is not of type %s', $this->valueType)); } $index = $key->ordinal(); $oldValue = $this->values[$index]; $this->values[$index] = $this->maskNull($value); if (null === $oldValue) { ++$this->size; } return $this->unmaskNull($oldValue); } /** * Removes the mapping for this key frm this map if present. * * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key. * (a null return can also indicate that the map previously associated null with the specified key.) */ public function remove(AbstractEnum $key) { $this->checkKeyType($key); $index = $key->ordinal(); $oldValue = $this->values[$index]; $this->values[$index] = null; if (null !== $oldValue) { --$this->size; } return $this->unmaskNull($oldValue); } /** * Removes all mappings from this map. */ public function clear() : void { $this->values = array_fill(0, count($this->keyUniverse), null); $this->size = 0; } /** * Compares the specified map with this map for quality. * * Returns true if the two maps represent the same mappings. */ public function equals(self $other) : bool { if ($this === $other) { return true; } if ($this->size !== $other->size) { return false; } return $this->values === $other->values; } /** * Returns the values contained in this map. * * The array will contain the values in the order their corresponding keys appear in the map, which is their natural * order (the order in which the num constants are declared). */ public function values() : array { return array_values(array_map(function ($value) { return $this->unmaskNull($value); }, array_filter($this->values, function ($value) : bool { return null !== $value; }))); } public function serialize() : string { $values = []; foreach ($this->values as $ordinal => $value) { if (null === $value) { continue; } $values[$ordinal] = $this->unmaskNull($value); } return serialize([ 'keyType' => $this->keyType, 'valueType' => $this->valueType, 'allowNullValues' => $this->allowNullValues, 'values' => $values, ]); } public function unserialize($serialized) : void { $data = unserialize($serialized); $this->__construct($data['keyType'], $data['valueType'], $data['allowNullValues']); foreach ($this->keyUniverse as $key) { if (array_key_exists($key->ordinal(), $data['values'])) { $this->put($key, $data['values'][$key->ordinal()]); } } } public function getIterator() : Traversable { foreach ($this->keyUniverse as $key) { if (null === $this->values[$key->ordinal()]) { continue; } yield $key => $this->unmaskNull($this->values[$key->ordinal()]); } } private function maskNull($value) { if (null === $value) { return NullValue::instance(); } return $value; } private function unmaskNull($value) { if ($value instanceof NullValue) { return null; } return $value; } /** * @throws IllegalArgumentException when the passed key does not match the internal key type */ private function checkKeyType(AbstractEnum $key) : void { if (get_class($key) !== $this->keyType) { throw new IllegalArgumentException(sprintf( 'Object of type %s is not the same type as %s', get_class($key), $this->keyType )); } } private function isValidValue($value) : bool { if (null === $value) { if ($this->allowNullValues) { return true; } return false; } switch ($this->valueType) { case 'mixed': return true; case 'bool': case 'boolean': return is_bool($value); case 'int': case 'integer': return is_int($value); case 'float': case 'double': return is_float($value); case 'string': return is_string($value); case 'object': return is_object($value); case 'array': return is_array($value); } return $value instanceof $this->valueType; } } PK!mY&Exception/IllegalArgumentException.phpnu[> */ private static $values = []; /** * @var array */ private static $allValuesLoaded = []; /** * @var array */ private static $constants = []; /** * The constructor is private by default to avoid arbitrary enum creation. * * When creating your own constructor for a parameterized enum, make sure to declare it as protected, so that * the static methods are able to construct it. Avoid making it public, as that would allow creation of * non-singleton enum instances. */ private function __construct() { } /** * Magic getter which forwards all calls to {@see self::valueOf()}. * * @return static */ final public static function __callStatic(string $name, array $arguments) : self { return static::valueOf($name); } /** * Returns an enum with the specified name. * * The name must match exactly an identifier used to declare an enum in this type (extraneous whitespace characters * are not permitted). * * @return static * @throws IllegalArgumentException if the enum has no constant with the specified name */ final public static function valueOf(string $name) : self { if (isset(self::$values[static::class][$name])) { return self::$values[static::class][$name]; } $constants = self::constants(); if (array_key_exists($name, $constants)) { return self::createValue($name, $constants[$name][0], $constants[$name][1]); } throw new IllegalArgumentException(sprintf('No enum constant %s::%s', static::class, $name)); } /** * @return static */ private static function createValue(string $name, int $ordinal, array $arguments) : self { $instance = new static(...$arguments); $instance->name = $name; $instance->ordinal = $ordinal; self::$values[static::class][$name] = $instance; return $instance; } /** * Obtains all possible types defined by this enum. * * @return static[] */ final public static function values() : array { if (isset(self::$allValuesLoaded[static::class])) { return self::$values[static::class]; } if (! isset(self::$values[static::class])) { self::$values[static::class] = []; } foreach (self::constants() as $name => $constant) { if (array_key_exists($name, self::$values[static::class])) { continue; } static::createValue($name, $constant[0], $constant[1]); } uasort(self::$values[static::class], function (self $a, self $b) { return $a->ordinal() <=> $b->ordinal(); }); self::$allValuesLoaded[static::class] = true; return self::$values[static::class]; } private static function constants() : array { if (isset(self::$constants[static::class])) { return self::$constants[static::class]; } self::$constants[static::class] = []; $reflectionClass = new ReflectionClass(static::class); $ordinal = -1; foreach ($reflectionClass->getReflectionConstants() as $reflectionConstant) { if (! $reflectionConstant->isProtected()) { continue; } $value = $reflectionConstant->getValue(); self::$constants[static::class][$reflectionConstant->name] = [ ++$ordinal, is_array($value) ? $value : [] ]; } return self::$constants[static::class]; } /** * Returns the name of this enum constant, exactly as declared in its enum declaration. * * Most programmers should use the {@see self::__toString()} method in preference to this one, as the toString * method may return a more user-friendly name. This method is designed primarily for use in specialized situations * where correctness depends on getting the exact name, which will not vary from release to release. */ final public function name() : string { return $this->name; } /** * Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial * constant is assigned an ordinal of zero). * * Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data * structures. */ final public function ordinal() : int { return $this->ordinal; } /** * Compares this enum with the specified object for order. * * Returns negative integer, zero or positive integer as this object is less than, equal to or greater than the * specified object. * * Enums are only comparable to other enums of the same type. The natural order implemented by this method is the * order in which the constants are declared. * * @throws MismatchException if the passed enum is not of the same type */ final public function compareTo(self $other) : int { if (! $other instanceof static) { throw new MismatchException(sprintf( 'The passed enum %s is not of the same type as %s', get_class($other), static::class )); } return $this->ordinal - $other->ordinal; } /** * Forbid cloning enums. * * @throws CloneNotSupportedException */ final public function __clone() { throw new CloneNotSupportedException(); } /** * Forbid serializing enums. * * @throws SerializeNotSupportedException */ final public function __sleep() : array { throw new SerializeNotSupportedException(); } /** * Forbid unserializing enums. * * @throws UnserializeNotSupportedException */ final public function __wakeup() : void { throw new UnserializeNotSupportedException(); } /** * Turns the enum into a string representation. * * You may override this method to give a more user-friendly version. */ public function __toString() : string { return $this->name; } } PK!~z**,Pecee/SimpleRouter/Handlers/EventHandler.phpnu[registeredEvents[$name]) === true) { $this->registeredEvents[$name][] = $callback; } else { $this->registeredEvents[$name] = [$callback]; } return $this; } /** * Get events. * * @param string|null $name Filter events by name. * @param array|string ...$names Add multiple names... * @return array */ public function getEvents(?string $name, ...$names): array { if ($name === null) { return $this->registeredEvents; } $names[] = $name; $events = []; foreach ($names as $eventName) { if (isset($this->registeredEvents[$eventName]) === true) { $events += $this->registeredEvents[$eventName]; } } return $events; } /** * Fires any events registered with given event-name * * @param Router $router Router instance * @param string $name Event name * @param array $eventArgs Event arguments */ public function fireEvents(Router $router, string $name, array $eventArgs = []): void { $events = $this->getEvents(static::EVENT_ALL, $name); /* @var $event Closure */ foreach ($events as $event) { $event(new EventArgument($name, $router, $eventArgs)); } } }PK!}EE-Pecee/SimpleRouter/Handlers/IEventHandler.phpnu[callback = $callback; } /** * @param Request $request * @param Exception $error */ public function handleError(Request $request, Exception $error): void { /* Fire exceptions */ call_user_func($this->callback, $request, $error ); } }PK!#1Pecee/SimpleRouter/Handlers/IExceptionHandler.phpnu[callback = static function (EventArgument $argument): void { // todo: log in database }; } /** * Get events. * * @param string|null $name Filter events by name. * @return array */ public function getEvents(?string $name): array { return [ $name => [ $this->callback, ], ]; } /** * Fires any events registered with given event-name * * @param Router $router Router instance * @param string $name Event name * @param array $eventArgs Event arguments */ public function fireEvents(Router $router, string $name, array $eventArgs = []): void { $callback = $this->callback; $callback(new EventArgument($name, $router, $eventArgs)); } /** * Set debug callback * * @param Closure $event */ public function setCallback(Closure $event): void { $this->callback = $event; } }PK!0==#Pecee/SimpleRouter/SimpleRouter.phpnu[getRoutes() as $route) { static::addDefaultNamespace($route); } echo static::router()->start(); } /** * Start the routing an return array with debugging-information * * @return array */ public static function startDebug(): array { $routerOutput = null; try { ob_start(); static::router()->setDebugEnabled(true)->start(); $routerOutput = ob_get_clean(); } catch (Exception $e) { } // Try to parse library version $composerFile = dirname(__DIR__, 3) . '/composer.lock'; $version = false; if (is_file($composerFile) === true) { $composerInfo = json_decode(file_get_contents($composerFile), true); if (isset($composerInfo['packages']) === true && is_array($composerInfo['packages']) === true) { foreach ($composerInfo['packages'] as $package) { if (isset($package['name']) === true && strtolower($package['name']) === 'pecee/simple-router') { $version = $package['version']; break; } } } } $request = static::request(); $router = static::router(); return [ 'url' => $request->getUrl(), 'method' => $request->getMethod(), 'host' => $request->getHost(), 'loaded_routes' => $request->getLoadedRoutes(), 'all_routes' => $router->getRoutes(), 'boot_managers' => $router->getBootManagers(), 'csrf_verifier' => $router->getCsrfVerifier(), 'log' => $router->getDebugLog(), 'event_handlers' => $router->getEventHandlers(), 'router_output' => $routerOutput, 'library_version' => $version, 'php_version' => PHP_VERSION, 'server_params' => $request->getHeaders(), ]; } /** * Set default namespace which will be prepended to all routes. * * @param string $defaultNamespace */ public static function setDefaultNamespace(string $defaultNamespace): void { static::$defaultNamespace = $defaultNamespace; } /** * Base CSRF verifier * * @param BaseCsrfVerifier $baseCsrfVerifier */ public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier): void { static::router()->setCsrfVerifier($baseCsrfVerifier); } /** * Add new event handler to the router * * @param IEventHandler $eventHandler */ public static function addEventHandler(IEventHandler $eventHandler): void { static::router()->addEventHandler($eventHandler); } /** * Boot managers allows you to alter the routes before the routing occurs. * Perfect if you want to load pretty-urls from a file or database. * * @param IRouterBootManager $bootManager */ public static function addBootManager(IRouterBootManager $bootManager): void { static::router()->addBootManager($bootManager); } /** * Redirect to when route matches. * * @param string $where * @param string $to * @param int $httpCode * @return IRoute */ public static function redirect(string $where, string $to, int $httpCode = 301): IRoute { return static::get($where, static function () use ($to, $httpCode): void { static::response()->redirect($to, $httpCode); }); } /** * Route the given url to your callback on GET request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * * @return RouteUrl|IRoute */ public static function get(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_GET], $url, $callback, $settings); } /** * Route the given url to your callback on POST request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function post(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_POST], $url, $callback, $settings); } /** * Route the given url to your callback on PUT request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function put(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_PUT], $url, $callback, $settings); } /** * Route the given url to your callback on PATCH request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function patch(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_PATCH], $url, $callback, $settings); } /** * Route the given url to your callback on OPTIONS request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function options(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_OPTIONS], $url, $callback, $settings); } /** * Route the given url to your callback on DELETE request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function delete(string $url, $callback, array $settings = null): IRoute { return static::match([Request::REQUEST_TYPE_DELETE], $url, $callback, $settings); } /** * Groups allows for encapsulating routes with special settings. * * @param array $settings * @param Closure $callback * @return RouteGroup|IGroupRoute * @throws InvalidArgumentException */ public static function group(array $settings, Closure $callback): IGroupRoute { $group = new RouteGroup(); $group->setCallback($callback); $group->setSettings($settings); static::router()->addRoute($group); return $group; } /** * Special group that has the same benefits as group but supports * parameters and which are only rendered when the url matches. * * @param string $url * @param Closure $callback * @param array $settings * @return RoutePartialGroup|IPartialGroupRoute * @throws InvalidArgumentException */ public static function partialGroup(string $url, Closure $callback, array $settings = []): IPartialGroupRoute { $settings['prefix'] = $url; $group = new RoutePartialGroup(); $group->setSettings($settings); $group->setCallback($callback); static::router()->addRoute($group); return $group; } /** * Alias for the form method * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute * @see SimpleRouter::form */ public static function basic(string $url, $callback, array $settings = null): IRoute { return static::form($url, $callback, $settings); } /** * This type will route the given url to your callback on the provided request methods. * Route the given url to your callback on POST and GET request method. * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute * @see SimpleRouter::form */ public static function form(string $url, $callback, array $settings = null): IRoute { return static::match([ Request::REQUEST_TYPE_GET, Request::REQUEST_TYPE_POST, ], $url, $callback, $settings); } /** * This type will route the given url to your callback on the provided request methods. * * @param array $requestMethods * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function match(array $requestMethods, string $url, $callback, array $settings = null): IRoute { $route = new RouteUrl($url, $callback); $route->setRequestMethods($requestMethods); if ($settings !== null) { $route->setSettings($settings); } return static::router()->addRoute($route); } /** * This type will route the given url to your callback and allow any type of request method * * @param string $url * @param string|array|Closure $callback * @param array|null $settings * @return RouteUrl|IRoute */ public static function all(string $url, $callback, array $settings = null): IRoute { $route = new RouteUrl($url, $callback); if ($settings !== null) { $route->setSettings($settings); } return static::router()->addRoute($route); } /** * This route will route request from the given url to the controller. * * @param string $url * @param string $controller * @param array|null $settings * @return RouteController|IRoute */ public static function controller(string $url, string $controller, array $settings = null): IRoute { $route = new RouteController($url, $controller); if ($settings !== null) { $route->setSettings($settings); } return static::router()->addRoute($route); } /** * This type will route all REST-supported requests to different methods in the provided controller. * * @param string $url * @param string $controller * @param array|null $settings * @return RouteResource|IRoute */ public static function resource(string $url, string $controller, array $settings = null): IRoute { $route = new RouteResource($url, $controller); if ($settings !== null) { $route->setSettings($settings); } return static::router()->addRoute($route); } /** * Add exception callback handler. * * @param Closure $callback * @return CallbackExceptionHandler $callbackHandler */ public static function error(Closure $callback): CallbackExceptionHandler { $callbackHandler = new CallbackExceptionHandler($callback); static::router()->addExceptionHandler($callbackHandler); return $callbackHandler; } /** * Get url for a route by using either name/alias, class or method name. * * The name parameter supports the following values: * - Route name * - Controller/resource name (with or without method) * - Controller class name * * When searching for controller/resource by name, you can use this syntax "route.name@method". * You can also use the same syntax when searching for a specific controller-class "MyController@home". * If no arguments is specified, it will return the url for the current loaded route. * * @param string|null $name * @param string|array|null $parameters * @param array|null $getParams * @return Url */ public static function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url { try { return static::router()->getUrl($name, $parameters, $getParams); } catch (Exception $e) { return new Url('/'); } } /** * Get the request * * @return Request */ public static function request(): Request { return static::router()->getRequest(); } /** * Get the response object * * @return Response */ public static function response(): Response { if (static::$response === null) { static::$response = new Response(static::request()); } return static::$response; } /** * Returns the router instance * * @return Router */ public static function router(): Router { if (static::$router === null) { static::$router = new Router(); } return static::$router; } /** * Prepends the default namespace to all new routes added. * * @param ILoadableRoute|IRoute $route * @return IRoute */ public static function addDefaultNamespace(IRoute $route): IRoute { if (static::$defaultNamespace !== null) { $route->setNamespace(static::$defaultNamespace); } return $route; } /** * Changes the rendering behavior of the router. * When enabled the router will render all routes that matches. * When disabled the router will stop rendering at the first route that matches. * * @param bool $bool */ public static function enableMultiRouteRendering(bool $bool): void { static::router()->setRenderMultipleRoutes($bool); } /** * Set custom class-loader class used. * @param IClassLoader $classLoader */ public static function setCustomClassLoader(IClassLoader $classLoader): void { static::router()->setClassLoader($classLoader); } /** * Get default namespace * @return string|null */ public static function getDefaultNamespace(): ?string { return static::$defaultNamespace; } }PK!fI,+Pecee/SimpleRouter/Event/IEventArgument.phpnu[eventName = $eventName; $this->router = $router; $this->arguments = $arguments; } /** * Get event name * * @return string */ public function getEventName(): string { return $this->eventName; } /** * Set the event name * * @param string $name */ public function setEventName(string $name): void { $this->eventName = $name; } /** * Get the router instance * * @return Router */ public function getRouter(): Router { return $this->router; } /** * Get the request instance * * @return Request */ public function getRequest(): Request { return $this->getRouter()->getRequest(); } /** * @param string $name * @return mixed */ public function __get(string $name) { return $this->arguments[$name] ?? null; } /** * @param string $name * @return bool */ public function __isset(string $name): bool { return array_key_exists($name, $this->arguments); } /** * @param string $name * @param mixed $value * @throws InvalidArgumentException */ public function __set(string $name, $value): void { throw new InvalidArgumentException('Not supported'); } /** * Get arguments * * @return array */ public function getArguments(): array { return $this->arguments; } }PK!Y %Pecee/SimpleRouter/Route/RouteUrl.phpnu[setUrl($url); $this->setCallback($callback); } public function matchRoute(string $url, Request $request): bool { if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) { return false; } /* Match global regular-expression for route */ $regexMatch = $this->matchRegex($request, $url); if ($regexMatch === false) { return false; } /* Parse parameters from current route */ $parameters = $this->parseParameters($this->url, $url); /* If no custom regular expression or parameters was found on this route, we stop */ if ($regexMatch === null && $parameters === null) { return false; } /* Set the parameters */ $this->setParameters((array)$parameters); return true; } }PK!%+y-Pecee/SimpleRouter/Route/IControllerRoute.phpnu[domains === null || count($this->domains) === 0) { return true; } foreach ($this->domains as $domain) { // If domain has no parameters but matches if ($domain === $request->getHost()) { return true; } $parameters = $this->parseParameters($domain, $request->getHost(), '.*'); if ($parameters !== null && count($parameters) !== 0) { $this->parameters = $parameters; return true; } } return false; } /** * Method called to check if route matches * * @param string $url * @param Request $request * @return bool */ public function matchRoute(string $url, Request $request): bool { if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) { return false; } if ($this->prefix !== null) { /* Parse parameters from current route */ $parameters = $this->parseParameters($this->prefix, $url); /* If no custom regular expression or parameters was found on this route, we stop */ if ($parameters === null) { return false; } /* Set the parameters */ $this->setParameters($parameters); } $parsedPrefix = $this->prefix; foreach ($this->getParameters() as $parameter => $value) { $parsedPrefix = str_ireplace('{' . $parameter . '}', $value, $parsedPrefix); } /* Skip if prefix doesn't match */ if ($this->prefix !== null && stripos($url, rtrim($parsedPrefix, '/') . '/') === false) { return false; } return $this->matchDomain($request); } /** * Add exception handler * * @param IExceptionHandler|string $handler * @return static */ public function addExceptionHandler($handler): IGroupRoute { $this->exceptionHandlers[] = $handler; return $this; } /** * Set exception-handlers for group * * @param array $handlers * @return static */ public function setExceptionHandlers(array $handlers): IGroupRoute { $this->exceptionHandlers = $handlers; return $this; } /** * Get exception-handlers for group * * @return array */ public function getExceptionHandlers(): array { return $this->exceptionHandlers; } /** * Get allowed domains for domain. * * @return array */ public function getDomains(): array { return $this->domains; } /** * Set allowed domains for group. * * @param array $domains * @return static */ public function setDomains(array $domains): IGroupRoute { $this->domains = $domains; return $this; } /** * @param string $prefix * @return static */ public function setPrefix(string $prefix): IGroupRoute { $this->prefix = '/' . trim($prefix, '/'); return $this; } /** * Prepends prefix while ensuring that the url has the correct formatting. * * @param string $url * @return static */ public function prependPrefix(string $url): IGroupRoute { return $this->setPrefix(rtrim($url, '/') . $this->prefix); } /** * Set prefix that child-routes will inherit. * * @return string|null */ public function getPrefix(): ?string { return $this->prefix; } /** * When enabled group will overwrite any existing exception-handlers. * * @param bool $merge * @return static */ public function setMergeExceptionHandlers(bool $merge): IGroupRoute { $this->mergeExceptionHandlers = $merge; return $this; } /** * Returns true if group should overwrite existing exception-handlers. * * @return bool */ public function getMergeExceptionHandlers(): bool { return $this->mergeExceptionHandlers; } /** * Merge with information from another route. * * @param array $settings * @param bool $merge * @return static */ public function setSettings(array $settings, bool $merge = false): IRoute { if (isset($settings['prefix']) === true) { $this->setPrefix($settings['prefix'] . $this->prefix); } if (isset($settings['mergeExceptionHandlers']) === true) { $this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']); } if ($merge === false && isset($settings['exceptionHandler']) === true) { $this->setExceptionHandlers((array)$settings['exceptionHandler']); } if ($merge === false && isset($settings['domain']) === true) { $this->setDomains((array)$settings['domain']); } if (isset($settings['as']) === true) { $name = $settings['as']; if ($this->name !== null && $merge !== false) { $name .= '.' . $this->name; } $this->name = $name; } return parent::setSettings($settings, $merge); } /** * Export route settings to array so they can be merged with another route. * * @return array */ public function toArray(): array { $values = []; if ($this->prefix !== null) { $values['prefix'] = $this->getPrefix(); } if ($this->name !== null) { $values['as'] = $this->name; } if (count($this->parameters) !== 0) { $values['parameters'] = $this->parameters; } return array_merge($values, parent::toArray()); } }PK!dFQKK#Pecee/SimpleRouter/Route/IRoute.phpnu[debug('Starting rendering route "%s"', get_class($this)); $callback = $this->getCallback(); if ($callback === null) { return null; } $router->debug('Parsing parameters'); $parameters = $this->getParameters(); $router->debug('Finished parsing parameters'); /* Filter parameters with null-value */ if ($this->filterEmptyParams === true) { $parameters = array_filter($parameters, static function ($var): bool { return ($var !== null); }); } /* Render callback function */ if (is_callable($callback) === true) { $router->debug('Executing callback'); /* Load class from type hinting */ if (is_array($callback) === true && isset($callback[0], $callback[1]) === true) { $callback[0] = $router->getClassLoader()->loadClass($callback[0]); } /* When the callback is a function */ return $router->getClassLoader()->loadClosure($callback, $parameters); } $controller = $this->getClass(); $method = $this->getMethod(); $namespace = $this->getNamespace(); $className = ($namespace !== null && $controller[0] !== '\\') ? $namespace . '\\' . $controller : $controller; $router->debug('Loading class %s', $className); $class = $router->getClassLoader()->loadClass($className); if ($method === null) { $controller[1] = '__invoke'; } if (method_exists($class, $method) === false) { throw new ClassNotFoundHttpException($className, $method, sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404, null); } $router->debug('Executing callback %s -> %s', $className, $method); return $router->getClassLoader()->loadClassMethod($class, $method, $parameters); } protected function parseParameters($route, $url, $parameterRegex = null): ?array { $regex = (strpos($route, $this->paramModifiers[0]) === false) ? null : sprintf ( static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1] ); // Ensures that host names/domains will work with parameters $url = '/' . ltrim($url, '/'); $urlRegex = ''; $parameters = []; if ($regex === null || (bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) { $urlRegex = preg_quote($route, '/'); } else { foreach (preg_split('/((-?\/?){[^}]+})/', $route) as $key => $t) { $regex = ''; if ($key < count($parameters[1])) { $name = $parameters[1][$key]; /* If custom regex is defined, use that */ if (isset($this->where[$name]) === true) { $regex = $this->where[$name]; } else { $regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX; } $regex = sprintf('((\/|-)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex); } $urlRegex .= preg_quote($t, '/') . $regex; } } if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) { return null; } $values = []; if (isset($parameters[1]) === true) { $groupParameters = $this->getGroup() !== null ? $this->getGroup()->getParameters() : []; $lastParams = []; /* Only take matched parameters with name */ foreach ((array)$parameters[1] as $name) { // Ignore parent parameters if (isset($groupParameters[$name]) === true) { $lastParams[$name] = $matches[$name]; continue; } $values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null; } $values = array_merge($values, $lastParams); } $this->originalParameters = $values; return $values; } /** * Returns callback name/identifier for the current route based on the callback. * Useful if you need to get a unique identifier for the loaded route, for instance * when using translations etc. * * @return string */ public function getIdentifier(): string { if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { return $this->callback; } return 'function:' . md5($this->callback); } /** * Set allowed request methods * * @param array $methods * @return static */ public function setRequestMethods(array $methods): IRoute { $this->requestMethods = $methods; return $this; } /** * Get allowed request methods * * @return array */ public function getRequestMethods(): array { return $this->requestMethods; } /** * @return IRoute|null */ public function getParent(): ?IRoute { return $this->parent; } /** * Get the group for the route. * * @return IGroupRoute|null */ public function getGroup(): ?IGroupRoute { return $this->group; } /** * Set group * * @param IGroupRoute $group * @return static */ public function setGroup(IGroupRoute $group): IRoute { $this->group = $group; /* Add/merge parent settings with child */ return $this->setSettings($group->toArray(), true); } /** * Set parent route * * @param IRoute $parent * @return static */ public function setParent(IRoute $parent): IRoute { $this->parent = $parent; return $this; } /** * Set callback * * @param string|array|\Closure $callback * @return static */ public function setCallback($callback): IRoute { $this->callback = $callback; return $this; } /** * @return string|callable|null */ public function getCallback() { return $this->callback; } public function getMethod(): ?string { if (is_array($this->callback) === true && count($this->callback) > 1) { return $this->callback[1]; } if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { $tmp = explode('@', $this->callback); return $tmp[1]; } return null; } public function getClass(): ?string { if (is_array($this->callback) === true && count($this->callback) > 0) { return $this->callback[0]; } if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { $tmp = explode('@', $this->callback); return $tmp[0]; } return null; } public function setMethod(string $method): IRoute { $this->callback = [$this->getClass(), $method]; return $this; } public function setClass(string $class): IRoute { $this->callback = [$class, $this->getMethod()]; return $this; } /** * @param string $namespace * @return static */ public function setNamespace(string $namespace): IRoute { // Do not set namespace when class-hinting is used if (is_array($this->callback) === true) { return $this; } $ns = $this->getNamespace(); if ($ns !== null) { // Don't overwrite namespaces that starts with \ if ($ns[0] !== '\\') { $namespace .= '\\' . $ns; } else { $namespace = $ns; } } $this->namespace = $namespace; return $this; } /** * @param string $namespace * @return static */ public function setDefaultNamespace(string $namespace): IRoute { $this->defaultNamespace = $namespace; return $this; } public function getDefaultNamespace(): ?string { return $this->defaultNamespace; } /** * @return string|null */ public function getNamespace(): ?string { return $this->namespace ?? $this->defaultNamespace; } /** * Export route settings to array so they can be merged with another route. * * @return array */ public function toArray(): array { $values = []; if ($this->namespace !== null) { $values['namespace'] = $this->namespace; } if (count($this->requestMethods) !== 0) { $values['method'] = $this->requestMethods; } if (count($this->where) !== 0) { $values['where'] = $this->where; } if (count($this->middlewares) !== 0) { $values['middleware'] = $this->middlewares; } if ($this->defaultParameterRegex !== null) { $values['defaultParameterRegex'] = $this->defaultParameterRegex; } return $values; } /** * Merge with information from another route. * * @param array $settings * @param bool $merge * @return static */ public function setSettings(array $settings, bool $merge = false): IRoute { if (isset($settings['namespace']) === true) { $this->setNamespace($settings['namespace']); } if (isset($settings['method']) === true) { $this->setRequestMethods(array_merge($this->requestMethods, (array)$settings['method'])); } if (isset($settings['where']) === true) { $this->setWhere(array_merge($this->where, (array)$settings['where'])); } if (isset($settings['parameters']) === true) { $this->setParameters(array_merge($this->parameters, (array)$settings['parameters'])); } // Push middleware if multiple if (isset($settings['middleware']) === true) { $this->setMiddlewares(array_merge((array)$settings['middleware'], $this->middlewares)); } if (isset($settings['defaultParameterRegex']) === true) { $this->setDefaultParameterRegex($settings['defaultParameterRegex']); } return $this; } /** * Get parameter names. * * @return array */ public function getWhere(): array { return $this->where; } /** * Set parameter names. * * @param array $options * @return static */ public function setWhere(array $options): IRoute { $this->where = $options; return $this; } /** * Add regular expression parameter match. * Alias for LoadableRoute::where() * * @param array $options * @return static * @see LoadableRoute::where() */ public function where(array $options) { return $this->setWhere($options); } /** * Get parameters * * @return array */ public function getParameters(): array { /* Sort the parameters after the user-defined param order, if any */ $parameters = []; if (count($this->originalParameters) !== 0) { $parameters = $this->originalParameters; } return array_merge($parameters, $this->parameters); } /** * Get parameters * * @param array $parameters * @return static */ public function setParameters(array $parameters): IRoute { $this->parameters = array_merge($this->parameters, $parameters); return $this; } /** * Add middleware class-name * * @param string $middleware * @return static * @deprecated This method is deprecated and will be removed in the near future. */ public function setMiddleware(string $middleware): self { $this->middlewares[] = $middleware; return $this; } /** * Add middleware class-name * * @param string $middleware * @return static */ public function addMiddleware(string $middleware): IRoute { $this->middlewares[] = $middleware; return $this; } /** * Set middlewares array * * @param array $middlewares * @return static */ public function setMiddlewares(array $middlewares): IRoute { $this->middlewares = $middlewares; return $this; } /** * @return array */ public function getMiddlewares(): array { return $this->middlewares; } /** * Set default regular expression used when matching parameters. * This is used when no custom parameter regex is found. * * @param string $regex * @return static */ public function setDefaultParameterRegex(string $regex): self { $this->defaultParameterRegex = $regex; return $this; } /** * Get default regular expression used when matching parameters. * * @return string */ public function getDefaultParameterRegex(): string { return $this->defaultParameterRegex; } /** * If enabled parameters containing null-value will not be passed along to the callback. * * @param bool $enabled * @return static $this */ public function setFilterEmptyParams(bool $enabled): IRoute { $this->filterEmptyParams = $enabled; return $this; } /** * Status if filtering of empty params is enabled or disabled * @return bool */ public function getFilterEmptyParams(): bool { return $this->filterEmptyParams; } }PK!2ĥEE*Pecee/SimpleRouter/Route/LoadableRoute.phpnu[debug('Loading middlewares'); foreach ($this->getMiddlewares() as $middleware) { if (is_object($middleware) === false) { $middleware = $router->getClassLoader()->loadClass($middleware); } if (($middleware instanceof IMiddleware) === false) { throw new HttpException($middleware . ' must be inherit the IMiddleware interface'); } $className = get_class($middleware); $router->debug('Loading middleware "%s"', $className); $middleware->handle($request); $router->debug('Finished loading middleware "%s"', $className); } $router->debug('Finished loading middlewares'); } public function matchRegex(Request $request, $url): ?bool { /* Match on custom defined regular expression */ if ($this->regex === null) { return null; } $parameters = []; if ((bool)preg_match($this->regex, $url, $parameters) !== false) { $this->setParameters($parameters); return true; } return false; } /** * Set url * * @param string $url * @return static */ public function setUrl(string $url): ILoadableRoute { $this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/'; if (strpos($this->url, $this->paramModifiers[0]) !== false) { $regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]); if ((bool)preg_match_all('/' . $regex . '/u', $this->url, $matches) !== false) { $this->parameters = array_fill_keys($matches[1], null); } } return $this; } /** * Prepends url while ensuring that the url has the correct formatting. * * @param string $url * @return ILoadableRoute */ public function prependUrl(string $url): ILoadableRoute { return $this->setUrl(rtrim($url, '/') . $this->url); } public function getUrl(): string { return $this->url; } /** * Returns true if group is defined and matches the given url. * * @param string $url * @param Request $request * @return bool */ protected function matchGroup(string $url, Request $request): bool { return ($this->getGroup() === null || $this->getGroup()->matchRoute($url, $request) === true); } /** * Find url that matches method, parameters or name. * Used when calling the url() helper. * * @param string|null $method * @param string|array|null $parameters * @param string|null $name * @return string */ public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string { $url = $this->getUrl(); $group = $this->getGroup(); if ($group !== null && count($group->getDomains()) !== 0) { $url = '//' . $group->getDomains()[0] . $url; } /* Create the param string - {parameter} */ $param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1]; /* Create the param string with the optional symbol - {parameter?} */ $param2 = $this->paramModifiers[0] . '%s' . $this->paramOptionalSymbol . $this->paramModifiers[1]; /* Replace any {parameter} in the url with the correct value */ $params = $this->getParameters(); foreach (array_keys($params) as $param) { if ($parameters === '' || (is_array($parameters) === true && count($parameters) === 0)) { $value = ''; } else { $p = (array)$parameters; $value = array_key_exists($param, $p) ? $p[$param] : $params[$param]; /* If parameter is specifically set to null - use the original-defined value */ if ($value === null && isset($this->originalParameters[$param]) === true) { $value = $this->originalParameters[$param]; } } if (stripos($url, $param1) !== false || stripos($url, $param) !== false) { /* Add parameter to the correct position */ $url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url); } else { /* Parameter aren't recognized and will be appended at the end of the url */ $url .= $value . '/'; } } return rtrim('/' . ltrim($url, '/'), '/') . '/'; } /** * Returns the provided name for the router. * * @return string */ public function getName(): ?string { return $this->name; } /** * Check if route has given name. * * @param string $name * @return bool */ public function hasName(string $name): bool { return strtolower($this->name) === strtolower($name); } /** * Add regular expression match for the entire route. * * @param string $regex * @return static */ public function setMatch(string $regex): ILoadableRoute { $this->regex = $regex; return $this; } /** * Get regular expression match used for matching route (if defined). * * @return string */ public function getMatch(): string { return $this->regex; } /** * Sets the router name, which makes it easier to obtain the url or router at a later point. * Alias for LoadableRoute::setName(). * * @param string|array $name * @return static * @see LoadableRoute::setName() */ public function name($name): ILoadableRoute { return $this->setName($name); } /** * Sets the router name, which makes it easier to obtain the url or router at a later point. * * @param string $name * @return static */ public function setName(string $name): ILoadableRoute { $this->name = $name; return $this; } /** * Merge with information from another route. * * @param array $settings * @param bool $merge * @return static */ public function setSettings(array $settings, bool $merge = false): IRoute { if (isset($settings['as']) === true) { $name = $settings['as']; if ($this->name !== null && $merge !== false) { $name .= '.' . $this->name; } $this->setName($name); } if (isset($settings['prefix']) === true) { $this->prependUrl($settings['prefix']); } return parent::setSettings($settings, $merge); } }PK!|PE(Pecee/SimpleRouter/Route/IGroupRoute.phpnu[setUrl($url); $this->setName(trim(str_replace('/', '.', $url), '/')); $this->controller = $controller; } /** * Check if route has given name. * * @param string $name * @return bool */ public function hasName(string $name): bool { if ($this->name === null) { return false; } /* Remove method/type */ if (strpos($name, '.') !== false) { $method = substr($name, strrpos($name, '.') + 1); $newName = substr($name, 0, strrpos($name, '.')); if (in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) { return true; } } return parent::hasName($name); } /** * @param string|null $method * @param string|array|null $parameters * @param string|null $name * @return string */ public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string { if (strpos($name, '.') !== false) { $found = array_search(substr($name, strrpos($name, '.') + 1), $this->names, true); if ($found !== false) { $method = (string)$found; } } $url = ''; $parameters = (array)$parameters; if ($method !== null) { /* Remove requestType from method-name, if it exists */ foreach (Request::$requestTypes as $requestType) { if (stripos($method, $requestType) === 0) { $method = substr($method, strlen($requestType)); break; } } $method .= '/'; } $group = $this->getGroup(); if ($group !== null && count($group->getDomains()) !== 0) { $url .= '//' . $group->getDomains()[0]; } $url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters); return '/' . trim($url, '/') . '/'; } public function matchRoute(string $url, Request $request): bool { if ($this->matchGroup($url, $request) === false) { return false; } /* Match global regular-expression for route */ $regexMatch = $this->matchRegex($request, $url); if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) { return false; } $strippedUrl = trim(str_ireplace($this->url, '/', $url), '/'); $path = explode('/', $strippedUrl); if (count($path) !== 0) { $method = (isset($path[0]) === false || trim($path[0]) === '') ? $this->defaultMethod : $path[0]; $this->method = $request->getMethod() . ucfirst($method); $this->parameters = array_slice($path, 1); // Set callback $this->setCallback([$this->controller, $this->method]); return true; } return false; } /** * Get controller class-name. * * @return string */ public function getController(): string { return $this->controller; } /** * Get controller class-name. * * @param string $controller * @return static */ public function setController(string $controller): IControllerRoute { $this->controller = $controller; return $this; } /** * Return active method * * @return string|null */ public function getMethod(): ?string { return $this->method; } /** * Set active method * * @param string $method * @return static */ public function setMethod(string $method): IRoute { $this->method = $method; return $this; } /** * Merge with information from another route. * * @param array $settings * @param bool $merge * @return static */ public function setSettings(array $settings, bool $merge = false): IRoute { if (isset($settings['names']) === true) { $this->names = $settings['names']; } return parent::setSettings($settings, $merge); } }PK!0ixx.Pecee/SimpleRouter/Route/RoutePartialGroup.phpnu[ '', 'create' => 'create', 'store' => '', 'show' => '', 'edit' => 'edit', 'update' => '', 'destroy' => '', ]; protected $methodNames = [ 'index' => 'index', 'create' => 'create', 'store' => 'store', 'show' => 'show', 'edit' => 'edit', 'update' => 'update', 'destroy' => 'destroy', ]; protected $names = []; protected $controller; public function __construct($url, $controller) { $this->setUrl($url); $this->controller = $controller; $this->setName(trim(str_replace('/', '.', $url), '/')); } /** * Check if route has given name. * * @param string $name * @return bool */ public function hasName(string $name): bool { if ($this->name === null) { return false; } if (strtolower($this->name) === strtolower($name)) { return true; } /* Remove method/type */ if (strpos($name, '.') !== false) { $name = substr($name, 0, strrpos($name, '.')); } return (strtolower($this->name) === strtolower($name)); } /** * @param string|null $method * @param array|string|null $parameters * @param string|null $name * @return string */ public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string { $url = array_search($name, $this->names, true); if ($url !== false) { return rtrim($this->url . $this->urls[$url], '/') . '/'; } return $this->url; } protected function call($method): bool { $this->setCallback([$this->controller, $method]); return true; } public function matchRoute(string $url, Request $request): bool { if ($this->matchGroup($url, $request) === false) { return false; } /* Match global regular-expression for route */ $regexMatch = $this->matchRegex($request, $url); if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) { return false; } $route = rtrim($this->url, '/') . '/{id?}/{action?}'; /* Parse parameters from current route */ $this->parameters = $this->parseParameters($route, $url); /* If no custom regular expression or parameters was found on this route, we stop */ if ($regexMatch === null && $this->parameters === null) { return false; } $action = strtolower(trim($this->parameters['action'])); $id = $this->parameters['id']; // Remove action parameter unset($this->parameters['action']); $method = $request->getMethod(); // Delete if ($method === Request::REQUEST_TYPE_DELETE && $id !== null) { return $this->call($this->methodNames['destroy']); } // Update if ($id !== null && in_array($method, [Request::REQUEST_TYPE_PATCH, Request::REQUEST_TYPE_PUT], true) === true) { return $this->call($this->methodNames['update']); } // Edit if ($method === Request::REQUEST_TYPE_GET && $id !== null && $action === 'edit') { return $this->call($this->methodNames['edit']); } // Create if ($method === Request::REQUEST_TYPE_GET && $id === 'create') { return $this->call($this->methodNames['create']); } // Save if ($method === Request::REQUEST_TYPE_POST) { return $this->call($this->methodNames['store']); } // Show if ($method === Request::REQUEST_TYPE_GET && $id !== null) { return $this->call($this->methodNames['show']); } // Index return $this->call($this->methodNames['index']); } /** * @return string */ public function getController(): string { return $this->controller; } /** * @param string $controller * @return static */ public function setController(string $controller): IControllerRoute { $this->controller = $controller; return $this; } public function setName(string $name): ILoadableRoute { $this->name = $name; $this->names = [ 'index' => $this->name . '.index', 'create' => $this->name . '.create', 'store' => $this->name . '.store', 'show' => $this->name . '.show', 'edit' => $this->name . '.edit', 'update' => $this->name . '.update', 'destroy' => $this->name . '.destroy', ]; return $this; } /** * Define custom method name for resource controller * * @param array $names * @return static $this */ public function setMethodNames(array $names): RouteResource { $this->methodNames = $names; return $this; } /** * Get method names * * @return array */ public function getMethodNames(): array { return $this->methodNames; } /** * Merge with information from another route. * * @param array $settings * @param bool $merge * @return static */ public function setSettings(array $settings, bool $merge = false): IRoute { if (isset($settings['names']) === true) { $this->names = $settings['names']; } if (isset($settings['methods']) === true) { $this->methodNames = $settings['methods']; } return parent::setSettings($settings, $merge); } }PK! kk/Pecee/SimpleRouter/Exceptions/HttpException.phpnu[class = $class; $this->method = $method; } /** * Get class name * @return string */ public function getClass(): string { return $this->class; } /** * Get method * @return string|null */ public function getMethod(): ?string { return $this->method; } }PK!^gg7Pecee/SimpleRouter/Exceptions/NotFoundHttpException.phpnu[reset(); } /** * Resets the router by reloading request and clearing all routes and data. */ public function reset(): void { $this->debugStartTime = microtime(true); $this->isProcessingRoute = false; try { $this->request = new Request(); } catch (MalformedUrlException $e) { $this->debug(sprintf('Invalid request-uri url: %s', $e->getMessage())); } $this->routes = []; $this->bootManagers = []; $this->routeStack = []; $this->processedRoutes = []; $this->exceptionHandlers = []; $this->loadedExceptionHandlers = []; $this->eventHandlers = []; $this->debugList = []; $this->csrfVerifier = null; $this->classLoader = new ClassLoader(); } /** * Add route * @param IRoute $route * @return IRoute */ public function addRoute(IRoute $route): IRoute { $this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [ 'route' => $route, 'isSubRoute' => $this->isProcessingRoute, ]); /* * If a route is currently being processed, that means that the route being added are rendered from the parent * routes callback, so we add them to the stack instead. */ if ($this->isProcessingRoute === true) { $this->routeStack[] = $route; } else { $this->routes[] = $route; } return $route; } /** * Render and process any new routes added. * * @param IRoute $route * @throws NotFoundHttpException */ protected function renderAndProcess(IRoute $route): void { $this->isProcessingRoute = true; $route->renderRoute($this->request, $this); $this->isProcessingRoute = false; if (count($this->routeStack) !== 0) { /* Pop and grab the routes added when executing group callback earlier */ $stack = $this->routeStack; $this->routeStack = []; /* Route any routes added to the stack */ $this->processRoutes($stack, ($route instanceof IGroupRoute) ? $route : null); } } /** * Process added routes. * * @param array|IRoute[] $routes * @param IGroupRoute|null $group * @throws NotFoundHttpException */ protected function processRoutes(array $routes, ?IGroupRoute $group = null): void { $this->debug('Processing routes'); // Stop processing routes if no valid route is found. if ($this->request->getRewriteRoute() === null && $this->request->getUrl()->getOriginalUrl() === '') { $this->debug('Halted route-processing as no valid route was found'); return; } $url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath(); // Loop through each route-request foreach ($routes as $route) { $this->debug('Processing route "%s"', get_class($route)); if ($group !== null) { /* Add the parent group */ $route->setGroup($group); } /* @var $route IGroupRoute */ if ($route instanceof IGroupRoute) { if ($route->matchRoute($url, $this->request) === true) { /* Add exception handlers */ if (count($route->getExceptionHandlers()) !== 0) { if ($route->getMergeExceptionHandlers() === true) { foreach ($route->getExceptionHandlers() as $handler) { $this->exceptionHandlers[] = $handler; } } else { $this->exceptionHandlers = $route->getExceptionHandlers(); } } /* Only render partial group if it matches */ if ($route instanceof IPartialGroupRoute === true) { $this->renderAndProcess($route); continue; } } if ($route instanceof IPartialGroupRoute === false) { $this->renderAndProcess($route); } continue; } if ($route instanceof ILoadableRoute === true) { /* Add the route to the map, so we can find the active one when all routes has been loaded */ $this->processedRoutes[] = $route; } } } /** * Load routes * @return void * @throws NotFoundHttpException */ public function loadRoutes(): void { $this->debug('Loading routes'); $this->fireEvents(EventHandler::EVENT_LOAD_ROUTES, [ 'routes' => $this->routes, ]); /* Loop through each route-request */ $this->processRoutes($this->routes); $this->fireEvents(EventHandler::EVENT_BOOT, [ 'bootmanagers' => $this->bootManagers, ]); /* Initialize boot-managers */ /* @var $manager IRouterBootManager */ foreach ($this->bootManagers as $manager) { $className = get_class($manager); $this->debug('Rendering bootmanager "%s"', $className); $this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [ 'bootmanagers' => $this->bootManagers, 'bootmanager' => $manager, ]); /* Render bootmanager */ $manager->boot($this, $this->request); $this->debug('Finished rendering bootmanager "%s"', $className); } $this->debug('Finished loading routes'); } /** * Start the routing * * @return string|null * @throws NotFoundHttpException * @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException * @throws HttpException * @throws Exception */ public function start(): ?string { $this->debug('Router starting'); $this->fireEvents(EventHandler::EVENT_INIT); $this->loadRoutes(); if ($this->csrfVerifier !== null) { $this->fireEvents(EventHandler::EVENT_RENDER_CSRF, [ 'csrfVerifier' => $this->csrfVerifier, ]); /* Verify csrf token for request */ $this->csrfVerifier->handle($this->request); } $output = $this->routeRequest(); $this->fireEvents(EventHandler::EVENT_LOAD, [ 'loadedRoutes' => $this->getRequest()->getLoadedRoutes(), ]); $this->debug('Routing complete'); return $output; } /** * Routes the request * * @return string|null * @throws HttpException * @throws Exception */ public function routeRequest(): ?string { $this->debug('Routing request'); $methodNotAllowed = null; try { $url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath(); /* @var $route ILoadableRoute */ foreach ($this->processedRoutes as $key => $route) { $this->debug('Matching route "%s"', get_class($route)); /* If the route matches */ if ($route->matchRoute($url, $this->request) === true) { $this->fireEvents(EventHandler::EVENT_MATCH_ROUTE, [ 'route' => $route, ]); /* Check if request method matches */ if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) { $this->debug('Method "%s" not allowed', $this->request->getMethod()); // Only set method not allowed is not already set if ($methodNotAllowed === null) { $methodNotAllowed = true; } continue; } $this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [ 'route' => $route, 'middlewares' => $route->getMiddlewares(), ]); $route->loadMiddleware($this->request, $this); $output = $this->handleRouteRewrite($key, $url); if ($output !== null) { return $output; } $methodNotAllowed = false; $this->request->addLoadedRoute($route); $this->fireEvents(EventHandler::EVENT_RENDER_ROUTE, [ 'route' => $route, ]); $routeOutput = $route->renderRoute($this->request, $this); if ($this->renderMultipleRoutes === true) { if ($routeOutput !== null) { return $routeOutput; } $output = $this->handleRouteRewrite($key, $url); if ($output !== null) { return $output; } } else { $output = $this->handleRouteRewrite($key, $url); return $output ?? $routeOutput; } } } } catch (Exception $e) { $this->handleException($e); } if ($methodNotAllowed === true) { $message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUrl()->getPath(), $this->request->getMethod()); $this->handleException(new NotFoundHttpException($message, 403)); } if (count($this->request->getLoadedRoutes()) === 0) { $rewriteUrl = $this->request->getRewriteUrl(); if ($rewriteUrl !== null) { $message = sprintf('Route not found: "%s" (rewrite from: "%s")', $rewriteUrl, $this->request->getUrl()->getPath()); } else { $message = sprintf('Route not found: "%s"', $this->request->getUrl()->getPath()); } $this->debug($message); return $this->handleException(new NotFoundHttpException($message, 404)); } return null; } /** * Handle route-rewrite * * @param string $key * @param string $url * @return string|null * @throws HttpException * @throws Exception */ protected function handleRouteRewrite(string $key, string $url): ?string { /* If the request has changed */ if ($this->request->hasPendingRewrite() === false) { return null; } $route = $this->request->getRewriteRoute(); if ($route !== null) { /* Add rewrite route */ $this->processedRoutes[] = $route; } if ($this->request->getRewriteUrl() !== $url) { unset($this->processedRoutes[$key]); $this->request->setHasPendingRewrite(false); $this->fireEvents(EventHandler::EVENT_REWRITE, [ 'rewriteUrl' => $this->request->getRewriteUrl(), 'rewriteRoute' => $this->request->getRewriteRoute(), ]); return $this->routeRequest(); } return null; } /** * @param Exception $e * @return string|null * @throws Exception * @throws HttpException */ protected function handleException(Exception $e): ?string { $this->debug('Starting exception handling for "%s"', get_class($e)); $this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [ 'exception' => $e, 'exceptionHandlers' => $this->exceptionHandlers, ]); /* @var $handler IExceptionHandler */ foreach (array_reverse($this->exceptionHandlers) as $key => $handler) { if (is_object($handler) === false) { $handler = new $handler(); } $this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [ 'exception' => $e, 'exceptionHandler' => $handler, 'exceptionHandlers' => $this->exceptionHandlers, ]); $this->debug('Processing exception-handler "%s"', get_class($handler)); if (($handler instanceof IExceptionHandler) === false) { throw new HttpException('Exception handler must implement the IExceptionHandler interface.', 500); } try { $this->debug('Start rendering exception handler'); $handler->handleError($this->request, $e); $this->debug('Finished rendering exception-handler'); if (isset($this->loadedExceptionHandlers[$key]) === false && $this->request->hasPendingRewrite() === true) { $this->loadedExceptionHandlers[$key] = $handler; $this->debug('Exception handler contains rewrite, reloading routes'); $this->fireEvents(EventHandler::EVENT_REWRITE, [ 'rewriteUrl' => $this->request->getRewriteUrl(), 'rewriteRoute' => $this->request->getRewriteRoute(), ]); if ($this->request->getRewriteRoute() !== null) { $this->processedRoutes[] = $this->request->getRewriteRoute(); } return $this->routeRequest(); } } catch (Exception $e) { } $this->debug('Finished processing'); } $this->debug('Finished exception handling - exception not handled, throwing'); throw $e; } /** * Find route by alias, class, callback or method. * * @param string $name * @return ILoadableRoute|null */ public function findRoute(string $name): ?ILoadableRoute { $this->debug('Finding route by name "%s"', $name); $this->fireEvents(EventHandler::EVENT_FIND_ROUTE, [ 'name' => $name, ]); foreach ($this->processedRoutes as $route) { /* Check if the name matches with a name on the route. Should match either router alias or controller alias. */ if ($route->hasName($name) === true) { $this->debug('Found route "%s" by name "%s"', $route->getUrl(), $name); return $route; } /* Direct match to controller */ if ($route instanceof IControllerRoute && strtoupper($route->getController()) === strtoupper($name)) { $this->debug('Found route "%s" by controller "%s"', $route->getUrl(), $name); return $route; } /* Using @ is most definitely a controller@method or alias@method */ if (strpos($name, '@') !== false) { [$controller, $method] = array_map('strtolower', explode('@', $name)); if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) { $this->debug('Found route "%s" by controller "%s" and method "%s"', $route->getUrl(), $controller, $method); return $route; } } /* Check if callback matches (if it's not a function) */ $callback = $route->getCallback(); if (is_string($callback) === true && is_callable($callback) === false && strpos($name, '@') !== false && strpos($callback, '@') !== false) { /* Check if the entire callback is matching */ if (strpos($callback, $name) === 0 || strtolower($callback) === strtolower($name)) { $this->debug('Found route "%s" by callback "%s"', $route->getUrl(), $name); return $route; } /* Check if the class part of the callback matches (class@method) */ if (strtolower($name) === strtolower($route->getClass())) { $this->debug('Found route "%s" by class "%s"', $route->getUrl(), $name); return $route; } } } $this->debug('Route not found'); return null; } /** * Get url for a route by using either name/alias, class or method name. * * The name parameter supports the following values: * - Route name * - Controller/resource name (with or without method) * - Controller class name * * When searching for controller/resource by name, you can use this syntax "route.name@method". * You can also use the same syntax when searching for a specific controller-class "MyController@home". * If no arguments is specified, it will return the url for the current loaded route. * * @param string|null $name * @param string|array|null $parameters * @param array|null $getParams * @return Url * @throws InvalidArgumentException */ public function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url { $this->debug('Finding url', func_get_args()); $this->fireEvents(EventHandler::EVENT_GET_URL, [ 'name' => $name, 'parameters' => $parameters, 'getParams' => $getParams, ]); if ($name === '' && $parameters === '') { return new Url('/'); } /* Only merge $_GET when all parameters are null */ $getParams = ($name === null && $parameters === null && $getParams === null) ? $_GET : (array)$getParams; /* Return current route if no options has been specified */ if ($name === null && $parameters === null) { return $this->request ->getUrlCopy() ->setParams($getParams); } $loadedRoute = $this->request->getLoadedRoute(); /* If nothing is defined and a route is loaded we use that */ if ($name === null && $loadedRoute !== null) { return $this->request ->getUrlCopy() ->setPath($loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name)) ->setParams($getParams); } if ($name !== null) { /* We try to find a match on the given name */ $route = $this->findRoute($name); if ($route !== null) { return $this->request ->getUrlCopy() ->setPath($route->findUrl($route->getMethod(), $parameters, $name)) ->setParams($getParams); } } /* Using @ is most definitely a controller@method or alias@method */ if (is_string($name) === true && strpos($name, '@') !== false) { [$controller, $method] = explode('@', $name); /* Loop through all the routes to see if we can find a match */ /* @var $route ILoadableRoute */ foreach ($this->processedRoutes as $processedRoute) { /* Check if the route contains the name/alias */ if ($processedRoute->hasName($controller) === true) { return $this->request ->getUrlCopy() ->setPath($processedRoute->findUrl($method, $parameters, $name)) ->setParams($getParams); } /* Check if the route controller is equal to the name */ if ($processedRoute instanceof IControllerRoute && strtolower($processedRoute->getController()) === strtolower($controller)) { return $this->request ->getUrlCopy() ->setPath($processedRoute->findUrl($method, $parameters, $name)) ->setParams($getParams); } } } /* No result so we assume that someone is using a hardcoded url and join everything together. */ $url = trim(implode('/', array_merge((array)$name, (array)$parameters)), '/'); $url = (($url === '') ? '/' : '/' . $url . '/'); return $this->request ->getUrlCopy() ->setPath($url) ->setParams($getParams); } /** * Get BootManagers * @return array */ public function getBootManagers(): array { return $this->bootManagers; } /** * Set BootManagers * * @param array $bootManagers * @return static */ public function setBootManagers(array $bootManagers): self { $this->bootManagers = $bootManagers; return $this; } /** * Add BootManager * * @param IRouterBootManager $bootManager * @return static */ public function addBootManager(IRouterBootManager $bootManager): self { $this->bootManagers[] = $bootManager; return $this; } /** * Get routes that has been processed. * * @return array */ public function getProcessedRoutes(): array { return $this->processedRoutes; } /** * @return array */ public function getRoutes(): array { return $this->routes; } /** * Set routes * * @param array $routes * @return static */ public function setRoutes(array $routes): self { $this->routes = $routes; return $this; } /** * Get current request * * @return Request */ public function getRequest(): Request { return $this->request; } /** * Get csrf verifier class * @return BaseCsrfVerifier */ public function getCsrfVerifier(): ?BaseCsrfVerifier { return $this->csrfVerifier; } /** * Set csrf verifier class * * @param BaseCsrfVerifier $csrfVerifier */ public function setCsrfVerifier(BaseCsrfVerifier $csrfVerifier): void { $this->csrfVerifier = $csrfVerifier; } /** * Set class loader * * @param IClassLoader $loader */ public function setClassLoader(IClassLoader $loader): void { $this->classLoader = $loader; } /** * Get class loader * * @return IClassLoader */ public function getClassLoader(): IClassLoader { return $this->classLoader; } /** * Register event handler * * @param IEventHandler $handler */ public function addEventHandler(IEventHandler $handler): void { $this->eventHandlers[] = $handler; } /** * Get registered event-handler. * * @return array */ public function getEventHandlers(): array { return $this->eventHandlers; } /** * Fire event in event-handler. * * @param string $name * @param array $arguments */ protected function fireEvents(string $name, array $arguments = []): void { if (count($this->eventHandlers) === 0) { return; } /* @var IEventHandler $eventHandler */ foreach ($this->eventHandlers as $eventHandler) { $eventHandler->fireEvents($this, $name, $arguments); } } /** * Add new debug message * @param string $message * @param array $args */ public function debug(string $message, ...$args): void { if ($this->debugEnabled === false) { return; } $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $this->debugList[] = [ 'message' => vsprintf($message, $args), 'time' => number_format(microtime(true) - $this->debugStartTime, 10), 'trace' => end($trace), ]; } /** * Enable or disables debugging * * @param bool $enabled * @return static */ public function setDebugEnabled(bool $enabled): self { $this->debugEnabled = $enabled; return $this; } /** * Get the list containing all debug messages. * * @return array */ public function getDebugLog(): array { return $this->debugList; } /** * Changes the rendering behavior of the router. * When enabled the router will render all routes that matches. * When disabled the router will stop rendering at the first route that matches. * * @param bool $bool * @return $this */ public function setRenderMultipleRoutes(bool $bool): self { $this->renderMultipleRoutes = $bool; return $this; } public function addExceptionHandler(IExceptionHandler $handler): self { $this->exceptionHandlers[] = $handler; return $this; } }PK!н&)Pecee/Controllers/IResourceController.phpnu[originalUrl = $url; if ($url !== null && $url !== '/') { $data = $this->parseUrl($url); $this->scheme = $data['scheme'] ?? null; $this->host = $data['host'] ?? null; $this->port = $data['port'] ?? null; $this->username = $data['user'] ?? null; $this->password = $data['pass'] ?? null; if (isset($data['path']) === true) { $this->setPath($data['path']); } $this->fragment = $data['fragment'] ?? null; if (isset($data['query']) === true) { $this->setQueryString($data['query']); } } } /** * Check if url is using a secure protocol like https * * @return bool */ public function isSecure(): bool { return (strtolower($this->getScheme()) === 'https'); } /** * Checks if url is relative * * @return bool */ public function isRelative(): bool { return ($this->getHost() === null); } /** * Get url scheme * * @return string|null */ public function getScheme(): ?string { return $this->scheme; } /** * Set the scheme of the url * * @param string $scheme * @return static */ public function setScheme(string $scheme): self { $this->scheme = $scheme; return $this; } /** * Get url host * * @return string|null */ public function getHost(): ?string { return $this->host; } /** * Set the host of the url * * @param string $host * @return static */ public function setHost(string $host): self { $this->host = $host; return $this; } /** * Get url port * * @return int|null */ public function getPort(): ?int { return ($this->port !== null) ? (int)$this->port : null; } /** * Set the port of the url * * @param int $port * @return static */ public function setPort(int $port): self { $this->port = $port; return $this; } /** * Parse username from url * * @return string|null */ public function getUsername(): ?string { return $this->username; } /** * Set the username of the url * * @param string $username * @return static */ public function setUsername(string $username): self { $this->username = $username; return $this; } /** * Parse password from url * @return string|null */ public function getPassword(): ?string { return $this->password; } /** * Set the url password * * @param string $password * @return static */ public function setPassword(string $password): self { $this->password = $password; return $this; } /** * Get path from url * @return string */ public function getPath(): ?string { return $this->path ?? '/'; } /** * Set the url path * * @param string $path * @return static */ public function setPath(string $path): self { $this->path = rtrim($path, '/') . '/'; return $this; } /** * Get query-string from url * * @return array */ public function getParams(): array { return $this->params; } /** * Merge parameters array * * @param array $params * @return static */ public function mergeParams(array $params): self { return $this->setParams(array_merge($this->getParams(), $params)); } /** * Set the url params * * @param array $params * @return static */ public function setParams(array $params): self { $this->params = $params; return $this; } /** * Set raw query-string parameters as string * * @param string $queryString * @return static */ public function setQueryString(string $queryString): self { $params = []; parse_str($queryString, $params); if(count($params) > 0) { return $this->setParams($params); } return $this; } /** * Get query-string params as string * * @return string */ public function getQueryString(): string { return static::arrayToParams($this->getParams()); } /** * Get fragment from url (everything after #) * * @return string|null */ public function getFragment(): ?string { return $this->fragment; } /** * Set url fragment * * @param string $fragment * @return static */ public function setFragment(string $fragment): self { $this->fragment = $fragment; return $this; } /** * @return string */ public function getOriginalUrl(): string { return $this->originalUrl; } /** * Get position of value. * Returns -1 on failure. * * @param string $value * @return int */ public function indexOf(string $value): int { $index = stripos($this->getOriginalUrl(), $value); return ($index === false) ? -1 : $index; } /** * Check if url contains value. * * @param string $value * @return bool */ public function contains(string $value): bool { return (stripos($this->getOriginalUrl(), $value) !== false); } /** * Check if url contains parameter/query string. * * @param string $name * @return bool */ public function hasParam(string $name): bool { return array_key_exists($name, $this->getParams()); } /** * Removes multiple parameters from the query-string * * @param array ...$names * @return static */ public function removeParams(...$names): self { $params = array_diff_key($this->getParams(), array_flip(...$names)); $this->setParams($params); return $this; } /** * Removes parameter from the query-string * * @param string $name * @return static */ public function removeParam(string $name): self { $params = $this->getParams(); unset($params[$name]); $this->setParams($params); return $this; } /** * Get parameter by name. * Returns parameter value or default value. * * @param string $name * @param string|null $defaultValue * @return string|null */ public function getParam(string $name, ?string $defaultValue = null): ?string { return (isset($this->getParams()[$name]) === true) ? $this->getParams()[$name] : $defaultValue; } /** * UTF-8 aware parse_url() replacement. * @param string $url * @param int $component * @return array * @throws MalformedUrlException */ public function parseUrl(string $url, int $component = -1): array { $encodedUrl = preg_replace_callback( '/[^:\/@?&=#]+/u', static function ($matches): string { return urlencode($matches[0]); }, $url ); $parts = parse_url($encodedUrl, $component); if ($parts === false) { throw new MalformedUrlException(sprintf('Failed to parse url: "%s"', $url)); } return array_map('urldecode', $parts); } /** * Convert array to query-string params * * @param array $getParams * @param bool $includeEmpty * @return string */ public static function arrayToParams(array $getParams = [], bool $includeEmpty = true): string { if (count($getParams) !== 0) { if ($includeEmpty === false) { $getParams = array_filter($getParams, static function ($item): bool { return (trim($item) !== ''); }); } return http_build_query($getParams); } return ''; } /** * Returns the relative url * * @param bool $includeParams * @return string */ public function getRelativeUrl(bool $includeParams = true): string { $path = $this->path ?? '/'; if($includeParams === false) { return $path; } $query = $this->getQueryString() !== '' ? '?' . $this->getQueryString() : ''; $fragment = $this->fragment !== null ? '#' . $this->fragment : ''; return $path . $query . $fragment; } /** * Returns the absolute url * * @param bool $includeParams * @return string */ public function getAbsoluteUrl(bool $includeParams = true): string { $scheme = $this->scheme !== null ? $this->scheme . '://' : ''; $host = $this->host ?? ''; $port = $this->port !== null ? ':' . $this->port : ''; $user = $this->username ?? ''; $pass = $this->password !== null ? ':' . $this->password : ''; $pass = ($user !== '' || $pass !== '') ? $pass . '@' : ''; return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl($includeParams); } /** * Specify data which should be serialized to JSON * @link http://php.net/manual/en/jsonserializable.jsonserialize.php * @return string data which can be serialized by json_encode, * which is a value of any type other than a resource. * @since 5.4.0 */ public function jsonSerialize(): string { return $this->getRelativeUrl(); } public function __toString(): string { return $this->getRelativeUrl(); } }PK!2+ + Pecee/Http/Response.phpnu[request = $request; } /** * Set the http status code * * @param int $code * @return static */ public function httpCode(int $code): self { http_response_code($code); return $this; } /** * Redirect the response * * @param string $url * @param ?int $httpCode */ public function redirect(string $url, ?int $httpCode = null): void { if ($httpCode !== null) { $this->httpCode($httpCode); } $this->header('location: ' . $url); exit(0); } public function refresh(): void { $this->redirect($this->request->getUrl()->getOriginalUrl()); } /** * Add http authorisation * @param string $name * @return static */ public function auth(string $name = ''): self { $this->headers([ 'WWW-Authenticate: Basic realm="' . $name . '"', 'HTTP/1.0 401 Unauthorized', ]); return $this; } public function cache(string $eTag, int $lastModifiedTime = 2592000): self { $this->headers([ 'Cache-Control: public', sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)), sprintf('Etag: %s', $eTag), ]); $httpModified = $this->request->getHeader('http-if-modified-since'); $httpIfNoneMatch = $this->request->getHeader('http-if-none-match'); if (($httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) || ($httpModified !== null && strtotime($httpModified) === $lastModifiedTime)) { $this->header('HTTP/1.1 304 Not Modified'); exit(0); } return $this; } /** * Json encode * @param array|JsonSerializable $value * @param ?int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR. * @param int $dept JSON debt. * @throws InvalidArgumentException */ public function json($value, ?int $options = null, int $dept = 512): void { if (($value instanceof JsonSerializable) === false && is_array($value) === false) { throw new InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.'); } $this->header('Content-Type: application/json; charset=utf-8'); echo json_encode($value, $options, $dept); exit(0); } /** * Add header to response * @param string $value * @return static */ public function header(string $value): self { header($value); return $this; } /** * Add multiple headers to response * @param array $headers * @return static */ public function headers(array $headers): self { foreach ($headers as $header) { $this->header($header); } return $this; } }PK!OT T *Pecee/Http/Middleware/BaseCsrfVerifier.phpnu[tokenProvider = new CookieTokenProvider(); } /** * Check if the url matches the urls in the except property * @param Request $request * @return bool */ protected function skip(Request $request): bool { if ($this->except === null || count($this->except) === 0) { return false; } foreach($this->except as $url) { $url = rtrim($url, '/'); if ($url[strlen($url) - 1] === '*') { $url = rtrim($url, '*'); $skip = $request->getUrl()->contains($url); } else { $skip = ($url === rtrim($request->getUrl()->getRelativeUrl(false), '/')); } if ($skip === true) { if(is_array($this->include) === true && count($this->include) > 0) { foreach($this->include as $includeUrl) { $includeUrl = rtrim($includeUrl, '/'); if ($includeUrl[strlen($includeUrl) - 1] === '*') { $includeUrl = rtrim($includeUrl, '*'); $skip = !$request->getUrl()->contains($includeUrl); break; } $skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/')); } } if($skip === false) { continue; } return true; } } return false; } /** * Handle request * * @param Request $request * @throws TokenMismatchException */ public function handle(Request $request): void { if ($this->skip($request) === false && $request->isPostBack() === true) { $token = $request->getInputHandler()->value( static::POST_KEY, $request->getHeader(static::HEADER_KEY), Request::$requestTypesPost ); if ($this->tokenProvider->validate((string)$token) === false) { throw new TokenMismatchException('Invalid CSRF-token.'); } } // Refresh existing token $this->tokenProvider->refresh(); } public function getTokenProvider(): ITokenProvider { return $this->tokenProvider; } /** * Set token provider * @param ITokenProvider $provider */ public function setTokenProvider(ITokenProvider $provider): void { $this->tokenProvider = $provider; } }PK!tww;Pecee/Http/Middleware/Exceptions/TokenMismatchException.phpnu[ipWhitelist, true) === true) { return true; } foreach ($this->ipBlacklist as $blackIp) { // Blocks range (8.8.*) if ($blackIp[strlen($blackIp) - 1] === '*' && strpos($ip, trim($blackIp, '*')) === 0) { return false; } // Blocks exact match if ($blackIp === $ip) { return false; } } return true; } /** * @param Request $request * @throws HttpException */ public function handle(Request $request): void { if($this->validate((string)$request->getIp()) === false) { throw new HttpException(sprintf('Restricted ip. Access to %s has been blocked', $request->getIp()), 403); } } }PK!N|%Pecee/Http/Middleware/IMiddleware.phpnu[ $value) { $this->headers[strtolower($key)] = $value; $this->headers[str_replace('_', '-', strtolower($key))] = $value; } $this->setHost($this->getHeader('http-host')); // Check if special IIS header exist, otherwise use default. $this->setUrl(new Url($this->getFirstHeader(['unencoded-url', 'request-uri']))); $this->setContentType((string)$this->getHeader('content-type')); $this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method'))); $this->inputHandler = new InputHandler($this); } public function isSecure(): bool { return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || (int)$this->getHeader('server-port') === 443; } /** * @return Url */ public function getUrl(): Url { return $this->url; } /** * Copy url object * * @return Url */ public function getUrlCopy(): Url { return clone $this->url; } /** * @return string|null */ public function getHost(): ?string { return $this->host; } /** * @return string|null */ public function getMethod(): ?string { return $this->method; } /** * Get http basic auth user * @return string|null */ public function getUser(): ?string { return $this->getHeader('php-auth-user'); } /** * Get http basic auth password * @return string|null */ public function getPassword(): ?string { return $this->getHeader('php-auth-pw'); } /** * Get the csrf token * @return string|null */ public function getCsrfToken(): ?string { return $this->getHeader(BaseCsrfVerifier::HEADER_KEY); } /** * Get all headers * @return array */ public function getHeaders(): array { return $this->headers; } /** * Get id address * If $safe is false, this function will detect Proxys. But the user can edit this header to whatever he wants! * https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php#comment-25086804 * @param bool $safeMode When enabled, only safe non-spoofable headers will be returned. Note this can cause issues when using proxy. * @return string|null */ public function getIp(bool $safeMode = false): ?string { $headers = ['remote-addr']; if($safeMode === false) { $headers = array_merge($headers, [ 'http-cf-connecting-ip', 'http-client-ip', 'http-x-forwarded-for', ]); } return $this->getFirstHeader($headers); } /** * Get remote address/ip * * @alias static::getIp * @return string|null */ public function getRemoteAddr(): ?string { return $this->getIp(); } /** * Get referer * @return string|null */ public function getReferer(): ?string { return $this->getHeader('http-referer'); } /** * Get user agent * @return string|null */ public function getUserAgent(): ?string { return $this->getHeader('http-user-agent'); } /** * Get header value by name * * @param string $name Name of the header. * @param string|mixed|null $defaultValue Value to be returned if header is not found. * @param bool $tryParse When enabled the method will try to find the header from both from client (http) and server-side variants, if the header is not found. * * @return string|null */ public function getHeader(string $name, $defaultValue = null, bool $tryParse = true): ?string { $name = strtolower($name); $header = $this->headers[$name] ?? null; if ($tryParse === true && $header === null) { if (strpos($name, 'http-') === 0) { // Trying to find client header variant which was not found, searching for header variant without http- prefix. $header = $this->headers[str_replace('http-', '', $name)] ?? null; } else { // Trying to find server variant which was not found, searching for client variant with http- prefix. $header = $this->headers['http-' . $name] ?? null; } } return $header ?? $defaultValue; } /** * Will try to find first header from list of headers. * * @param array $headers * @param mixed|null $defaultValue * @return mixed|null */ public function getFirstHeader(array $headers, $defaultValue = null) { foreach($headers as $header) { $header = $this->getHeader($header); if($header !== null) { return $header; } } return $defaultValue; } /** * Get request content-type * @return string|null */ public function getContentType(): ?string { return $this->contentType; } /** * Set request content-type * @param string $contentType * @return $this */ protected function setContentType(string $contentType): self { if(strpos($contentType, ';') > 0) { $this->contentType = strtolower(substr($contentType, 0, strpos($contentType, ';'))); } else { $this->contentType = strtolower($contentType); } return $this; } /** * Get input class * @return InputHandler */ public function getInputHandler(): InputHandler { return $this->inputHandler; } /** * Is format accepted * * @param string $format * * @return bool */ public function isFormatAccepted(string $format): bool { return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) !== false); } /** * Returns true if the request is made through Ajax * * @return bool */ public function isAjax(): bool { return (strtolower($this->getHeader('http-x-requested-with')) === 'xmlhttprequest'); } /** * Returns true when request-method is type that could contain data in the page body. * * @return bool */ public function isPostBack(): bool { return in_array($this->getMethod(), static::$requestTypesPost, true); } /** * Get accept formats * @return array */ public function getAcceptFormats(): array { return explode(',', $this->getHeader('http-accept')); } /** * @param Url $url */ public function setUrl(Url $url): void { $this->url = $url; if ($this->url->getHost() === null) { $this->url->setHost((string)$this->getHost()); } if($this->isSecure() === true) { $this->url->setScheme('https'); } } /** * @param string|null $host */ public function setHost(?string $host): void { $this->host = $host; } /** * @param string $method */ public function setMethod(string $method): void { $this->method = strtolower($method); } /** * Set rewrite route * * @param ILoadableRoute $route * @return static */ public function setRewriteRoute(ILoadableRoute $route): self { $this->hasPendingRewrite = true; $this->rewriteRoute = SimpleRouter::addDefaultNamespace($route); return $this; } /** * Get rewrite route * * @return ILoadableRoute|null */ public function getRewriteRoute(): ?ILoadableRoute { return $this->rewriteRoute; } /** * Get rewrite url * * @return string|null */ public function getRewriteUrl(): ?string { return $this->rewriteUrl; } /** * Set rewrite url * * @param string $rewriteUrl * @return static */ public function setRewriteUrl(string $rewriteUrl): self { $this->hasPendingRewrite = true; $this->rewriteUrl = rtrim($rewriteUrl, '/') . '/'; return $this; } /** * Set rewrite callback * @param string|\Closure $callback * @return static */ public function setRewriteCallback($callback): self { $this->hasPendingRewrite = true; return $this->setRewriteRoute(new RouteUrl($this->getUrl()->getPath(), $callback)); } /** * Get loaded route * @return ILoadableRoute|null */ public function getLoadedRoute(): ?ILoadableRoute { return (count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null; } /** * Get all loaded routes * * @return array */ public function getLoadedRoutes(): array { return $this->loadedRoutes; } /** * Set loaded routes * * @param array $routes * @return static */ public function setLoadedRoutes(array $routes): self { $this->loadedRoutes = $routes; return $this; } /** * Added loaded route * * @param ILoadableRoute $route * @return static */ public function addLoadedRoute(ILoadableRoute $route): self { $this->loadedRoutes[] = $route; return $this; } /** * Returns true if the request contains a rewrite * * @return bool */ public function hasPendingRewrite(): bool { return $this->hasPendingRewrite; } /** * Defines if the current request contains a rewrite. * * @param bool $boolean * @return Request */ public function setHasPendingRewrite(bool $boolean): self { $this->hasPendingRewrite = $boolean; return $this; } public function __isset($name): bool { return array_key_exists($name, $this->data) === true; } public function __set($name, $value = null) { $this->data[$name] = $value; } public function __get($name) { return $this->data[$name] ?? null; } }PK!nPecee/Http/Input/IInputItem.phpnu[index = $index; $this->errors = 0; // Make the name human friendly, by replace _ with space $this->name = ucfirst(str_replace('_', ' ', strtolower($this->index))); } /** * Create from array * * @param array $values * @throws InvalidArgumentException * @return static */ public static function createFromArray(array $values): self { if (isset($values['index']) === false) { throw new InvalidArgumentException('Index key is required'); } /* Easy way of ensuring that all indexes-are set and not filling the screen with isset() */ $values += [ 'tmp_name' => null, 'type' => null, 'size' => null, 'name' => null, 'error' => null, ]; return (new static($values['index'])) ->setSize((int)$values['size']) ->setError((int)$values['error']) ->setType($values['type']) ->setTmpName($values['tmp_name']) ->setFilename($values['name']); } /** * @return string */ public function getIndex(): string { return $this->index; } /** * Set input index * @param string $index * @return static */ public function setIndex(string $index): IInputItem { $this->index = $index; return $this; } /** * @return string */ public function getSize(): string { return $this->size; } /** * Set file size * @param int $size * @return static */ public function setSize(int $size): IInputItem { $this->size = $size; return $this; } /** * Get mime-type of file * @return string */ public function getMime(): string { return $this->getType(); } /** * @return string */ public function getType(): string { return $this->type; } /** * Set type * @param string $type * @return static */ public function setType(string $type): IInputItem { $this->type = $type; return $this; } /** * Returns extension without "." * * @return string */ public function getExtension(): string { return pathinfo($this->getFilename(), PATHINFO_EXTENSION); } /** * Get human friendly name * * @return string */ public function getName(): ?string { return $this->name; } /** * Set human friendly name. * Useful for adding validation etc. * * @param string $name * @return static */ public function setName(string $name): IInputItem { $this->name = $name; return $this; } /** * Set filename * * @param string $name * @return static */ public function setFilename(string $name): IInputItem { $this->filename = $name; return $this; } /** * Get filename * * @return string mixed */ public function getFilename(): ?string { return $this->filename; } /** * Move the uploaded temporary file to it's new home * * @param string $destination * @return bool */ public function move(string $destination): bool { return move_uploaded_file($this->tmpName, $destination); } /** * Get file contents * * @return string */ public function getContents(): string { return file_get_contents($this->tmpName); } /** * Return true if an upload error occurred. * * @return bool */ public function hasError(): bool { return ($this->getError() !== 0); } /** * Get upload-error code. * * @return int|null */ public function getError(): ?int { return $this->errors; } /** * Set error * * @param int|null $error * @return static */ public function setError(?int $error): IInputItem { $this->errors = (int)$error; return $this; } /** * @return string */ public function getTmpName(): string { return $this->tmpName; } /** * Set file temp. name * @param string $name * @return static */ public function setTmpName(string $name): IInputItem { $this->tmpName = $name; return $this; } public function __toString(): string { return $this->getTmpName(); } public function getValue(): string { return $this->getFilename(); } /** * @param mixed $value * @return static */ public function setValue($value): IInputItem { $this->filename = $value; return $this; } public function toArray(): array { return [ 'tmp_name' => $this->tmpName, 'type' => $this->type, 'size' => $this->size, 'name' => $this->name, 'error' => $this->errors, 'filename' => $this->filename, ]; } }PK!c ,,!Pecee/Http/Input/InputHandler.phpnu[request = $request; $this->parseInputs(); } /** * Parse input values * */ public function parseInputs(): void { /* Parse get requests */ if (count($_GET) !== 0) { $this->originalParams = $_GET; $this->get = $this->parseInputItem($this->originalParams); } /* Parse post requests */ $this->originalPost = $_POST; if ($this->request->isPostBack() === true) { $contents = file_get_contents('php://input'); // Append any PHP-input json if (strpos(trim($contents), '{') === 0) { $post = json_decode($contents, true); if ($post !== false) { $this->originalPost += $post; } } } if (count($this->originalPost) !== 0) { $this->post = $this->parseInputItem($this->originalPost); } /* Parse get requests */ if (count($_FILES) !== 0) { $this->originalFile = $_FILES; $this->file = $this->parseFiles($this->originalFile); } } /** * @param array $files Array with files to parse * @param string|null $parentKey Key from parent (used when parsing nested array). * @return array */ public function parseFiles(array $files, ?string $parentKey = null): array { $list = []; foreach ($files as $key => $value) { // Parse multi dept file array if(isset($value['name']) === false && is_array($value) === true) { $list[$key] = $this->parseFiles($value, $key); continue; } // Handle array input if (is_array($value['name']) === false) { $values = ['index' => $parentKey ?? $key]; try { $list[$key] = InputFile::createFromArray($values + $value); } catch (InvalidArgumentException $e) { } continue; } $keys = [$key]; $files = $this->rearrangeFile($value['name'], $keys, $value); if (isset($list[$key]) === true) { $list[$key][] = $files; } else { $list[$key] = $files; } } return $list; } /** * Rearrange multi-dimensional file object created by PHP. * * @param array $values * @param array $index * @param array|null $original * @return array */ protected function rearrangeFile(array $values, array &$index, ?array $original): array { $originalIndex = $index[0]; array_shift($index); $output = []; foreach ($values as $key => $value) { if (is_array($original['name'][$key]) === false) { try { $file = InputFile::createFromArray([ 'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key, 'name' => $original['name'][$key], 'error' => $original['error'][$key], 'tmp_name' => $original['tmp_name'][$key], 'type' => $original['type'][$key], 'size' => $original['size'][$key], ]); if (isset($output[$key]) === true) { $output[$key][] = $file; continue; } $output[$key] = $file; continue; } catch (InvalidArgumentException $e) { } } $index[] = $key; $files = $this->rearrangeFile($value, $index, $original); if (isset($output[$key]) === true) { $output[$key][] = $files; } else { $output[$key] = $files; } } return $output; } /** * Parse input item from array * * @param array $array * @return array */ protected function parseInputItem(array $array): array { $list = []; foreach ($array as $key => $value) { // Handle array input if (is_array($value) === true) { $value = $this->parseInputItem($value); } $list[$key] = new InputItem($key, $value); } return $list; } /** * Find input object * * @param string $index * @param array ...$methods * @return IInputItem|array|null */ public function find(string $index, ...$methods) { $element = null; if(count($methods) > 0) { $methods = is_array(...$methods) ? array_values(...$methods) : $methods; } if (count($methods) === 0 || in_array(Request::REQUEST_TYPE_GET, $methods, true) === true) { $element = $this->get($index); } if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array(Request::REQUEST_TYPE_POST, $methods, true) === true)) { $element = $this->post($index); } if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array('file', $methods, true) === true)) { $element = $this->file($index); } return $element; } protected function getValueFromArray(array $array): array { $output = []; /* @var $item InputItem */ foreach ($array as $key => $item) { if ($item instanceof IInputItem) { $item = $item->getValue(); } $output[$key] = is_array($item) ? $this->getValueFromArray($item) : $item; } return $output; } /** * Get input element value matching index * * @param string $index * @param string|mixed|null $defaultValue * @param array ...$methods * @return string|array */ public function value(string $index, $defaultValue = null, ...$methods) { $input = $this->find($index, ...$methods); if ($input instanceof IInputItem) { $input = $input->getValue(); } /* Handle collection */ if (is_array($input) === true) { $output = $this->getValueFromArray($input); return (count($output) === 0) ? $defaultValue : $output; } return ($input === null || (is_string($input) && trim($input) === '')) ? $defaultValue : $input; } /** * Check if a input-item exist. * If an array is as $index parameter the method returns true if all elements exist. * * @param string|array $index * @param array ...$methods * @return bool */ public function exists($index, ...$methods): bool { // Check array if(is_array($index) === true) { foreach($index as $key) { if($this->value($key, null, ...$methods) === null) { return false; } } return true; } return $this->value($index, null, ...$methods) !== null; } /** * Find post-value by index or return default value. * * @param string $index * @param mixed|null $defaultValue * @return InputItem|array|string|null */ public function post(string $index, $defaultValue = null) { return $this->post[$index] ?? $defaultValue; } /** * Find file by index or return default value. * * @param string $index * @param mixed|null $defaultValue * @return InputFile|array|string|null */ public function file(string $index, $defaultValue = null) { return $this->file[$index] ?? $defaultValue; } /** * Find parameter/query-string by index or return default value. * * @param string $index * @param mixed|null $defaultValue * @return InputItem|array|string|null */ public function get(string $index, $defaultValue = null) { return $this->get[$index] ?? $defaultValue; } /** * Get all get/post items * @param array $filter Only take items in filter * @return array */ public function all(array $filter = []): array { $output = $this->originalParams + $this->originalPost + $this->originalFile; $output = (count($filter) > 0) ? array_intersect_key($output, array_flip($filter)) : $output; foreach ($filter as $filterKey) { if (array_key_exists($filterKey, $output) === false) { $output[$filterKey] = null; } } return $output; } /** * Add GET parameter * * @param string $key * @param InputItem $item */ public function addGet(string $key, InputItem $item): void { $this->get[$key] = $item; } /** * Add POST parameter * * @param string $key * @param InputItem $item */ public function addPost(string $key, InputItem $item): void { $this->post[$key] = $item; } /** * Add FILE parameter * * @param string $key * @param InputFile $item */ public function addFile(string $key, InputFile $item): void { $this->file[$key] = $item; } /** * Get original post variables * @return array */ public function getOriginalPost(): array { return $this->originalPost; } /** * Set original post variables * @param array $post * @return static $this */ public function setOriginalPost(array $post): self { $this->originalPost = $post; return $this; } /** * Get original get variables * @return array */ public function getOriginalParams(): array { return $this->originalParams; } /** * Set original get-variables * @param array $params * @return static $this */ public function setOriginalParams(array $params): self { $this->originalParams = $params; return $this; } /** * Get original file variables * @return array */ public function getOriginalFile(): array { return $this->originalFile; } /** * Set original file posts variables * @param array $file * @return static $this */ public function setOriginalFile(array $file): self { $this->originalFile = $file; return $this; } }PK!`۫tnnPecee/Http/Input/InputItem.phpnu[index = $index; $this->value = $value; // Make the name human friendly, by replace _ with space $this->name = ucfirst(str_replace('_', ' ', strtolower($this->index))); } /** * @return string */ public function getIndex(): string { return $this->index; } public function setIndex(string $index): IInputItem { $this->index = $index; return $this; } /** * @return string */ public function getName(): ?string { return $this->name; } /** * Set input name * @param string $name * @return static */ public function setName(string $name): IInputItem { $this->name = $name; return $this; } /** * @return mixed */ public function getValue() { return $this->value; } /** * Set input value * @param mixed $value * @return static */ public function setValue($value): IInputItem { $this->value = $value; return $this; } public function offsetExists($offset): bool { return isset($this->value[$offset]); } public function offsetGet($offset) { if ($this->offsetExists($offset) === true) { return $this->value[$offset]; } return null; } public function offsetSet($offset, $value): void { $this->value[$offset] = $value; } public function offsetUnset($offset): void { unset($this->value[$offset]); } public function __toString(): string { $value = $this->getValue(); return (is_array($value) === true) ? json_encode($value) : $value; } public function getIterator(): ArrayIterator { return new ArrayIterator($this->getValue()); } }PK!}@ +Pecee/Http/Security/CookieTokenProvider.phpnu[token = ($this->hasToken() === true) ? $_COOKIE[static::CSRF_KEY] : null; if ($this->token === null) { $this->token = $this->generateToken(); } } /** * Generate random identifier for CSRF token * * @return string * @throws SecurityException */ public function generateToken(): string { try { return bin2hex(random_bytes(32)); } catch (Exception $e) { throw new SecurityException($e->getMessage(), (int)$e->getCode(), $e->getPrevious()); } } /** * Validate valid CSRF token * * @param string $token * @return bool */ public function validate(string $token): bool { if ($this->getToken() !== null) { return hash_equals($token, $this->getToken()); } return false; } /** * Set csrf token cookie * Overwrite this method to save the token to another storage like session etc. * * @param string $token */ public function setToken(string $token): void { $this->token = $token; setcookie(static::CSRF_KEY, $token, time() + (60 * $this->cookieTimeoutMinutes), '/', ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly')); } /** * Get csrf token * @param string|null $defaultValue * @return string|null */ public function getToken(?string $defaultValue = null): ?string { return $this->token ?? $defaultValue; } /** * Refresh existing token */ public function refresh(): void { if ($this->token !== null) { $this->setToken($this->token); } } /** * Returns whether the csrf token has been defined * @return bool */ public function hasToken(): bool { return isset($_COOKIE[static::CSRF_KEY]); } /** * Get timeout for cookie in minutes * @return int */ public function getCookieTimeoutMinutes(): int { return $this->cookieTimeoutMinutes; } /** * Set cookie timeout in minutes * @param int $minutes */ public function setCookieTimeoutMinutes(int $minutes): void { $this->cookieTimeoutMinutes = $minutes; } }PK!&Pecee/Http/Security/ITokenProvider.phpnu[ * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ namespace PHPMailer\PHPMailer; /** * PHPMailer POP-Before-SMTP Authentication Class. * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. * 1) This class does not support APOP authentication. * 2) Opening and closing lots of POP3 connections can be quite slow. If you need * to send a batch of emails then just perform the authentication once at the start, * and then loop through your mail sending script. Providing this process doesn't * take longer than the verification period lasts on your POP3 server, you should be fine. * 3) This is really ancient technology; you should only need to use it to talk to very old systems. * 4) This POP3 class is deliberately lightweight and incomplete, implementing just * enough to do authentication. * If you want a more complete class there are other POP3 classes for PHP available. * * @author Richard Davey (original author) * @author Marcus Bointon (Synchro/coolbru) * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) */ class POP3 { /** * The POP3 PHPMailer Version number. * * @var string */ const VERSION = '6.6.3'; /** * Default POP3 port number. * * @var int */ const DEFAULT_PORT = 110; /** * Default timeout in seconds. * * @var int */ const DEFAULT_TIMEOUT = 30; /** * POP3 class debug output mode. * Debug output level. * Options: * @see POP3::DEBUG_OFF: No output * @see POP3::DEBUG_SERVER: Server messages, connection/server errors * @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors * * @var int */ public $do_debug = self::DEBUG_OFF; /** * POP3 mail server hostname. * * @var string */ public $host; /** * POP3 port number. * * @var int */ public $port; /** * POP3 Timeout Value in seconds. * * @var int */ public $tval; /** * POP3 username. * * @var string */ public $username; /** * POP3 password. * * @var string */ public $password; /** * Resource handle for the POP3 connection socket. * * @var resource */ protected $pop_conn; /** * Are we connected? * * @var bool */ protected $connected = false; /** * Error container. * * @var array */ protected $errors = []; /** * Line break constant. */ const LE = "\r\n"; /** * Debug level for no output. * * @var int */ const DEBUG_OFF = 0; /** * Debug level to show server -> client messages * also shows clients connection errors or errors from server * * @var int */ const DEBUG_SERVER = 1; /** * Debug level to show client -> server and server -> client messages. * * @var int */ const DEBUG_CLIENT = 2; /** * Simple static wrapper for all-in-one POP before SMTP. * * @param string $host The hostname to connect to * @param int|bool $port The port number to connect to * @param int|bool $timeout The timeout value * @param string $username * @param string $password * @param int $debug_level * * @return bool */ public static function popBeforeSmtp( $host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0 ) { $pop = new self(); return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); } /** * Authenticate with a POP3 server. * A connect, login, disconnect sequence * appropriate for POP-before SMTP authorisation. * * @param string $host The hostname to connect to * @param int|bool $port The port number to connect to * @param int|bool $timeout The timeout value * @param string $username * @param string $password * @param int $debug_level * * @return bool */ public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) { $this->host = $host; //If no port value provided, use default if (false === $port) { $this->port = static::DEFAULT_PORT; } else { $this->port = (int) $port; } //If no timeout value provided, use default if (false === $timeout) { $this->tval = static::DEFAULT_TIMEOUT; } else { $this->tval = (int) $timeout; } $this->do_debug = $debug_level; $this->username = $username; $this->password = $password; //Reset the error log $this->errors = []; //Connect $result = $this->connect($this->host, $this->port, $this->tval); if ($result) { $login_result = $this->login($this->username, $this->password); if ($login_result) { $this->disconnect(); return true; } } //We need to disconnect regardless of whether the login succeeded $this->disconnect(); return false; } /** * Connect to a POP3 server. * * @param string $host * @param int|bool $port * @param int $tval * * @return bool */ public function connect($host, $port = false, $tval = 30) { //Are we already connected? if ($this->connected) { return true; } //On Windows this will raise a PHP Warning error if the hostname doesn't exist. //Rather than suppress it with @fsockopen, capture it cleanly instead set_error_handler([$this, 'catchWarning']); if (false === $port) { $port = static::DEFAULT_PORT; } //Connect to the POP3 server $errno = 0; $errstr = ''; $this->pop_conn = fsockopen( $host, //POP3 Host $port, //Port # $errno, //Error Number $errstr, //Error Message $tval ); //Timeout (seconds) //Restore the error handler restore_error_handler(); //Did we connect? if (false === $this->pop_conn) { //It would appear not... $this->setError( "Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr" ); return false; } //Increase the stream time-out stream_set_timeout($this->pop_conn, $tval, 0); //Get the POP3 server response $pop3_response = $this->getResponse(); //Check for the +OK if ($this->checkResponse($pop3_response)) { //The connection is established and the POP3 server is talking $this->connected = true; return true; } return false; } /** * Log in to the POP3 server. * Does not support APOP (RFC 2828, 4949). * * @param string $username * @param string $password * * @return bool */ public function login($username = '', $password = '') { if (!$this->connected) { $this->setError('Not connected to POP3 server'); return false; } if (empty($username)) { $username = $this->username; } if (empty($password)) { $password = $this->password; } //Send the Username $this->sendString("USER $username" . static::LE); $pop3_response = $this->getResponse(); if ($this->checkResponse($pop3_response)) { //Send the Password $this->sendString("PASS $password" . static::LE); $pop3_response = $this->getResponse(); if ($this->checkResponse($pop3_response)) { return true; } } return false; } /** * Disconnect from the POP3 server. */ public function disconnect() { $this->sendString('QUIT'); // RFC 1939 shows POP3 server sending a +OK response to the QUIT command. // Try to get it. Ignore any failures here. try { $this->getResponse(); } catch (Exception $e) { //Do nothing } //The QUIT command may cause the daemon to exit, which will kill our connection //So ignore errors here try { @fclose($this->pop_conn); } catch (Exception $e) { //Do nothing } // Clean up attributes. $this->connected = false; $this->pop_conn = false; } /** * Get a response from the POP3 server. * * @param int $size The maximum number of bytes to retrieve * * @return string */ protected function getResponse($size = 128) { $response = fgets($this->pop_conn, $size); if ($this->do_debug >= self::DEBUG_SERVER) { echo 'Server -> Client: ', $response; } return $response; } /** * Send raw data to the POP3 server. * * @param string $string * * @return int */ protected function sendString($string) { if ($this->pop_conn) { if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2 echo 'Client -> Server: ', $string; } return fwrite($this->pop_conn, $string, strlen($string)); } return 0; } /** * Checks the POP3 server response. * Looks for for +OK or -ERR. * * @param string $string * * @return bool */ protected function checkResponse($string) { if (strpos($string, '+OK') !== 0) { $this->setError("Server reported an error: $string"); return false; } return true; } /** * Add an error to the internal error store. * Also display debug output if it's enabled. * * @param string $error */ protected function setError($error) { $this->errors[] = $error; if ($this->do_debug >= self::DEBUG_SERVER) { echo '
';
            foreach ($this->errors as $e) {
                print_r($e);
            }
            echo '
'; } } /** * Get an array of error messages, if any. * * @return array */ public function getErrors() { return $this->errors; } /** * POP3 connection error handler. * * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline */ protected function catchWarning($errno, $errstr, $errfile, $errline) { $this->setError( 'Connecting to the POP3 server raised a PHP warning:' . "errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline" ); } } PK!ݿ6OAuthTokenProvider.phpnu[ * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ namespace PHPMailer\PHPMailer; /** * OAuthTokenProvider - OAuth2 token provider interface. * Provides base64 encoded OAuth2 auth strings for SMTP authentication. * * @see OAuth * @see SMTP::authenticate() * * @author Peter Scopes (pdscopes) * @author Marcus Bointon (Synchro/coolbru) */ interface OAuthTokenProvider { /** * Generate a base64-encoded OAuth token ensuring that the access token has not expired. * The string to be base 64 encoded should be in the form: * "user=\001auth=Bearer \001\001" * * @return string */ public function getOauth64(); } PK!־ OAuth.phpnu[ * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ namespace PHPMailer\PHPMailer; use League\OAuth2\Client\Grant\RefreshToken; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Token\AccessToken; /** * OAuth - OAuth2 authentication wrapper class. * Uses the oauth2-client package from the League of Extraordinary Packages. * * @see http://oauth2-client.thephpleague.com * * @author Marcus Bointon (Synchro/coolbru) */ class OAuth implements OAuthTokenProvider { /** * An instance of the League OAuth Client Provider. * * @var AbstractProvider */ protected $provider; /** * The current OAuth access token. * * @var AccessToken */ protected $oauthToken; /** * The user's email address, usually used as the login ID * and also the from address when sending email. * * @var string */ protected $oauthUserEmail = ''; /** * The client secret, generated in the app definition of the service you're connecting to. * * @var string */ protected $oauthClientSecret = ''; /** * The client ID, generated in the app definition of the service you're connecting to. * * @var string */ protected $oauthClientId = ''; /** * The refresh token, used to obtain new AccessTokens. * * @var string */ protected $oauthRefreshToken = ''; /** * OAuth constructor. * * @param array $options Associative array containing * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements */ public function __construct($options) { $this->provider = $options['provider']; $this->oauthUserEmail = $options['userName']; $this->oauthClientSecret = $options['clientSecret']; $this->oauthClientId = $options['clientId']; $this->oauthRefreshToken = $options['refreshToken']; } /** * Get a new RefreshToken. * * @return RefreshToken */ protected function getGrant() { return new RefreshToken(); } /** * Get a new AccessToken. * * @return AccessToken */ protected function getToken() { return $this->provider->getAccessToken( $this->getGrant(), ['refresh_token' => $this->oauthRefreshToken] ); } /** * Generate a base64-encoded OAuth token. * * @return string */ public function getOauth64() { //Get a new token if it's not available or has expired if (null === $this->oauthToken || $this->oauthToken->hasExpired()) { $this->oauthToken = $this->getToken(); } return base64_encode( 'user=' . $this->oauthUserEmail . "\001auth=Bearer " . $this->oauthToken . "\001\001" ); } } PK!f^KKSMTP.phpnu[ * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ namespace PHPMailer\PHPMailer; /** * PHPMailer RFC821 SMTP email transport class. * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. * * @author Chris Ryan * @author Marcus Bointon */ class SMTP { /** * The PHPMailer SMTP version number. * * @var string */ const VERSION = '6.6.3'; /** * SMTP line break constant. * * @var string */ const LE = "\r\n"; /** * The SMTP port to use if one is not specified. * * @var int */ const DEFAULT_PORT = 25; /** * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, * *excluding* a trailing CRLF break. * * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 * * @var int */ const MAX_LINE_LENGTH = 998; /** * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, * *including* a trailing CRLF line break. * * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 * * @var int */ const MAX_REPLY_LENGTH = 512; /** * Debug level for no output. * * @var int */ const DEBUG_OFF = 0; /** * Debug level to show client -> server messages. * * @var int */ const DEBUG_CLIENT = 1; /** * Debug level to show client -> server and server -> client messages. * * @var int */ const DEBUG_SERVER = 2; /** * Debug level to show connection status, client -> server and server -> client messages. * * @var int */ const DEBUG_CONNECTION = 3; /** * Debug level to show all messages. * * @var int */ const DEBUG_LOWLEVEL = 4; /** * Debug output level. * Options: * * self::DEBUG_OFF (`0`) No debug output, default * * self::DEBUG_CLIENT (`1`) Client commands * * self::DEBUG_SERVER (`2`) Client commands and server responses * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages. * * @var int */ public $do_debug = self::DEBUG_OFF; /** * How to handle debug output. * Options: * * `echo` Output plain-text as-is, appropriate for CLI * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output * * `error_log` Output to error log as configured in php.ini * Alternatively, you can provide a callable expecting two params: a message string and the debug level: * * ```php * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; * ``` * * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` * level output is used: * * ```php * $mail->Debugoutput = new myPsr3Logger; * ``` * * @var string|callable|\Psr\Log\LoggerInterface */ public $Debugoutput = 'echo'; /** * Whether to use VERP. * * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path * @see http://www.postfix.org/VERP_README.html Info on VERP * * @var bool */ public $do_verp = false; /** * The timeout value for connection, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. * * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 * * @var int */ public $Timeout = 300; /** * How long to wait for commands to complete, in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. * * @var int */ public $Timelimit = 300; /** * Patterns to extract an SMTP transaction id from reply to a DATA command. * The first capture group in each regex will be used as the ID. * MS ESMTP returns the message ID, which may not be correct for internal tracking. * * @var string[] */ protected $smtp_transaction_id_patterns = [ 'exim' => '/[\d]{3} OK id=(.*)/', 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', 'Amazon_SES' => '/[\d]{3} Ok (.*)/', 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/', 'Mailjet' => '/[\d]{3} OK queued as (.*)/', ]; /** * The last transaction ID issued in response to a DATA command, * if one was detected. * * @var string|bool|null */ protected $last_smtp_transaction_id; /** * The socket for the server connection. * * @var ?resource */ protected $smtp_conn; /** * Error information, if any, for the last SMTP command. * * @var array */ protected $error = [ 'error' => '', 'detail' => '', 'smtp_code' => '', 'smtp_code_ex' => '', ]; /** * The reply the server sent to us for HELO. * If null, no HELO string has yet been received. * * @var string|null */ protected $helo_rply; /** * The set of SMTP extensions sent in reply to EHLO command. * Indexes of the array are extension names. * Value at index 'HELO' or 'EHLO' (according to command that was sent) * represents the server name. In case of HELO it is the only element of the array. * Other values can be boolean TRUE or an array containing extension options. * If null, no HELO/EHLO string has yet been received. * * @var array|null */ protected $server_caps; /** * The most recent reply received from the server. * * @var string */ protected $last_reply = ''; /** * Output debugging info via a user-selected method. * * @param string $str Debug string to output * @param int $level The debug level of this message; see DEBUG_* constants * * @see SMTP::$Debugoutput * @see SMTP::$do_debug */ protected function edebug($str, $level = 0) { if ($level > $this->do_debug) { return; } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { $this->Debugoutput->debug($str); return; } //Avoid clash with built-in function names if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { call_user_func($this->Debugoutput, $str, $level); return; } switch ($this->Debugoutput) { case 'error_log': //Don't output, just log error_log($str); break; case 'html': //Cleans up output a bit for a better looking, HTML-safe output echo gmdate('Y-m-d H:i:s'), ' ', htmlentities( preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8' ), "
\n"; break; case 'echo': default: //Normalize line breaks $str = preg_replace('/\r\n|\r/m', "\n", $str); echo gmdate('Y-m-d H:i:s'), "\t", //Trim trailing space trim( //Indent for readability, except for trailing break str_replace( "\n", "\n \t ", trim($str) ) ), "\n"; } } /** * Connect to an SMTP server. * * @param string $host SMTP server IP or host name * @param int $port The port number to connect to * @param int $timeout How long to wait for the connection to open * @param array $options An array of options for stream_context_create() * * @return bool */ public function connect($host, $port = null, $timeout = 30, $options = []) { //Clear errors to avoid confusion $this->setError(''); //Make sure we are __not__ connected if ($this->connected()) { //Already connected, generate error $this->setError('Already connected to a server'); return false; } if (empty($port)) { $port = self::DEFAULT_PORT; } //Connect to the SMTP server $this->edebug( "Connection: opening to $host:$port, timeout=$timeout, options=" . (count($options) > 0 ? var_export($options, true) : 'array()'), self::DEBUG_CONNECTION ); $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options); if ($this->smtp_conn === false) { //Error info already set inside `getSMTPConnection()` return false; } $this->edebug('Connection: opened', self::DEBUG_CONNECTION); //Get any announcement $this->last_reply = $this->get_lines(); $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); $responseCode = (int)substr($this->last_reply, 0, 3); if ($responseCode === 220) { return true; } //Anything other than a 220 response means something went wrong //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error //https://tools.ietf.org/html/rfc5321#section-3.1 if ($responseCode === 554) { $this->quit(); } //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down) $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION); $this->close(); return false; } /** * Create connection to the SMTP server. * * @param string $host SMTP server IP or host name * @param int $port The port number to connect to * @param int $timeout How long to wait for the connection to open * @param array $options An array of options for stream_context_create() * * @return false|resource */ protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = []) { static $streamok; //This is enabled by default since 5.0.0 but some providers disable it //Check this once and cache the result if (null === $streamok) { $streamok = function_exists('stream_socket_client'); } $errno = 0; $errstr = ''; if ($streamok) { $socket_context = stream_context_create($options); set_error_handler([$this, 'errorHandler']); $connection = stream_socket_client( $host . ':' . $port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $socket_context ); } else { //Fall back to fsockopen which should work in more places, but is missing some features $this->edebug( 'Connection: stream_socket_client not available, falling back to fsockopen', self::DEBUG_CONNECTION ); set_error_handler([$this, 'errorHandler']); $connection = fsockopen( $host, $port, $errno, $errstr, $timeout ); } restore_error_handler(); //Verify we connected properly if (!is_resource($connection)) { $this->setError( 'Failed to connect to server', '', (string) $errno, $errstr ); $this->edebug( 'SMTP ERROR: ' . $this->error['error'] . ": $errstr ($errno)", self::DEBUG_CLIENT ); return false; } //SMTP server can take longer to respond, give longer timeout for first read //Windows does not have support for this timeout function if (strpos(PHP_OS, 'WIN') !== 0) { $max = (int)ini_get('max_execution_time'); //Don't bother if unlimited, or if set_time_limit is disabled if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) { @set_time_limit($timeout); } stream_set_timeout($connection, $timeout, 0); } return $connection; } /** * Initiate a TLS (encrypted) session. * * @return bool */ public function startTLS() { if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { return false; } //Allow the best TLS version(s) we can $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT //so add them back in manually if we can if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; } //Begin encrypted connection set_error_handler([$this, 'errorHandler']); $crypto_ok = stream_socket_enable_crypto( $this->smtp_conn, true, $crypto_method ); restore_error_handler(); return (bool) $crypto_ok; } /** * Perform SMTP authentication. * Must be run after hello(). * * @see hello() * * @param string $username The user name * @param string $password The password * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2) * @param OAuthTokenProvider $OAuth An optional OAuthTokenProvider instance for XOAUTH2 authentication * * @return bool True if successfully authenticated */ public function authenticate( $username, $password, $authtype = null, $OAuth = null ) { if (!$this->server_caps) { $this->setError('Authentication is not allowed before HELO/EHLO'); return false; } if (array_key_exists('EHLO', $this->server_caps)) { //SMTP extensions are available; try to find a proper authentication method if (!array_key_exists('AUTH', $this->server_caps)) { $this->setError('Authentication is not allowed at this stage'); //'at this stage' means that auth may be allowed after the stage changes //e.g. after STARTTLS return false; } $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); $this->edebug( 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), self::DEBUG_LOWLEVEL ); //If we have requested a specific auth type, check the server supports it before trying others if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) { $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL); $authtype = null; } if (empty($authtype)) { //If no auth mechanism is specified, attempt to use these, in this order //Try CRAM-MD5 first as it's more secure than the others foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) { if (in_array($method, $this->server_caps['AUTH'], true)) { $authtype = $method; break; } } if (empty($authtype)) { $this->setError('No supported authentication methods found'); return false; } $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); } if (!in_array($authtype, $this->server_caps['AUTH'], true)) { $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); return false; } } elseif (empty($authtype)) { $authtype = 'LOGIN'; } switch ($authtype) { case 'PLAIN': //Start authentication if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { return false; } //Send encoded username and password if ( //Format from https://tools.ietf.org/html/rfc4616#section-2 //We skip the first field (it's forgery), so the string starts with a null byte !$this->sendCommand( 'User & Password', base64_encode("\0" . $username . "\0" . $password), 235 ) ) { return false; } break; case 'LOGIN': //Start authentication if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { return false; } if (!$this->sendCommand('Username', base64_encode($username), 334)) { return false; } if (!$this->sendCommand('Password', base64_encode($password), 235)) { return false; } break; case 'CRAM-MD5': //Start authentication if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { return false; } //Get the challenge $challenge = base64_decode(substr($this->last_reply, 4)); //Build the response $response = $username . ' ' . $this->hmac($challenge, $password); //send encoded credentials return $this->sendCommand('Username', base64_encode($response), 235); case 'XOAUTH2': //The OAuth instance must be set up prior to requesting auth. if (null === $OAuth) { return false; } $oauth = $OAuth->getOauth64(); //Start authentication if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { return false; } break; default: $this->setError("Authentication method \"$authtype\" is not supported"); return false; } return true; } /** * Calculate an MD5 HMAC hash. * Works like hash_hmac('md5', $data, $key) * in case that function is not available. * * @param string $data The data to hash * @param string $key The key to hash with * * @return string */ protected function hmac($data, $key) { if (function_exists('hash_hmac')) { return hash_hmac('md5', $data, $key); } //The following borrowed from //http://php.net/manual/en/function.mhash.php#27225 //RFC 2104 HMAC implementation for php. //Creates an md5 HMAC. //Eliminates the need to install mhash to compute a HMAC //by Lance Rushing $bytelen = 64; //byte length for md5 if (strlen($key) > $bytelen) { $key = pack('H*', md5($key)); } $key = str_pad($key, $bytelen, chr(0x00)); $ipad = str_pad('', $bytelen, chr(0x36)); $opad = str_pad('', $bytelen, chr(0x5c)); $k_ipad = $key ^ $ipad; $k_opad = $key ^ $opad; return md5($k_opad . pack('H*', md5($k_ipad . $data))); } /** * Check connection state. * * @return bool True if connected */ public function connected() { if (is_resource($this->smtp_conn)) { $sock_status = stream_get_meta_data($this->smtp_conn); if ($sock_status['eof']) { //The socket is valid but we are not connected $this->edebug( 'SMTP NOTICE: EOF caught while checking if connected', self::DEBUG_CLIENT ); $this->close(); return false; } return true; //everything looks good } return false; } /** * Close the socket and clean up the state of the class. * Don't use this function without first trying to use QUIT. * * @see quit() */ public function close() { $this->setError(''); $this->server_caps = null; $this->helo_rply = null; if (is_resource($this->smtp_conn)) { //Close the connection and cleanup fclose($this->smtp_conn); $this->smtp_conn = null; //Makes for cleaner serialization $this->edebug('Connection: closed', self::DEBUG_CONNECTION); } } /** * Send an SMTP DATA command. * Issues a data command and sends the msg_data to the server, * finalizing the mail transaction. $msg_data is the message * that is to be send with the headers. Each header needs to be * on a single line followed by a with the message headers * and the message body being separated by an additional . * Implements RFC 821: DATA . * * @param string $msg_data Message data to send * * @return bool */ public function data($msg_data) { //This will use the standard timelimit if (!$this->sendCommand('DATA', 'DATA', 354)) { return false; } /* The server is ready to accept data! * According to rfc821 we should not send more than 1000 characters on a single line (including the LE) * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into * smaller lines to fit within the limit. * We will also look for lines that start with a '.' and prepend an additional '.'. * NOTE: this does not count towards line-length limit. */ //Normalize line breaks before exploding $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field * of the first line (':' separated) does not contain a space then it _should_ be a header and we will * process all lines before a blank line as headers. */ $field = substr($lines[0], 0, strpos($lines[0], ':')); $in_headers = false; if (!empty($field) && strpos($field, ' ') === false) { $in_headers = true; } foreach ($lines as $line) { $lines_out = []; if ($in_headers && $line === '') { $in_headers = false; } //Break this line up into several smaller lines if it's too long //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), while (isset($line[self::MAX_LINE_LENGTH])) { //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on //so as to avoid breaking in the middle of a word $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); //Deliberately matches both false and 0 if (!$pos) { //No nice break found, add a hard break $pos = self::MAX_LINE_LENGTH - 1; $lines_out[] = substr($line, 0, $pos); $line = substr($line, $pos); } else { //Break at the found point $lines_out[] = substr($line, 0, $pos); //Move along by the amount we dealt with $line = substr($line, $pos + 1); } //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 if ($in_headers) { $line = "\t" . $line; } } $lines_out[] = $line; //Send the lines to the server foreach ($lines_out as $line_out) { //Dot-stuffing as per RFC5321 section 4.5.2 //https://tools.ietf.org/html/rfc5321#section-4.5.2 if (!empty($line_out) && $line_out[0] === '.') { $line_out = '.' . $line_out; } $this->client_send($line_out . static::LE, 'DATA'); } } //Message data has been sent, complete the command //Increase timelimit for end of DATA command $savetimelimit = $this->Timelimit; $this->Timelimit *= 2; $result = $this->sendCommand('DATA END', '.', 250); $this->recordLastTransactionID(); //Restore timelimit $this->Timelimit = $savetimelimit; return $result; } /** * Send an SMTP HELO or EHLO command. * Used to identify the sending server to the receiving server. * This makes sure that client and server are in a known state. * Implements RFC 821: HELO * and RFC 2821 EHLO. * * @param string $host The host name or IP to connect to * * @return bool */ public function hello($host = '') { //Try extended hello first (RFC 2821) if ($this->sendHello('EHLO', $host)) { return true; } //Some servers shut down the SMTP service here (RFC 5321) if (substr($this->helo_rply, 0, 3) == '421') { return false; } return $this->sendHello('HELO', $host); } /** * Send an SMTP HELO or EHLO command. * Low-level implementation used by hello(). * * @param string $hello The HELO string * @param string $host The hostname to say we are * * @return bool * * @see hello() */ protected function sendHello($hello, $host) { $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); $this->helo_rply = $this->last_reply; if ($noerror) { $this->parseHelloFields($hello); } else { $this->server_caps = null; } return $noerror; } /** * Parse a reply to HELO/EHLO command to discover server extensions. * In case of HELO, the only parameter that can be discovered is a server name. * * @param string $type `HELO` or `EHLO` */ protected function parseHelloFields($type) { $this->server_caps = []; $lines = explode("\n", $this->helo_rply); foreach ($lines as $n => $s) { //First 4 chars contain response code followed by - or space $s = trim(substr($s, 4)); if (empty($s)) { continue; } $fields = explode(' ', $s); if (!empty($fields)) { if (!$n) { $name = $type; $fields = $fields[0]; } else { $name = array_shift($fields); switch ($name) { case 'SIZE': $fields = ($fields ? $fields[0] : 0); break; case 'AUTH': if (!is_array($fields)) { $fields = []; } break; default: $fields = true; } } $this->server_caps[$name] = $fields; } } } /** * Send an SMTP MAIL command. * Starts a mail transaction from the email address specified in * $from. Returns true if successful or false otherwise. If True * the mail transaction is started and then one or more recipient * commands may be called followed by a data command. * Implements RFC 821: MAIL FROM: . * * @param string $from Source address of this message * * @return bool */ public function mail($from) { $useVerp = ($this->do_verp ? ' XVERP' : ''); return $this->sendCommand( 'MAIL FROM', 'MAIL FROM:<' . $from . '>' . $useVerp, 250 ); } /** * Send an SMTP QUIT command. * Closes the socket if there is no error or the $close_on_error argument is true. * Implements from RFC 821: QUIT . * * @param bool $close_on_error Should the connection close if an error occurs? * * @return bool */ public function quit($close_on_error = true) { $noerror = $this->sendCommand('QUIT', 'QUIT', 221); $err = $this->error; //Save any error if ($noerror || $close_on_error) { $this->close(); $this->error = $err; //Restore any error from the quit command } return $noerror; } /** * Send an SMTP RCPT command. * Sets the TO argument to $toaddr. * Returns true if the recipient was accepted false if it was rejected. * Implements from RFC 821: RCPT TO: . * * @param string $address The address the message is being sent to * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE * or DELAY. If you specify NEVER all other notifications are ignored. * * @return bool */ public function recipient($address, $dsn = '') { if (empty($dsn)) { $rcpt = 'RCPT TO:<' . $address . '>'; } else { $dsn = strtoupper($dsn); $notify = []; if (strpos($dsn, 'NEVER') !== false) { $notify[] = 'NEVER'; } else { foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) { if (strpos($dsn, $value) !== false) { $notify[] = $value; } } } $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify); } return $this->sendCommand( 'RCPT TO', $rcpt, [250, 251] ); } /** * Send an SMTP RSET command. * Abort any transaction that is currently in progress. * Implements RFC 821: RSET . * * @return bool True on success */ public function reset() { return $this->sendCommand('RSET', 'RSET', 250); } /** * Send a command to an SMTP server and check its return code. * * @param string $command The command name - not sent to the server * @param string $commandstring The actual command to send * @param int|array $expect One or more expected integer success codes * * @return bool True on success */ protected function sendCommand($command, $commandstring, $expect) { if (!$this->connected()) { $this->setError("Called $command without being connected"); return false; } //Reject line breaks in all commands if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) { $this->setError("Command '$command' contained line breaks"); return false; } $this->client_send($commandstring . static::LE, $command); $this->last_reply = $this->get_lines(); //Fetch SMTP code and possible error code explanation $matches = []; if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { $code = (int) $matches[1]; $code_ex = (count($matches) > 2 ? $matches[2] : null); //Cut off error code from each response line $detail = preg_replace( "/{$code}[ -]" . ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', '', $this->last_reply ); } else { //Fall back to simple parsing if regex fails $code = (int) substr($this->last_reply, 0, 3); $code_ex = null; $detail = substr($this->last_reply, 4); } $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); if (!in_array($code, (array) $expect, true)) { $this->setError( "$command command failed", $detail, $code, $code_ex ); $this->edebug( 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, self::DEBUG_CLIENT ); return false; } //Don't clear the error store when using keepalive if ($command !== 'RSET') { $this->setError(''); } return true; } /** * Send an SMTP SAML command. * Starts a mail transaction from the email address specified in $from. * Returns true if successful or false otherwise. If True * the mail transaction is started and then one or more recipient * commands may be called followed by a data command. This command * will send the message to the users terminal if they are logged * in and send them an email. * Implements RFC 821: SAML FROM: . * * @param string $from The address the message is from * * @return bool */ public function sendAndMail($from) { return $this->sendCommand('SAML', "SAML FROM:$from", 250); } /** * Send an SMTP VRFY command. * * @param string $name The name to verify * * @return bool */ public function verify($name) { return $this->sendCommand('VRFY', "VRFY $name", [250, 251]); } /** * Send an SMTP NOOP command. * Used to keep keep-alives alive, doesn't actually do anything. * * @return bool */ public function noop() { return $this->sendCommand('NOOP', 'NOOP', 250); } /** * Send an SMTP TURN command. * This is an optional command for SMTP that this class does not support. * This method is here to make the RFC821 Definition complete for this class * and _may_ be implemented in future. * Implements from RFC 821: TURN . * * @return bool */ public function turn() { $this->setError('The SMTP TURN command is not implemented'); $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); return false; } /** * Send raw data to the server. * * @param string $data The data to send * @param string $command Optionally, the command this is part of, used only for controlling debug output * * @return int|bool The number of bytes sent to the server or false on error */ public function client_send($data, $command = '') { //If SMTP transcripts are left enabled, or debug output is posted online //it can leak credentials, so hide credentials in all but lowest level if ( self::DEBUG_LOWLEVEL > $this->do_debug && in_array($command, ['User & Password', 'Username', 'Password'], true) ) { $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); } else { $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); } set_error_handler([$this, 'errorHandler']); $result = fwrite($this->smtp_conn, $data); restore_error_handler(); return $result; } /** * Get the latest error. * * @return array */ public function getError() { return $this->error; } /** * Get SMTP extensions available on the server. * * @return array|null */ public function getServerExtList() { return $this->server_caps; } /** * Get metadata about the SMTP server from its HELO/EHLO response. * The method works in three ways, dependent on argument value and current state: * 1. HELO/EHLO has not been sent - returns null and populates $this->error. * 2. HELO has been sent - * $name == 'HELO': returns server name * $name == 'EHLO': returns boolean false * $name == any other string: returns null and populates $this->error * 3. EHLO has been sent - * $name == 'HELO'|'EHLO': returns the server name * $name == any other string: if extension $name exists, returns True * or its options (e.g. AUTH mechanisms supported). Otherwise returns False. * * @param string $name Name of SMTP extension or 'HELO'|'EHLO' * * @return string|bool|null */ public function getServerExt($name) { if (!$this->server_caps) { $this->setError('No HELO/EHLO was sent'); return null; } if (!array_key_exists($name, $this->server_caps)) { if ('HELO' === $name) { return $this->server_caps['EHLO']; } if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) { return false; } $this->setError('HELO handshake was used; No information about server extensions available'); return null; } return $this->server_caps[$name]; } /** * Get the last reply from the server. * * @return string */ public function getLastReply() { return $this->last_reply; } /** * Read the SMTP server's response. * Either before eof or socket timeout occurs on the operation. * With SMTP we can tell if we have more lines to read if the * 4th character is '-' symbol. If it is a space then we don't * need to read anything else. * * @return string */ protected function get_lines() { //If the connection is bad, give up straight away if (!is_resource($this->smtp_conn)) { return ''; } $data = ''; $endtime = 0; stream_set_timeout($this->smtp_conn, $this->Timeout); if ($this->Timelimit > 0) { $endtime = time() + $this->Timelimit; } $selR = [$this->smtp_conn]; $selW = null; while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { //Must pass vars in here as params are by reference //solution for signals inspired by https://github.com/symfony/symfony/pull/6540 set_error_handler([$this, 'errorHandler']); $n = stream_select($selR, $selW, $selW, $this->Timelimit); restore_error_handler(); if ($n === false) { $message = $this->getError()['detail']; $this->edebug( 'SMTP -> get_lines(): select failed (' . $message . ')', self::DEBUG_LOWLEVEL ); //stream_select returns false when the `select` system call is interrupted //by an incoming signal, try the select again if (stripos($message, 'interrupted system call') !== false) { $this->edebug( 'SMTP -> get_lines(): retrying stream_select', self::DEBUG_LOWLEVEL ); $this->setError(''); continue; } break; } if (!$n) { $this->edebug( 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)', self::DEBUG_LOWLEVEL ); break; } //Deliberate noise suppression - errors are handled afterwards $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); $data .= $str; //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), //or 4th character is a space or a line break char, we are done reading, break the loop. //String array access is a significant micro-optimisation over strlen if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { break; } //Timed-out? Log and break $info = stream_get_meta_data($this->smtp_conn); if ($info['timed_out']) { $this->edebug( 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)', self::DEBUG_LOWLEVEL ); break; } //Now check if reads took too long if ($endtime && time() > $endtime) { $this->edebug( 'SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' sec)', self::DEBUG_LOWLEVEL ); break; } } return $data; } /** * Enable or disable VERP address generation. * * @param bool $enabled */ public function setVerp($enabled = false) { $this->do_verp = $enabled; } /** * Get VERP address generation mode. * * @return bool */ public function getVerp() { return $this->do_verp; } /** * Set error messages and codes. * * @param string $message The error message * @param string $detail Further detail on the error * @param string $smtp_code An associated SMTP error code * @param string $smtp_code_ex Extended SMTP code */ protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') { $this->error = [ 'error' => $message, 'detail' => $detail, 'smtp_code' => $smtp_code, 'smtp_code_ex' => $smtp_code_ex, ]; } /** * Set debug output method. * * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it */ public function setDebugOutput($method = 'echo') { $this->Debugoutput = $method; } /** * Get debug output method. * * @return string */ public function getDebugOutput() { return $this->Debugoutput; } /** * Set debug output level. * * @param int $level */ public function setDebugLevel($level = 0) { $this->do_debug = $level; } /** * Get debug output level. * * @return int */ public function getDebugLevel() { return $this->do_debug; } /** * Set SMTP timeout. * * @param int $timeout The timeout duration in seconds */ public function setTimeout($timeout = 0) { $this->Timeout = $timeout; } /** * Get SMTP timeout. * * @return int */ public function getTimeout() { return $this->Timeout; } /** * Reports an error number and string. * * @param int $errno The error number returned by PHP * @param string $errmsg The error message returned by PHP * @param string $errfile The file the error occurred in * @param int $errline The line number the error occurred on */ protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) { $notice = 'Connection failed.'; $this->setError( $notice, $errmsg, (string) $errno ); $this->edebug( "$notice Error #$errno: $errmsg [$errfile line $errline]", self::DEBUG_CONNECTION ); } /** * Extract and return the ID of the last SMTP transaction based on * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. * Relies on the host providing the ID in response to a DATA command. * If no reply has been received yet, it will return null. * If no pattern was matched, it will return false. * * @return bool|string|null */ protected function recordLastTransactionID() { $reply = $this->getLastReply(); if (empty($reply)) { $this->last_smtp_transaction_id = null; } else { $this->last_smtp_transaction_id = false; foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { $matches = []; if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { $this->last_smtp_transaction_id = trim($matches[1]); break; } } } return $this->last_smtp_transaction_id; } /** * Get the queue/transaction ID of the last SMTP transaction * If no reply has been received yet, it will return null. * If no pattern was matched, it will return false. * * @return bool|string|null * * @see recordLastTransactionID() */ public function getLastTransactionID() { return $this->last_smtp_transaction_id; } } PK!2 PHPMailer.phpnu[ * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @note This program is distributed in the hope that it will be useful - WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. */ namespace PHPMailer\PHPMailer; /** * PHPMailer - PHP email creation and transport class. * * @author Marcus Bointon (Synchro/coolbru) * @author Jim Jagielski (jimjag) * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) */ class PHPMailer { const CHARSET_ASCII = 'us-ascii'; const CHARSET_ISO88591 = 'iso-8859-1'; const CHARSET_UTF8 = 'utf-8'; const CONTENT_TYPE_PLAINTEXT = 'text/plain'; const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; const CONTENT_TYPE_TEXT_HTML = 'text/html'; const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; const ENCODING_7BIT = '7bit'; const ENCODING_8BIT = '8bit'; const ENCODING_BASE64 = 'base64'; const ENCODING_BINARY = 'binary'; const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; const ENCRYPTION_STARTTLS = 'tls'; const ENCRYPTION_SMTPS = 'ssl'; const ICAL_METHOD_REQUEST = 'REQUEST'; const ICAL_METHOD_PUBLISH = 'PUBLISH'; const ICAL_METHOD_REPLY = 'REPLY'; const ICAL_METHOD_ADD = 'ADD'; const ICAL_METHOD_CANCEL = 'CANCEL'; const ICAL_METHOD_REFRESH = 'REFRESH'; const ICAL_METHOD_COUNTER = 'COUNTER'; const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; /** * Email priority. * Options: null (default), 1 = High, 3 = Normal, 5 = low. * When null, the header is not set at all. * * @var int|null */ public $Priority; /** * The character set of the message. * * @var string */ public $CharSet = self::CHARSET_ISO88591; /** * The MIME Content-type of the message. * * @var string */ public $ContentType = self::CONTENT_TYPE_PLAINTEXT; /** * The message encoding. * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". * * @var string */ public $Encoding = self::ENCODING_8BIT; /** * Holds the most recent mailer error message. * * @var string */ public $ErrorInfo = ''; /** * The From email address for the message. * * @var string */ public $From = ''; /** * The From name of the message. * * @var string */ public $FromName = ''; /** * The envelope sender of the message. * This will usually be turned into a Return-Path header by the receiver, * and is the address that bounces will be sent to. * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. * * @var string */ public $Sender = ''; /** * The Subject of the message. * * @var string */ public $Subject = ''; /** * An HTML or plain text message body. * If HTML then call isHTML(true). * * @var string */ public $Body = ''; /** * The plain-text message body. * This body can be read by mail clients that do not have HTML email * capability such as mutt & Eudora. * Clients that can read HTML will view the normal Body. * * @var string */ public $AltBody = ''; /** * An iCal message part body. * Only supported in simple alt or alt_inline message types * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. * * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ * @see http://kigkonsult.se/iCalcreator/ * * @var string */ public $Ical = ''; /** * Value-array of "method" in Contenttype header "text/calendar" * * @var string[] */ protected static $IcalMethods = [ self::ICAL_METHOD_REQUEST, self::ICAL_METHOD_PUBLISH, self::ICAL_METHOD_REPLY, self::ICAL_METHOD_ADD, self::ICAL_METHOD_CANCEL, self::ICAL_METHOD_REFRESH, self::ICAL_METHOD_COUNTER, self::ICAL_METHOD_DECLINECOUNTER, ]; /** * The complete compiled MIME message body. * * @var string */ protected $MIMEBody = ''; /** * The complete compiled MIME message headers. * * @var string */ protected $MIMEHeader = ''; /** * Extra headers that createHeader() doesn't fold in. * * @var string */ protected $mailHeader = ''; /** * Word-wrap the message body to this number of chars. * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. * * @see static::STD_LINE_LENGTH * * @var int */ public $WordWrap = 0; /** * Which method to use to send mail. * Options: "mail", "sendmail", or "smtp". * * @var string */ public $Mailer = 'mail'; /** * The path to the sendmail program. * * @var string */ public $Sendmail = '/usr/sbin/sendmail'; /** * Whether mail() uses a fully sendmail-compatible MTA. * One which supports sendmail's "-oi -f" options. * * @var bool */ public $UseSendmailOptions = true; /** * The email address that a reading confirmation should be sent to, also known as read receipt. * * @var string */ public $ConfirmReadingTo = ''; /** * The hostname to use in the Message-ID header and as default HELO string. * If empty, PHPMailer attempts to find one with, in order, * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value * 'localhost.localdomain'. * * @see PHPMailer::$Helo * * @var string */ public $Hostname = ''; /** * An ID to be used in the Message-ID header. * If empty, a unique id will be generated. * You can set your own, but it must be in the format "", * as defined in RFC5322 section 3.6.4 or it will be ignored. * * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 * * @var string */ public $MessageID = ''; /** * The message Date to be used in the Date header. * If empty, the current date will be added. * * @var string */ public $MessageDate = ''; /** * SMTP hosts. * Either a single hostname or multiple semicolon-delimited hostnames. * You can also specify a different port * for each host by using this format: [hostname:port] * (e.g. "smtp1.example.com:25;smtp2.example.com"). * You can also specify encryption type, for example: * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). * Hosts will be tried in order. * * @var string */ public $Host = 'localhost'; /** * The default SMTP server port. * * @var int */ public $Port = 25; /** * The SMTP HELO/EHLO name used for the SMTP connection. * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find * one with the same method described above for $Hostname. * * @see PHPMailer::$Hostname * * @var string */ public $Helo = ''; /** * What kind of encryption to use on the SMTP connection. * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. * * @var string */ public $SMTPSecure = ''; /** * Whether to enable TLS encryption automatically if a server supports it, * even if `SMTPSecure` is not set to 'tls'. * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. * * @var bool */ public $SMTPAutoTLS = true; /** * Whether to use SMTP authentication. * Uses the Username and Password properties. * * @see PHPMailer::$Username * @see PHPMailer::$Password * * @var bool */ public $SMTPAuth = false; /** * Options array passed to stream_context_create when connecting via SMTP. * * @var array */ public $SMTPOptions = []; /** * SMTP username. * * @var string */ public $Username = ''; /** * SMTP password. * * @var string */ public $Password = ''; /** * SMTP auth type. * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. * * @var string */ public $AuthType = ''; /** * An implementation of the PHPMailer OAuthTokenProvider interface. * * @var OAuthTokenProvider */ protected $oauth; /** * The SMTP server timeout in seconds. * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. * * @var int */ public $Timeout = 300; /** * Comma separated list of DSN notifications * 'NEVER' under no circumstances a DSN must be returned to the sender. * If you use NEVER all other notifications will be ignored. * 'SUCCESS' will notify you when your mail has arrived at its destination. * 'FAILURE' will arrive if an error occurred during delivery. * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual * delivery's outcome (success or failure) is not yet decided. * * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY */ public $dsn = ''; /** * SMTP class debug output mode. * Debug output level. * Options: * @see SMTP::DEBUG_OFF: No output * @see SMTP::DEBUG_CLIENT: Client messages * @see SMTP::DEBUG_SERVER: Client and server messages * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed * * @see SMTP::$do_debug * * @var int */ public $SMTPDebug = 0; /** * How to handle debug output. * Options: * * `echo` Output plain-text as-is, appropriate for CLI * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output * * `error_log` Output to error log as configured in php.ini * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. * Alternatively, you can provide a callable expecting two params: a message string and the debug level: * * ```php * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; * ``` * * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` * level output is used: * * ```php * $mail->Debugoutput = new myPsr3Logger; * ``` * * @see SMTP::$Debugoutput * * @var string|callable|\Psr\Log\LoggerInterface */ public $Debugoutput = 'echo'; /** * Whether to keep the SMTP connection open after each message. * If this is set to true then the connection will remain open after a send, * and closing the connection will require an explicit call to smtpClose(). * It's a good idea to use this if you are sending multiple messages as it reduces overhead. * See the mailing list example for how to use it. * * @var bool */ public $SMTPKeepAlive = false; /** * Whether to split multiple to addresses into multiple messages * or send them all in one message. * Only supported in `mail` and `sendmail` transports, not in SMTP. * * @var bool * * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! */ public $SingleTo = false; /** * Storage for addresses when SingleTo is enabled. * * @var array */ protected $SingleToArray = []; /** * Whether to generate VERP addresses on send. * Only applicable when sending via SMTP. * * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path * @see http://www.postfix.org/VERP_README.html Postfix VERP info * * @var bool */ public $do_verp = false; /** * Whether to allow sending messages with an empty body. * * @var bool */ public $AllowEmpty = false; /** * DKIM selector. * * @var string */ public $DKIM_selector = ''; /** * DKIM Identity. * Usually the email address used as the source of the email. * * @var string */ public $DKIM_identity = ''; /** * DKIM passphrase. * Used if your key is encrypted. * * @var string */ public $DKIM_passphrase = ''; /** * DKIM signing domain name. * * @example 'example.com' * * @var string */ public $DKIM_domain = ''; /** * DKIM Copy header field values for diagnostic use. * * @var bool */ public $DKIM_copyHeaderFields = true; /** * DKIM Extra signing headers. * * @example ['List-Unsubscribe', 'List-Help'] * * @var array */ public $DKIM_extraHeaders = []; /** * DKIM private key file path. * * @var string */ public $DKIM_private = ''; /** * DKIM private key string. * * If set, takes precedence over `$DKIM_private`. * * @var string */ public $DKIM_private_string = ''; /** * Callback Action function name. * * The function that handles the result of the send email action. * It is called out by send() for each email sent. * * Value can be any php callable: http://www.php.net/is_callable * * Parameters: * bool $result result of the send action * array $to email addresses of the recipients * array $cc cc email addresses * array $bcc bcc email addresses * string $subject the subject * string $body the email body * string $from email address of sender * string $extra extra information of possible use * "smtp_transaction_id' => last smtp transaction id * * @var string */ public $action_function = ''; /** * What to put in the X-Mailer header. * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. * * @var string|null */ public $XMailer = ''; /** * Which validator to use by default when validating email addresses. * May be a callable to inject your own validator, but there are several built-in validators. * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. * * @see PHPMailer::validateAddress() * * @var string|callable */ public static $validator = 'php'; /** * An instance of the SMTP sender class. * * @var SMTP */ protected $smtp; /** * The array of 'to' names and addresses. * * @var array */ protected $to = []; /** * The array of 'cc' names and addresses. * * @var array */ protected $cc = []; /** * The array of 'bcc' names and addresses. * * @var array */ protected $bcc = []; /** * The array of reply-to names and addresses. * * @var array */ protected $ReplyTo = []; /** * An array of all kinds of addresses. * Includes all of $to, $cc, $bcc. * * @see PHPMailer::$to * @see PHPMailer::$cc * @see PHPMailer::$bcc * * @var array */ protected $all_recipients = []; /** * An array of names and addresses queued for validation. * In send(), valid and non duplicate entries are moved to $all_recipients * and one of $to, $cc, or $bcc. * This array is used only for addresses with IDN. * * @see PHPMailer::$to * @see PHPMailer::$cc * @see PHPMailer::$bcc * @see PHPMailer::$all_recipients * * @var array */ protected $RecipientsQueue = []; /** * An array of reply-to names and addresses queued for validation. * In send(), valid and non duplicate entries are moved to $ReplyTo. * This array is used only for addresses with IDN. * * @see PHPMailer::$ReplyTo * * @var array */ protected $ReplyToQueue = []; /** * The array of attachments. * * @var array */ protected $attachment = []; /** * The array of custom headers. * * @var array */ protected $CustomHeader = []; /** * The most recent Message-ID (including angular brackets). * * @var string */ protected $lastMessageID = ''; /** * The message's MIME type. * * @var string */ protected $message_type = ''; /** * The array of MIME boundary strings. * * @var array */ protected $boundary = []; /** * The array of available text strings for the current language. * * @var array */ protected $language = []; /** * The number of errors encountered. * * @var int */ protected $error_count = 0; /** * The S/MIME certificate file path. * * @var string */ protected $sign_cert_file = ''; /** * The S/MIME key file path. * * @var string */ protected $sign_key_file = ''; /** * The optional S/MIME extra certificates ("CA Chain") file path. * * @var string */ protected $sign_extracerts_file = ''; /** * The S/MIME password for the key. * Used only if the key is encrypted. * * @var string */ protected $sign_key_pass = ''; /** * Whether to throw exceptions for errors. * * @var bool */ protected $exceptions = false; /** * Unique ID used for message ID and boundaries. * * @var string */ protected $uniqueid = ''; /** * The PHPMailer Version number. * * @var string */ const VERSION = '6.6.3'; /** * Error severity: message only, continue processing. * * @var int */ const STOP_MESSAGE = 0; /** * Error severity: message, likely ok to continue processing. * * @var int */ const STOP_CONTINUE = 1; /** * Error severity: message, plus full stop, critical error reached. * * @var int */ const STOP_CRITICAL = 2; /** * The SMTP standard CRLF line break. * If you want to change line break format, change static::$LE, not this. */ const CRLF = "\r\n"; /** * "Folding White Space" a white space string used for line folding. */ const FWS = ' '; /** * SMTP RFC standard line ending; Carriage Return, Line Feed. * * @var string */ protected static $LE = self::CRLF; /** * The maximum line length supported by mail(). * * Background: mail() will sometimes corrupt messages * with headers headers longer than 65 chars, see #818. * * @var int */ const MAIL_MAX_LINE_LENGTH = 63; /** * The maximum line length allowed by RFC 2822 section 2.1.1. * * @var int */ const MAX_LINE_LENGTH = 998; /** * The lower maximum line length allowed by RFC 2822 section 2.1.1. * This length does NOT include the line break * 76 means that lines will be 77 or 78 chars depending on whether * the line break format is LF or CRLF; both are valid. * * @var int */ const STD_LINE_LENGTH = 76; /** * Constructor. * * @param bool $exceptions Should we throw external exceptions? */ public function __construct($exceptions = null) { if (null !== $exceptions) { $this->exceptions = (bool) $exceptions; } //Pick an appropriate debug output format automatically $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); } /** * Destructor. */ public function __destruct() { //Close any open SMTP connection nicely $this->smtpClose(); } /** * Call mail() in a safe_mode-aware fashion. * Also, unless sendmail_path points to sendmail (or something that * claims to be sendmail), don't pass params (not a perfect fix, * but it will do). * * @param string $to To * @param string $subject Subject * @param string $body Message Body * @param string $header Additional Header(s) * @param string|null $params Params * * @return bool */ private function mailPassthru($to, $subject, $body, $header, $params) { //Check overloading of mail function to avoid double-encoding if (ini_get('mbstring.func_overload') & 1) { $subject = $this->secureHeader($subject); } else { $subject = $this->encodeHeader($this->secureHeader($subject)); } //Calling mail() with null params breaks $this->edebug('Sending with mail()'); $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); $this->edebug("Envelope sender: {$this->Sender}"); $this->edebug("To: {$to}"); $this->edebug("Subject: {$subject}"); $this->edebug("Headers: {$header}"); if (!$this->UseSendmailOptions || null === $params) { $result = @mail($to, $subject, $body, $header); } else { $this->edebug("Additional params: {$params}"); $result = @mail($to, $subject, $body, $header, $params); } $this->edebug('Result: ' . ($result ? 'true' : 'false')); return $result; } /** * Output debugging info via a user-defined method. * Only generates output if debug output is enabled. * * @see PHPMailer::$Debugoutput * @see PHPMailer::$SMTPDebug * * @param string $str */ protected function edebug($str) { if ($this->SMTPDebug <= 0) { return; } //Is this a PSR-3 logger? if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { $this->Debugoutput->debug($str); return; } //Avoid clash with built-in function names if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { call_user_func($this->Debugoutput, $str, $this->SMTPDebug); return; } switch ($this->Debugoutput) { case 'error_log': //Don't output, just log /** @noinspection ForgottenDebugOutputInspection */ error_log($str); break; case 'html': //Cleans up output a bit for a better looking, HTML-safe output echo htmlentities( preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8' ), "
\n"; break; case 'echo': default: //Normalize line breaks $str = preg_replace('/\r\n|\r/m', "\n", $str); echo gmdate('Y-m-d H:i:s'), "\t", //Trim trailing space trim( //Indent for readability, except for trailing break str_replace( "\n", "\n \t ", trim($str) ) ), "\n"; } } /** * Sets message type to HTML or plain. * * @param bool $isHtml True for HTML mode */ public function isHTML($isHtml = true) { if ($isHtml) { $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; } else { $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; } } /** * Send messages using SMTP. */ public function isSMTP() { $this->Mailer = 'smtp'; } /** * Send messages using PHP's mail() function. */ public function isMail() { $this->Mailer = 'mail'; } /** * Send messages using $Sendmail. */ public function isSendmail() { $ini_sendmail_path = ini_get('sendmail_path'); if (false === stripos($ini_sendmail_path, 'sendmail')) { $this->Sendmail = '/usr/sbin/sendmail'; } else { $this->Sendmail = $ini_sendmail_path; } $this->Mailer = 'sendmail'; } /** * Send messages using qmail. */ public function isQmail() { $ini_sendmail_path = ini_get('sendmail_path'); if (false === stripos($ini_sendmail_path, 'qmail')) { $this->Sendmail = '/var/qmail/bin/qmail-inject'; } else { $this->Sendmail = $ini_sendmail_path; } $this->Mailer = 'qmail'; } /** * Add a "To" address. * * @param string $address The email address to send to * @param string $name * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ public function addAddress($address, $name = '') { return $this->addOrEnqueueAnAddress('to', $address, $name); } /** * Add a "CC" address. * * @param string $address The email address to send to * @param string $name * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ public function addCC($address, $name = '') { return $this->addOrEnqueueAnAddress('cc', $address, $name); } /** * Add a "BCC" address. * * @param string $address The email address to send to * @param string $name * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ public function addBCC($address, $name = '') { return $this->addOrEnqueueAnAddress('bcc', $address, $name); } /** * Add a "Reply-To" address. * * @param string $address The email address to reply to * @param string $name * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ public function addReplyTo($address, $name = '') { return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); } /** * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still * be modified after calling this function), addition of such addresses is delayed until send(). * Addresses that have been added already return false, but do not throw exceptions. * * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' * @param string $address The email address * @param string $name An optional username associated with the address * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ protected function addOrEnqueueAnAddress($kind, $address, $name) { $pos = false; if ($address !== null) { $address = trim($address); $pos = strrpos($address, '@'); } if (false === $pos) { //At-sign is missing. $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), $kind, $address ); $this->setError($error_message); $this->edebug($error_message); if ($this->exceptions) { throw new Exception($error_message); } return false; } if ($name !== null) { $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim } else { $name = ''; } $params = [$kind, $address, $name]; //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. //Domain is assumed to be whatever is after the last @ symbol in the address if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { if ('Reply-To' !== $kind) { if (!array_key_exists($address, $this->RecipientsQueue)) { $this->RecipientsQueue[$address] = $params; return true; } } elseif (!array_key_exists($address, $this->ReplyToQueue)) { $this->ReplyToQueue[$address] = $params; return true; } return false; } //Immediately add standard addresses without IDN. return call_user_func_array([$this, 'addAnAddress'], $params); } /** * Add an address to one of the recipient arrays or to the ReplyTo array. * Addresses that have been added already return false, but do not throw exceptions. * * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' * @param string $address The email address to send, resp. to reply to * @param string $name * * @throws Exception * * @return bool true on success, false if address already used or invalid in some way */ protected function addAnAddress($kind, $address, $name = '') { if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { $error_message = sprintf( '%s: %s', $this->lang('Invalid recipient kind'), $kind ); $this->setError($error_message); $this->edebug($error_message); if ($this->exceptions) { throw new Exception($error_message); } return false; } if (!static::validateAddress($address)) { $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), $kind, $address ); $this->setError($error_message); $this->edebug($error_message); if ($this->exceptions) { throw new Exception($error_message); } return false; } if ('Reply-To' !== $kind) { if (!array_key_exists(strtolower($address), $this->all_recipients)) { $this->{$kind}[] = [$address, $name]; $this->all_recipients[strtolower($address)] = true; return true; } } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { $this->ReplyTo[strtolower($address)] = [$address, $name]; return true; } return false; } /** * Parse and validate a string containing one or more RFC822-style comma-separated email addresses * of the form "display name
" into an array of name/address pairs. * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. * Note that quotes in the name part are removed. * * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation * * @param string $addrstr The address list string * @param bool $useimap Whether to use the IMAP extension to parse the list * @param string $charset The charset to use when decoding the address list string. * * @return array */ public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) { $addresses = []; if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { //Use this built-in parser if it's available $list = imap_rfc822_parse_adrlist($addrstr, ''); // Clear any potential IMAP errors to get rid of notices being thrown at end of script. imap_errors(); foreach ($list as $address) { if ( '.SYNTAX-ERROR.' !== $address->host && static::validateAddress($address->mailbox . '@' . $address->host) ) { //Decode the name part if it's present and encoded if ( property_exists($address, 'personal') && //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $address->personal) ) { $origCharset = mb_internal_encoding(); mb_internal_encoding($charset); //Undo any RFC2047-encoded spaces-as-underscores $address->personal = str_replace('_', '=20', $address->personal); //Decode the name $address->personal = mb_decode_mimeheader($address->personal); mb_internal_encoding($origCharset); } $addresses[] = [ 'name' => (property_exists($address, 'personal') ? $address->personal : ''), 'address' => $address->mailbox . '@' . $address->host, ]; } } } else { //Use this simpler parser $list = explode(',', $addrstr); foreach ($list as $address) { $address = trim($address); //Is there a separate name part? if (strpos($address, '<') === false) { //No separate name, just use the whole thing if (static::validateAddress($address)) { $addresses[] = [ 'name' => '', 'address' => $address, ]; } } else { list($name, $email) = explode('<', $address); $email = trim(str_replace('>', '', $email)); $name = trim($name); if (static::validateAddress($email)) { //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled //If this name is encoded, decode it if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { $origCharset = mb_internal_encoding(); mb_internal_encoding($charset); //Undo any RFC2047-encoded spaces-as-underscores $name = str_replace('_', '=20', $name); //Decode the name $name = mb_decode_mimeheader($name); mb_internal_encoding($origCharset); } $addresses[] = [ //Remove any surrounding quotes and spaces from the name 'name' => trim($name, '\'" '), 'address' => $email, ]; } } } } return $addresses; } /** * Set the From and FromName properties. * * @param string $address * @param string $name * @param bool $auto Whether to also set the Sender address, defaults to true * * @throws Exception * * @return bool */ public function setFrom($address, $name = '', $auto = true) { $address = trim($address); $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim //Don't validate now addresses with IDN. Will be done in send(). $pos = strrpos($address, '@'); if ( (false === $pos) || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) && !static::validateAddress($address)) ) { $error_message = sprintf( '%s (From): %s', $this->lang('invalid_address'), $address ); $this->setError($error_message); $this->edebug($error_message); if ($this->exceptions) { throw new Exception($error_message); } return false; } $this->From = $address; $this->FromName = $name; if ($auto && empty($this->Sender)) { $this->Sender = $address; } return true; } /** * Return the Message-ID header of the last email. * Technically this is the value from the last time the headers were created, * but it's also the message ID of the last sent message except in * pathological cases. * * @return string */ public function getLastMessageID() { return $this->lastMessageID; } /** * Check that a string looks like an email address. * Validation patterns supported: * * `auto` Pick best pattern automatically; * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; * * `pcre` Use old PCRE implementation; * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. * * `noregex` Don't use a regex: super fast, really dumb. * Alternatively you may pass in a callable to inject your own validator, for example: * * ```php * PHPMailer::validateAddress('user@example.com', function($address) { * return (strpos($address, '@') !== false); * }); * ``` * * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. * * @param string $address The email address to check * @param string|callable $patternselect Which pattern to use * * @return bool */ public static function validateAddress($address, $patternselect = null) { if (null === $patternselect) { $patternselect = static::$validator; } //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 if (is_callable($patternselect) && !is_string($patternselect)) { return call_user_func($patternselect, $address); } //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { return false; } switch ($patternselect) { case 'pcre': //Kept for BC case 'pcre8': /* * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL * is based. * In addition to the addresses allowed by filter_var, also permits: * * dotless domains: `a@b` * * comments: `1234 @ local(blah) .machine .example` * * quoted elements: `'"test blah"@example.org'` * * numeric TLDs: `a@b.123` * * unbracketed IPv4 literals: `a@192.168.0.1` * * IPv6 literals: 'first.last@[IPv6:a1::]' * Not all of these will necessarily work for sending! * * @see http://squiloople.com/2009/12/20/email-address-validation/ * @copyright 2009-2010 Michael Rushton * Feel free to use and redistribute this code. But please keep this copyright notice. */ return (bool) preg_match( '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address ); case 'html5': /* * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. * * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) */ return (bool) preg_match( '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', $address ); case 'php': default: return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; } } /** * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the * `intl` and `mbstring` PHP extensions. * * @return bool `true` if required functions for IDN support are present */ public static function idnSupported() { return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); } /** * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. * This function silently returns unmodified address if: * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) * - Conversion to punycode is impossible (e.g. required PHP functions are not available) * or fails for any reason (e.g. domain contains characters not allowed in an IDN). * * @see PHPMailer::$CharSet * * @param string $address The email address to convert * * @return string The encoded address in ASCII form */ public function punyencodeAddress($address) { //Verify we have required functions, CharSet, and at-sign. $pos = strrpos($address, '@'); if ( !empty($this->CharSet) && false !== $pos && static::idnSupported() ) { $domain = substr($address, ++$pos); //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { //Convert the domain from whatever charset it's in to UTF-8 $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); //Ignore IDE complaints about this line - method signature changed in PHP 5.4 $errorcode = 0; if (defined('INTL_IDNA_VARIANT_UTS46')) { //Use the current punycode standard (appeared in PHP 7.2) $punycode = idn_to_ascii( $domain, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46 ); } elseif (defined('INTL_IDNA_VARIANT_2003')) { //Fall back to this old, deprecated/removed encoding $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); } else { //Fall back to a default we don't know about $punycode = idn_to_ascii($domain, $errorcode); } if (false !== $punycode) { return substr($address, 0, $pos) . $punycode; } } } return $address; } /** * Create a message and send it. * Uses the sending method specified by $Mailer. * * @throws Exception * * @return bool false on error - See the ErrorInfo property for details of the error */ public function send() { try { if (!$this->preSend()) { return false; } return $this->postSend(); } catch (Exception $exc) { $this->mailHeader = ''; $this->setError($exc->getMessage()); if ($this->exceptions) { throw $exc; } return false; } } /** * Prepare a message for sending. * * @throws Exception * * @return bool */ public function preSend() { if ( 'smtp' === $this->Mailer || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) ) { //SMTP mandates RFC-compliant line endings //and it's also used with mail() on Windows static::setLE(self::CRLF); } else { //Maintain backward compatibility with legacy Linux command line mailers static::setLE(PHP_EOL); } //Check for buggy PHP versions that add a header with an incorrect line break if ( 'mail' === $this->Mailer && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) && ini_get('mail.add_x_header') === '1' && stripos(PHP_OS, 'WIN') === 0 ) { trigger_error($this->lang('buggy_php'), E_USER_WARNING); } try { $this->error_count = 0; //Reset errors $this->mailHeader = ''; //Dequeue recipient and Reply-To addresses with IDN foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { $params[1] = $this->punyencodeAddress($params[1]); call_user_func_array([$this, 'addAnAddress'], $params); } if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); } //Validate From, Sender, and ConfirmReadingTo addresses foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { $this->{$address_kind} = trim($this->{$address_kind}); if (empty($this->{$address_kind})) { continue; } $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); if (!static::validateAddress($this->{$address_kind})) { $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), $address_kind, $this->{$address_kind} ); $this->setError($error_message); $this->edebug($error_message); if ($this->exceptions) { throw new Exception($error_message); } return false; } } //Set whether the message is multipart/alternative if ($this->alternativeExists()) { $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; } $this->setMessageType(); //Refuse to send an empty message unless we are specifically allowing it if (!$this->AllowEmpty && empty($this->Body)) { throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); } //Trim subject consistently $this->Subject = trim($this->Subject); //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) $this->MIMEHeader = ''; $this->MIMEBody = $this->createBody(); //createBody may have added some headers, so retain them $tempheaders = $this->MIMEHeader; $this->MIMEHeader = $this->createHeader(); $this->MIMEHeader .= $tempheaders; //To capture the complete message when using mail(), create //an extra header list which createHeader() doesn't fold in if ('mail' === $this->Mailer) { if (count($this->to) > 0) { $this->mailHeader .= $this->addrAppend('To', $this->to); } else { $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); } $this->mailHeader .= $this->headerLine( 'Subject', $this->encodeHeader($this->secureHeader($this->Subject)) ); } //Sign with DKIM if enabled if ( !empty($this->DKIM_domain) && !empty($this->DKIM_selector) && (!empty($this->DKIM_private_string) || (!empty($this->DKIM_private) && static::isPermittedPath($this->DKIM_private) && file_exists($this->DKIM_private) ) ) ) { $header_dkim = $this->DKIM_Add( $this->MIMEHeader . $this->mailHeader, $this->encodeHeader($this->secureHeader($this->Subject)), $this->MIMEBody ); $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . static::normalizeBreaks($header_dkim) . static::$LE; } return true; } catch (Exception $exc) { $this->setError($exc->getMessage()); if ($this->exceptions) { throw $exc; } return false; } } /** * Actually send a message via the selected mechanism. * * @throws Exception * * @return bool */ public function postSend() { try { //Choose the mailer and send through it switch ($this->Mailer) { case 'sendmail': case 'qmail': return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); case 'smtp': return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); case 'mail': return $this->mailSend($this->MIMEHeader, $this->MIMEBody); default: $sendMethod = $this->Mailer . 'Send'; if (method_exists($this, $sendMethod)) { return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); } return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (Exception $exc) { if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) { $this->smtp->reset(); } $this->setError($exc->getMessage()); $this->edebug($exc->getMessage()); if ($this->exceptions) { throw $exc; } } return false; } /** * Send mail using the $Sendmail program. * * @see PHPMailer::$Sendmail * * @param string $header The message headers * @param string $body The message body * * @throws Exception * * @return bool */ protected function sendmailSend($header, $body) { if ($this->Mailer === 'qmail') { $this->edebug('Sending with qmail'); } else { $this->edebug('Sending with sendmail'); } $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html //Example problem: https://www.drupal.org/node/1057954 //PHP 5.6 workaround $sendmail_from_value = ini_get('sendmail_from'); if (empty($this->Sender) && !empty($sendmail_from_value)) { //PHP config has a sender address we can use $this->Sender = ini_get('sendmail_from'); } //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { if ($this->Mailer === 'qmail') { $sendmailFmt = '%s -f%s'; } else { $sendmailFmt = '%s -oi -f%s -t'; } } else { //allow sendmail to choose a default envelope sender. It may //seem preferable to force it to use the From header as with //SMTP, but that introduces new problems (see //), and //it has historically worked this way. $sendmailFmt = '%s -oi -t'; } $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); $this->edebug('Sendmail path: ' . $this->Sendmail); $this->edebug('Sendmail command: ' . $sendmail); $this->edebug('Envelope sender: ' . $this->Sender); $this->edebug("Headers: {$header}"); if ($this->SingleTo) { foreach ($this->SingleToArray as $toAddr) { $mail = @popen($sendmail, 'w'); if (!$mail) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } $this->edebug("To: {$toAddr}"); fwrite($mail, 'To: ' . $toAddr . "\n"); fwrite($mail, $header); fwrite($mail, $body); $result = pclose($mail); $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); $this->doCallback( ($result === 0), [[$addrinfo['address'], $addrinfo['name']]], $this->cc, $this->bcc, $this->Subject, $body, $this->From, [] ); $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); if (0 !== $result) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } } } else { $mail = @popen($sendmail, 'w'); if (!$mail) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } fwrite($mail, $header); fwrite($mail, $body); $result = pclose($mail); $this->doCallback( ($result === 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, [] ); $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); if (0 !== $result) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } } return true; } /** * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. * * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report * * @param string $string The string to be validated * * @return bool */ protected static function isShellSafe($string) { //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, //so we don't. if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { return false; } if ( escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) ) { return false; } $length = strlen($string); for ($i = 0; $i < $length; ++$i) { $c = $string[$i]; //All other characters have a special meaning in at least one common shell, including = and +. //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. //Note that this does permit non-Latin alphanumeric characters based on the current locale. if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { return false; } } return true; } /** * Check whether a file path is of a permitted type. * Used to reject URLs and phar files from functions that access local file paths, * such as addAttachment. * * @param string $path A relative or absolute path to a file * * @return bool */ protected static function isPermittedPath($path) { //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); } /** * Check whether a file path is safe, accessible, and readable. * * @param string $path A relative or absolute path to a file * * @return bool */ protected static function fileIsAccessible($path) { if (!static::isPermittedPath($path)) { return false; } $readable = file_exists($path); //If not a UNC path (expected to start with \\), check read permission, see #2069 if (strpos($path, '\\\\') !== 0) { $readable = $readable && is_readable($path); } return $readable; } /** * Send mail using the PHP mail() function. * * @see http://www.php.net/manual/en/book.mail.php * * @param string $header The message headers * @param string $body The message body * * @throws Exception * * @return bool */ protected function mailSend($header, $body) { $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; $toArr = []; foreach ($this->to as $toaddr) { $toArr[] = $this->addrFormat($toaddr); } $to = implode(', ', $toArr); $params = null; //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver //A space after `-f` is optional, but there is a long history of its presence //causing problems, so we don't use one //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html //Example problem: https://www.drupal.org/node/1057954 //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. //PHP 5.6 workaround $sendmail_from_value = ini_get('sendmail_from'); if (empty($this->Sender) && !empty($sendmail_from_value)) { //PHP config has a sender address we can use $this->Sender = ini_get('sendmail_from'); } if (!empty($this->Sender) && static::validateAddress($this->Sender)) { if (self::isShellSafe($this->Sender)) { $params = sprintf('-f%s', $this->Sender); } $old_from = ini_get('sendmail_from'); ini_set('sendmail_from', $this->Sender); } $result = false; if ($this->SingleTo && count($toArr) > 1) { foreach ($toArr as $toAddr) { $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); $this->doCallback( $result, [[$addrinfo['address'], $addrinfo['name']]], $this->cc, $this->bcc, $this->Subject, $body, $this->From, [] ); } } else { $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); } if (isset($old_from)) { ini_set('sendmail_from', $old_from); } if (!$result) { throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); } return true; } /** * Get an instance to use for SMTP operations. * Override this function to load your own SMTP implementation, * or set one with setSMTPInstance. * * @return SMTP */ public function getSMTPInstance() { if (!is_object($this->smtp)) { $this->smtp = new SMTP(); } return $this->smtp; } /** * Provide an instance to use for SMTP operations. * * @return SMTP */ public function setSMTPInstance(SMTP $smtp) { $this->smtp = $smtp; return $this->smtp; } /** * Send mail via SMTP. * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. * * @see PHPMailer::setSMTPInstance() to use a different class. * * @uses \PHPMailer\PHPMailer\SMTP * * @param string $header The message headers * @param string $body The message body * * @throws Exception * * @return bool */ protected function smtpSend($header, $body) { $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; $bad_rcpt = []; if (!$this->smtpConnect($this->SMTPOptions)) { throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); } //Sender already validated in preSend() if ('' === $this->Sender) { $smtp_from = $this->From; } else { $smtp_from = $this->Sender; } if (!$this->smtp->mail($smtp_from)) { $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); } $callbacks = []; //Attempt to send to all recipients foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { foreach ($togroup as $to) { if (!$this->smtp->recipient($to[0], $this->dsn)) { $error = $this->smtp->getError(); $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; $isSent = false; } else { $isSent = true; } $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; } } //Only send the DATA command if we have viable recipients if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); } $smtp_transaction_id = $this->smtp->getLastTransactionID(); if ($this->SMTPKeepAlive) { $this->smtp->reset(); } else { $this->smtp->quit(); $this->smtp->close(); } foreach ($callbacks as $cb) { $this->doCallback( $cb['issent'], [[$cb['to'], $cb['name']]], [], [], $this->Subject, $body, $this->From, ['smtp_transaction_id' => $smtp_transaction_id] ); } //Create error message for any bad addresses if (count($bad_rcpt) > 0) { $errstr = ''; foreach ($bad_rcpt as $bad) { $errstr .= $bad['to'] . ': ' . $bad['error']; } throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); } return true; } /** * Initiate a connection to an SMTP server. * Returns false if the operation failed. * * @param array $options An array of options compatible with stream_context_create() * * @throws Exception * * @uses \PHPMailer\PHPMailer\SMTP * * @return bool */ public function smtpConnect($options = null) { if (null === $this->smtp) { $this->smtp = $this->getSMTPInstance(); } //If no options are provided, use whatever is set in the instance if (null === $options) { $options = $this->SMTPOptions; } //Already connected? if ($this->smtp->connected()) { return true; } $this->smtp->setTimeout($this->Timeout); $this->smtp->setDebugLevel($this->SMTPDebug); $this->smtp->setDebugOutput($this->Debugoutput); $this->smtp->setVerp($this->do_verp); $hosts = explode(';', $this->Host); $lastexception = null; foreach ($hosts as $hostentry) { $hostinfo = []; if ( !preg_match( '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', trim($hostentry), $hostinfo ) ) { $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); //Not a valid host entry continue; } //$hostinfo[1]: optional ssl or tls prefix //$hostinfo[2]: the hostname //$hostinfo[3]: optional port number //The host string prefix can temporarily override the current setting for SMTPSecure //If it's not specified, the default value is used //Check the host name is a valid name or IP address before trying to use it if (!static::isValidHost($hostinfo[2])) { $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); continue; } $prefix = ''; $secure = $this->SMTPSecure; $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { $prefix = 'ssl://'; $tls = false; //Can't have SSL and TLS at the same time $secure = static::ENCRYPTION_SMTPS; } elseif ('tls' === $hostinfo[1]) { $tls = true; //TLS doesn't use a prefix $secure = static::ENCRYPTION_STARTTLS; } //Do we need the OpenSSL extension? $sslext = defined('OPENSSL_ALGO_SHA256'); if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled if (!$sslext) { throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); } } $host = $hostinfo[2]; $port = $this->Port; if ( array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536 ) { $port = (int) $hostinfo[3]; } if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { try { if ($this->Helo) { $hello = $this->Helo; } else { $hello = $this->serverHostname(); } $this->smtp->hello($hello); //Automatically enable TLS encryption if: //* it's not disabled //* we have openssl extension //* we are not already using SSL //* the server offers STARTTLS if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { $tls = true; } if ($tls) { if (!$this->smtp->startTLS()) { $message = $this->getSmtpErrorMessage('connect_host'); throw new Exception($message); } //We must resend EHLO after TLS negotiation $this->smtp->hello($hello); } if ( $this->SMTPAuth && !$this->smtp->authenticate( $this->Username, $this->Password, $this->AuthType, $this->oauth ) ) { throw new Exception($this->lang('authenticate')); } return true; } catch (Exception $exc) { $lastexception = $exc; $this->edebug($exc->getMessage()); //We must have connected, but then failed TLS or Auth, so close connection nicely $this->smtp->quit(); } } } //If we get here, all connection attempts have failed, so close connection hard $this->smtp->close(); //As we've caught all exceptions, just report whatever the last one was if ($this->exceptions && null !== $lastexception) { throw $lastexception; } if ($this->exceptions) { // no exception was thrown, likely $this->smtp->connect() failed $message = $this->getSmtpErrorMessage('connect_host'); throw new Exception($message); } return false; } /** * Close the active SMTP session if one exists. */ public function smtpClose() { if ((null !== $this->smtp) && $this->smtp->connected()) { $this->smtp->quit(); $this->smtp->close(); } } /** * Set the language for error messages. * The default language is English. * * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") * Optionally, the language code can be enhanced with a 4-character * script annotation and/or a 2-character country annotation. * @param string $lang_path Path to the language file directory, with trailing separator (slash) * Do not set this from user input! * * @return bool Returns true if the requested language was loaded, false otherwise. */ public function setLanguage($langcode = 'en', $lang_path = '') { //Backwards compatibility for renamed language codes $renamed_langcodes = [ 'br' => 'pt_br', 'cz' => 'cs', 'dk' => 'da', 'no' => 'nb', 'se' => 'sv', 'rs' => 'sr', 'tg' => 'tl', 'am' => 'hy', ]; if (array_key_exists($langcode, $renamed_langcodes)) { $langcode = $renamed_langcodes[$langcode]; } //Define full set of translatable strings in English $PHPMAILER_LANG = [ 'authenticate' => 'SMTP Error: Could not authenticate.', 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', 'data_not_accepted' => 'SMTP Error: data not accepted.', 'empty_message' => 'Message body empty', 'encoding' => 'Unknown encoding: ', 'execute' => 'Could not execute: ', 'extension_missing' => 'Extension missing: ', 'file_access' => 'Could not access file: ', 'file_open' => 'File Error: Could not open file: ', 'from_failed' => 'The following From address failed: ', 'instantiate' => 'Could not instantiate mail function.', 'invalid_address' => 'Invalid address: ', 'invalid_header' => 'Invalid header name or value', 'invalid_hostentry' => 'Invalid hostentry: ', 'invalid_host' => 'Invalid host: ', 'mailer_not_supported' => ' mailer is not supported.', 'provide_address' => 'You must provide at least one recipient email address.', 'recipients_failed' => 'SMTP Error: The following recipients failed: ', 'signing' => 'Signing Error: ', 'smtp_code' => 'SMTP code: ', 'smtp_code_ex' => 'Additional SMTP info: ', 'smtp_connect_failed' => 'SMTP connect() failed.', 'smtp_detail' => 'Detail: ', 'smtp_error' => 'SMTP server error: ', 'variable_set' => 'Cannot set or reset variable: ', ]; if (empty($lang_path)) { //Calculate an absolute path so it can work if CWD is not here $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; } //Validate $langcode $foundlang = true; $langcode = strtolower($langcode); if ( !preg_match('/^(?P[a-z]{2})(?P