"apereo/phpcas": "^1.3",
"diogocomposer/xmpphp": "^3.0",
"ezyang/htmlpurifier": "^4.10",
+ "hoa/consistency": "^1.17.05.02",
"masterminds/html5": "^2.6",
"mf2/mf2": "^0.4.6",
"michelf/php-markdown": "^1.8.0",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ac49a57ede587e949b9bad4b3c080a1d",
+ "content-hash": "1f145be7e041fab248e0ffa15684f12f",
"packages": [
{
"name": "apereo/phpcas",
],
"time": "2018-02-23T01:58:20+00:00"
},
+ {
+ "name": "hoa/consistency",
+ "version": "1.17.05.02",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Consistency.git",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/exception": "~1.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "hoa/stream": "~1.0",
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Consistency\\": "."
+ },
+ "files": [
+ "Prelude.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Consistency library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "autoloader",
+ "callable",
+ "consistency",
+ "entity",
+ "flex",
+ "keyword",
+ "library"
+ ],
+ "time": "2017-05-02T12:18:12+00:00"
+ },
+ {
+ "name": "hoa/event",
+ "version": "1.17.01.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Event.git",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Event\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Event library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "event",
+ "library",
+ "listener",
+ "observer"
+ ],
+ "time": "2017-01-13T15:30:50+00:00"
+ },
+ {
+ "name": "hoa/exception",
+ "version": "1.17.01.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Exception.git",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Exception\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Exception library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "exception",
+ "library"
+ ],
+ "time": "2017-01-16T07:53:27+00:00"
+ },
{
"name": "masterminds/html5",
"version": "2.6.0",
],
"time": "2017-08-08T07:44:07+00:00"
},
- {
- "name": "hoa/consistency",
- "version": "1.17.05.02",
- "source": {
- "type": "git",
- "url": "https://github.com/hoaproject/Consistency.git",
- "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f",
- "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f",
- "shasum": ""
- },
- "require": {
- "hoa/exception": "~1.0",
- "php": ">=5.5.0"
- },
- "require-dev": {
- "hoa/stream": "~1.0",
- "hoa/test": "~2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Hoa\\Consistency\\": "."
- },
- "files": [
- "Prelude.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Ivan Enderlin",
- "email": "ivan.enderlin@hoa-project.net"
- },
- {
- "name": "Hoa community",
- "homepage": "https://hoa-project.net/"
- }
- ],
- "description": "The Hoa\\Consistency library.",
- "homepage": "https://hoa-project.net/",
- "keywords": [
- "autoloader",
- "callable",
- "consistency",
- "entity",
- "flex",
- "keyword",
- "library"
- ],
- "time": "2017-05-02T12:18:12+00:00"
- },
- {
- "name": "hoa/event",
- "version": "1.17.01.13",
- "source": {
- "type": "git",
- "url": "https://github.com/hoaproject/Event.git",
- "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54",
- "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54",
- "shasum": ""
- },
- "require": {
- "hoa/consistency": "~1.0",
- "hoa/exception": "~1.0"
- },
- "require-dev": {
- "hoa/test": "~2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Hoa\\Event\\": "."
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Ivan Enderlin",
- "email": "ivan.enderlin@hoa-project.net"
- },
- {
- "name": "Hoa community",
- "homepage": "https://hoa-project.net/"
- }
- ],
- "description": "The Hoa\\Event library.",
- "homepage": "https://hoa-project.net/",
- "keywords": [
- "event",
- "library",
- "listener",
- "observer"
- ],
- "time": "2017-01-13T15:30:50+00:00"
- },
- {
- "name": "hoa/exception",
- "version": "1.17.01.16",
- "source": {
- "type": "git",
- "url": "https://github.com/hoaproject/Exception.git",
- "reference": "091727d46420a3d7468ef0595651488bfc3a458f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f",
- "reference": "091727d46420a3d7468ef0595651488bfc3a458f",
- "shasum": ""
- },
- "require": {
- "hoa/consistency": "~1.0",
- "hoa/event": "~1.0"
- },
- "require-dev": {
- "hoa/test": "~2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Hoa\\Exception\\": "."
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Ivan Enderlin",
- "email": "ivan.enderlin@hoa-project.net"
- },
- {
- "name": "Hoa community",
- "homepage": "https://hoa-project.net/"
- }
- ],
- "description": "The Hoa\\Exception library.",
- "homepage": "https://hoa-project.net/",
- "keywords": [
- "exception",
- "library"
- ],
- "time": "2017-01-16T07:53:27+00:00"
- },
{
"name": "hoa/file",
"version": "1.17.07.11",
--- /dev/null
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * PHP 5.3 implementation of function currying, using native closures.
+ * On 5.2 and lower we use the fallback implementation in util.php
+ *
+ * @param callback $fn
+ * @param ... any remaining arguments will be appended to call-time params
+ * @return callback
+ */
+function callable_left_curry($fn) {
+ $extra_args = func_get_args();
+ array_shift($extra_args);
+ return function() use ($fn, $extra_args) {
+ $args = func_get_args();
+ return call_user_func_array($fn,
+ array_merge($args, $extra_args));
+ };
+}
+++ /dev/null
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * PHP 5.3 implementation of function currying, using native closures.
- * On 5.2 and lower we use the fallback implementation in util.php
- *
- * @param callback $fn
- * @param ... any remaining arguments will be appended to call-time params
- * @return callback
- */
-function curry($fn) {
- $extra_args = func_get_args();
- array_shift($extra_args);
- return function() use ($fn, $extra_args) {
- $args = func_get_args();
- return call_user_func_array($fn,
- array_merge($args, $extra_args));
- };
-}
define('_URL_SCHEME_NO_DOMAIN', 4);
define('_URL_SCHEME_COLON_COORDINATES', 8);
-function common_url_schemes($filter=null)
+function common_url_schemes($filter = null)
{
// TODO: move these to $config
$schemes = ['http' => _URL_SCHEME_COLON_DOUBLE_SLASH,
'#ixu';
//preg_match_all($regex,$text,$matches);
//print_r($matches);
- return preg_replace_callback($regex, curry('callback_helper', $callback, $arg), $text);
+ return preg_replace_callback($regex, callable_left_curry('callback_helper', $callback, $arg), $text);
}
/**
*
* @access private
*/
-function callback_helper($matches, $callback, $arg=null)
+function callback_helper($matches, $callback, $arg = null)
{
- $url=$matches[1];
+ $url = $matches[1];
$left = strpos($matches[0], $url);
- $right = $left+strlen($url);
+ $right = $left + strlen($url);
$groupSymbolSets=[
[
'right'=>'>'
]
];
- $cannotEndWith=['.','?',',','#'];
- $original_url=$url;
+
+ $cannotEndWith = ['.','?',',','#'];
do {
- $original_url=$url;
+ $original_url = $url;
foreach ($groupSymbolSets as $groupSymbolSet) {
- if (substr($url, -1)==$groupSymbolSet['right']) {
+ if (substr($url, -1) == $groupSymbolSet['right']) {
$group_left_count = substr_count($url, $groupSymbolSet['left']);
$group_right_count = substr_count($url, $groupSymbolSet['right']);
- if ($group_left_count<$group_right_count) {
- $right-=1;
- $url=substr($url, 0, -1);
+ if ($group_left_count < $group_right_count) {
+ $right -= 1;
+ $url = substr($url, 0, -1);
}
}
}
if (in_array(substr($url, -1), $cannotEndWith)) {
- $right-=1;
+ $right -= 1;
$url=substr($url, 0, -1);
}
- } while ($original_url!=$url);
+ } while ($original_url != $url);
$result = call_user_func_array($callback, [$url, $arg]);
return substr($matches[0], 0, $left) . $result . substr($matches[0], $right);
}
-require_once INSTALLDIR . "/lib/curry.php";
+require_once INSTALLDIR . "/lib/callable_left_curry.php";
function common_linkify($url)
{
public function testProduction($callback, $curry_params, $call_params, $expected)
{
$params = array_merge(array($callback), $curry_params);
- $curried = call_user_func_array('curry', $params);
+ $curried = call_user_func_array('callable_left_curry', $params);
$result = call_user_func_array($curried, $call_params);
$this->assertEquals($expected, $result);
}
'HTMLPurifier_VarParser_Flexible' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
'HTMLPurifier_VarParser_Native' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
'HTMLPurifier_Zipper' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
+ 'Hoa\\Consistency\\Autoloader' => $vendorDir . '/hoa/consistency/Autoloader.php',
+ 'Hoa\\Consistency\\Consistency' => $vendorDir . '/hoa/consistency/Consistency.php',
+ 'Hoa\\Consistency\\Exception' => $vendorDir . '/hoa/consistency/Exception.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Autoloader' => $vendorDir . '/hoa/consistency/Test/Unit/Autoloader.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Consistency' => $vendorDir . '/hoa/consistency/Test/Unit/Consistency.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Exception' => $vendorDir . '/hoa/consistency/Test/Unit/Exception.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Xcallable' => $vendorDir . '/hoa/consistency/Test/Unit/Xcallable.php',
+ 'Hoa\\Consistency\\Xcallable' => $vendorDir . '/hoa/consistency/Xcallable.php',
+ 'Hoa\\Event\\Bucket' => $vendorDir . '/hoa/event/Bucket.php',
+ 'Hoa\\Event\\Event' => $vendorDir . '/hoa/event/Event.php',
+ 'Hoa\\Event\\Exception' => $vendorDir . '/hoa/event/Exception.php',
+ 'Hoa\\Event\\Listenable' => $vendorDir . '/hoa/event/Listenable.php',
+ 'Hoa\\Event\\Listener' => $vendorDir . '/hoa/event/Listener.php',
+ 'Hoa\\Event\\Listens' => $vendorDir . '/hoa/event/Listens.php',
+ 'Hoa\\Event\\Source' => $vendorDir . '/hoa/event/Source.php',
+ 'Hoa\\Event\\Test\\Unit\\Bucket' => $vendorDir . '/hoa/event/Test/Unit/Bucket.php',
+ 'Hoa\\Event\\Test\\Unit\\Event' => $vendorDir . '/hoa/event/Test/Unit/Event.php',
+ 'Hoa\\Event\\Test\\Unit\\Exception' => $vendorDir . '/hoa/event/Test/Unit/Exception.php',
+ 'Hoa\\Event\\Test\\Unit\\Listenable' => $vendorDir . '/hoa/event/Test/Unit/Listenable.php',
+ 'Hoa\\Event\\Test\\Unit\\Listener' => $vendorDir . '/hoa/event/Test/Unit/Listener.php',
+ 'Hoa\\Event\\Test\\Unit\\Listens' => $vendorDir . '/hoa/event/Test/Unit/Listens.php',
+ 'Hoa\\Event\\Test\\Unit\\Source' => $vendorDir . '/hoa/event/Test/Unit/Source.php',
+ 'Hoa\\Event\\Test\\Unit\\_Listenable' => $vendorDir . '/hoa/event/Test/Unit/Listens.php',
+ 'Hoa\\Exception\\Error' => $vendorDir . '/hoa/exception/Error.php',
+ 'Hoa\\Exception\\Exception' => $vendorDir . '/hoa/exception/Exception.php',
+ 'Hoa\\Exception\\Group' => $vendorDir . '/hoa/exception/Group.php',
+ 'Hoa\\Exception\\Idle' => $vendorDir . '/hoa/exception/Idle.php',
+ 'Hoa\\Exception\\Test\\Unit\\Error' => $vendorDir . '/hoa/exception/Test/Unit/Error.php',
+ 'Hoa\\Exception\\Test\\Unit\\Exception' => $vendorDir . '/hoa/exception/Test/Unit/Exception.php',
+ 'Hoa\\Exception\\Test\\Unit\\Group' => $vendorDir . '/hoa/exception/Test/Unit/Group.php',
+ 'Hoa\\Exception\\Test\\Unit\\Idle' => $vendorDir . '/hoa/exception/Test/Unit/Idle.php',
'Masterminds\\HTML5' => $vendorDir . '/masterminds/html5/src/HTML5.php',
'Masterminds\\HTML5\\Elements' => $vendorDir . '/masterminds/html5/src/HTML5/Elements.php',
'Masterminds\\HTML5\\Entities' => $vendorDir . '/masterminds/html5/src/HTML5/Entities.php',
$baseDir = dirname($vendorDir);
return array(
+ 'e88992873b7765f9b5710cab95ba5dd7' => $vendorDir . '/hoa/consistency/Prelude.php',
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'757772e28a0943a9afe83def8db95bdf' => $vendorDir . '/mf2/mf2/Mf2/Parser.php',
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'Michelf\\' => array($vendorDir . '/michelf/php-markdown/Michelf'),
'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
+ 'Hoa\\Exception\\' => array($vendorDir . '/hoa/exception'),
+ 'Hoa\\Event\\' => array($vendorDir . '/hoa/event'),
+ 'Hoa\\Consistency\\' => array($vendorDir . '/hoa/consistency'),
);
class ComposerStaticInit444c3f31864f68a3f466e2c19837e185
{
public static $files = array (
+ 'e88992873b7765f9b5710cab95ba5dd7' => __DIR__ . '/..' . '/hoa/consistency/Prelude.php',
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
'757772e28a0943a9afe83def8db95bdf' => __DIR__ . '/..' . '/mf2/mf2/Mf2/Parser.php',
'Michelf\\' => 8,
'Masterminds\\' => 12,
),
+ 'H' =>
+ array (
+ 'Hoa\\Exception\\' => 14,
+ 'Hoa\\Event\\' => 10,
+ 'Hoa\\Consistency\\' => 16,
+ ),
);
public static $prefixDirsPsr4 = array (
array (
0 => __DIR__ . '/..' . '/masterminds/html5/src',
),
+ 'Hoa\\Exception\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/hoa/exception',
+ ),
+ 'Hoa\\Event\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/hoa/event',
+ ),
+ 'Hoa\\Consistency\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/hoa/consistency',
+ ),
);
public static $prefixesPsr0 = array (
'HTMLPurifier_VarParser_Flexible' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
'HTMLPurifier_VarParser_Native' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
'HTMLPurifier_Zipper' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
+ 'Hoa\\Consistency\\Autoloader' => __DIR__ . '/..' . '/hoa/consistency/Autoloader.php',
+ 'Hoa\\Consistency\\Consistency' => __DIR__ . '/..' . '/hoa/consistency/Consistency.php',
+ 'Hoa\\Consistency\\Exception' => __DIR__ . '/..' . '/hoa/consistency/Exception.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Autoloader' => __DIR__ . '/..' . '/hoa/consistency/Test/Unit/Autoloader.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Consistency' => __DIR__ . '/..' . '/hoa/consistency/Test/Unit/Consistency.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Exception' => __DIR__ . '/..' . '/hoa/consistency/Test/Unit/Exception.php',
+ 'Hoa\\Consistency\\Test\\Unit\\Xcallable' => __DIR__ . '/..' . '/hoa/consistency/Test/Unit/Xcallable.php',
+ 'Hoa\\Consistency\\Xcallable' => __DIR__ . '/..' . '/hoa/consistency/Xcallable.php',
+ 'Hoa\\Event\\Bucket' => __DIR__ . '/..' . '/hoa/event/Bucket.php',
+ 'Hoa\\Event\\Event' => __DIR__ . '/..' . '/hoa/event/Event.php',
+ 'Hoa\\Event\\Exception' => __DIR__ . '/..' . '/hoa/event/Exception.php',
+ 'Hoa\\Event\\Listenable' => __DIR__ . '/..' . '/hoa/event/Listenable.php',
+ 'Hoa\\Event\\Listener' => __DIR__ . '/..' . '/hoa/event/Listener.php',
+ 'Hoa\\Event\\Listens' => __DIR__ . '/..' . '/hoa/event/Listens.php',
+ 'Hoa\\Event\\Source' => __DIR__ . '/..' . '/hoa/event/Source.php',
+ 'Hoa\\Event\\Test\\Unit\\Bucket' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Bucket.php',
+ 'Hoa\\Event\\Test\\Unit\\Event' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Event.php',
+ 'Hoa\\Event\\Test\\Unit\\Exception' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Exception.php',
+ 'Hoa\\Event\\Test\\Unit\\Listenable' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Listenable.php',
+ 'Hoa\\Event\\Test\\Unit\\Listener' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Listener.php',
+ 'Hoa\\Event\\Test\\Unit\\Listens' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Listens.php',
+ 'Hoa\\Event\\Test\\Unit\\Source' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Source.php',
+ 'Hoa\\Event\\Test\\Unit\\_Listenable' => __DIR__ . '/..' . '/hoa/event/Test/Unit/Listens.php',
+ 'Hoa\\Exception\\Error' => __DIR__ . '/..' . '/hoa/exception/Error.php',
+ 'Hoa\\Exception\\Exception' => __DIR__ . '/..' . '/hoa/exception/Exception.php',
+ 'Hoa\\Exception\\Group' => __DIR__ . '/..' . '/hoa/exception/Group.php',
+ 'Hoa\\Exception\\Idle' => __DIR__ . '/..' . '/hoa/exception/Idle.php',
+ 'Hoa\\Exception\\Test\\Unit\\Error' => __DIR__ . '/..' . '/hoa/exception/Test/Unit/Error.php',
+ 'Hoa\\Exception\\Test\\Unit\\Exception' => __DIR__ . '/..' . '/hoa/exception/Test/Unit/Exception.php',
+ 'Hoa\\Exception\\Test\\Unit\\Group' => __DIR__ . '/..' . '/hoa/exception/Test/Unit/Group.php',
+ 'Hoa\\Exception\\Test\\Unit\\Idle' => __DIR__ . '/..' . '/hoa/exception/Test/Unit/Idle.php',
'Masterminds\\HTML5' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5.php',
'Masterminds\\HTML5\\Elements' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Elements.php',
'Masterminds\\HTML5\\Entities' => __DIR__ . '/..' . '/masterminds/html5/src/HTML5/Entities.php',
"html"
]
},
+ {
+ "name": "hoa/consistency",
+ "version": "1.17.05.02",
+ "version_normalized": "1.17.05.02",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Consistency.git",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/exception": "~1.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "hoa/stream": "~1.0",
+ "hoa/test": "~2.0"
+ },
+ "time": "2017-05-02T12:18:12+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Consistency\\": "."
+ },
+ "files": [
+ "Prelude.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Consistency library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "autoloader",
+ "callable",
+ "consistency",
+ "entity",
+ "flex",
+ "keyword",
+ "library"
+ ]
+ },
+ {
+ "name": "hoa/event",
+ "version": "1.17.01.13",
+ "version_normalized": "1.17.01.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Event.git",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "time": "2017-01-13T15:30:50+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Event\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Event library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "event",
+ "library",
+ "listener",
+ "observer"
+ ]
+ },
+ {
+ "name": "hoa/exception",
+ "version": "1.17.01.16",
+ "version_normalized": "1.17.01.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hoaproject/Exception.git",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f",
+ "reference": "091727d46420a3d7468ef0595651488bfc3a458f",
+ "shasum": ""
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event": "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "time": "2017-01-16T07:53:27+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Exception\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name": "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "description": "The Hoa\\Exception library.",
+ "homepage": "https://hoa-project.net/",
+ "keywords": [
+ "exception",
+ "library"
+ ]
+ },
{
"name": "masterminds/html5",
"version": "2.6.0",
--- /dev/null
+/vendor/
+/composer.lock
--- /dev/null
+language: php
+
+matrix:
+ include:
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: 7.1
+ env:
+ - ENABLE_XDEBUG=true
+ - php: 7.1
+ env:
+ - ENABLE_DEVTOOLS=true
+ - php: nightly
+ - php: hhvm-3.12
+ sudo: required
+ dist: trusty
+ group: edge
+ - php: hhvm
+ sudo: required
+ dist: trusty
+ group: edge
+ allow_failures:
+ - php: nightly
+ - php: hhvm-3.12
+ - php: hhvm
+ fast_finish: true
+
+os:
+ - linux
+
+notifications:
+ irc: "chat.freenode.net#hoaproject"
+
+sudo: false
+
+env:
+ global:
+ - secure: "AAAAB3NzaC1yc2EAAAADAQABAAACAQDIf0Rf76Hhkflz5b9UzWjOjk4UlMU5ySk0VY3B4WdHDLWMMK7fBp1Aj9qXWEDwkuX/NbQP1gB8jQNo7i5uZEOfu7Mn2svPkBBtnmKmaJhk90xypM4lcpcdPi4e8kXUgkriNQLQ2bRe1qZIeF115FkuIvActq7iWKY1TVSZbO54cDKMifDZfH09cf4vpwrZJqwZG6PUnUcCYijgDy99HtfRvzf9xalO4yWm55ZEbJ/VNTHlq1EhK73QLdHC7MO+OQFcd5wEyMbNxBj/bDn/udgb0HsrDijComTg/oTdQJMspYDQYV3ZYvpGozTTCVQrVTYYTP9RCNstgJLHDv9fZZW6yRlw4yNsT7jIQRLs/7awTxOAvRlxqaxk0//ECVNhDgawVtlbEIKrqnM1N7QTm0gjE0HkWEzxE0QbgoZqlLFD6qCp6WVvIT3uGY/i4TkVy78wf3/fzCKbrf72kYSbxIOCxVtptOmrgAblNEpiA/uZ9IofR2p2iwiVY1xF/mzxV2M4zCw6WASrlDhkaL0IncEdRtBuV2WTpixmtjmNkE9h/90kzb5cKExU786gZmvyflYvqlNlcMo3dNsDnROjQCAUXGBw5+risdqTT295BGmlEdZUtcf0c6/zEGhR8B7CktWYLSgOL5mpGMVNEBzyzEwnIiWCvI3pGgoV3Z9UzSJWKQ=="
+
+cache:
+ directories:
+ - vendor/
+
+before_script:
+ - export PATH="$PATH:$HOME/.composer/vendor/bin"
+ - if [[ ! $ENABLE_XDEBUG ]]; then
+ phpenv config-rm xdebug.ini || echo "ext-xdebug is not available, cannot remove it.";
+ fi
+
+script:
+ - composer install
+ - vendor/bin/hoa test:run
+ - if [[ $ENABLE_DEVTOOLS ]]; then
+ composer global require friendsofphp/php-cs-fixer;
+ vendor/bin/hoa devtools:cs --diff --dry-run .;
+ fi
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency;
+
+/**
+ * Class Hoa\Consistency\Autoloader.
+ *
+ * This class is a PSR-4 compliant autoloader.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Autoloader
+{
+ /**
+ * Namespace prefixes to base directories.
+ *
+ * @var array
+ */
+ protected $_namespacePrefixesToBaseDirectories = [];
+
+
+
+ /**
+ * Add a base directory for a namespace prefix.
+ *
+ * @param string $prefix Namespace prefix.
+ * @param string $baseDirectory Base directory for this prefix.
+ * @param bool $prepend Whether the prefix is prepended or
+ * appended to the prefix' stack.
+ * @return void
+ */
+ public function addNamespace($prefix, $baseDirectory, $prepend = false)
+ {
+ $prefix = trim($prefix, '\\') . '\\';
+ $baseDirectory = rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ if (false === isset($this->_namespacePrefixesToBaseDirectories[$prefix])) {
+ $this->_namespacePrefixesToBaseDirectories[$prefix] = [];
+ }
+
+ if (true === $prepend) {
+ array_unshift(
+ $this->_namespacePrefixesToBaseDirectories[$prefix],
+ $baseDirectory
+ );
+ } else {
+ array_push(
+ $this->_namespacePrefixesToBaseDirectories[$prefix],
+ $baseDirectory
+ );
+ }
+
+ return;
+ }
+
+ /**
+ * Try to load the entity file for a given entity name.
+ *
+ * @param string $entity Entity name to load.
+ * @return bool
+ */
+ public function load($entity)
+ {
+ $entityPrefix = $entity;
+ $hasBaseDirectory = false;
+
+ while (false !== $pos = strrpos($entityPrefix, '\\')) {
+ $currentEntityPrefix = substr($entity, 0, $pos + 1);
+ $entityPrefix = rtrim($currentEntityPrefix, '\\');
+ $entitySuffix = substr($entity, $pos + 1);
+ $entitySuffixAsPath = str_replace('\\', '/', $entitySuffix);
+
+ if (false === $this->hasBaseDirectory($currentEntityPrefix)) {
+ continue;
+ }
+
+ $hasBaseDirectory = true;
+
+ foreach ($this->getBaseDirectories($currentEntityPrefix) as $baseDirectory) {
+ $file = $baseDirectory . $entitySuffixAsPath . '.php';
+
+ if (false !== $this->requireFile($file)) {
+ return $file;
+ }
+ }
+ }
+
+ if (true === $hasBaseDirectory &&
+ $entity === Consistency::getEntityShortestName($entity) &&
+ false !== $pos = strrpos($entity, '\\')) {
+ return $this->runAutoloaderStack(
+ $entity . '\\' . substr($entity, $pos + 1)
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * Require a file if exists.
+ *
+ * @param string $filename File name.
+ * @return bool
+ */
+ public function requireFile($filename)
+ {
+ if (false === file_exists($filename)) {
+ return false;
+ }
+
+ require $filename;
+
+ return true;
+ }
+
+ /**
+ * Check whether at least one base directory exists for a namespace prefix.
+ *
+ * @param string $namespacePrefix Namespace prefix.
+ * @return bool
+ */
+ public function hasBaseDirectory($namespacePrefix)
+ {
+ return isset($this->_namespacePrefixesToBaseDirectories[$namespacePrefix]);
+ }
+
+ /**
+ * Get declared base directories for a namespace prefix.
+ *
+ * @param string $namespacePrefix Namespace prefix.
+ * @return array
+ */
+ public function getBaseDirectories($namespacePrefix)
+ {
+ if (false === $this->hasBaseDirectory($namespacePrefix)) {
+ return [];
+ }
+
+ return $this->_namespacePrefixesToBaseDirectories[$namespacePrefix];
+ }
+
+ /**
+ * Get loaded classes.
+ *
+ * @return array
+ */
+ public static function getLoadedClasses()
+ {
+ return get_declared_classes();
+ }
+
+ /**
+ * Run the entire autoloader stack with a specific entity.
+ *
+ * @param string $entity Entity name to load.
+ * @return void
+ */
+ public function runAutoloaderStack($entity)
+ {
+ return spl_autoload_call($entity);
+ }
+
+ /**
+ * Register the autoloader.
+ *
+ * @param bool $prepend Prepend this autoloader to the stack or not.
+ * @return bool
+ */
+ public function register($prepend = false)
+ {
+ return spl_autoload_register([$this, 'load'], true, $prepend);
+ }
+
+ /**
+ * Unregister the autoloader.
+ *
+ * @return bool
+ */
+ public function unregister()
+ {
+ return spl_autoload_unregister([$this, 'load']);
+ }
+
+ /**
+ * Get all registered autoloaders (not only from this library).
+ *
+ * @return array
+ */
+ public function getRegisteredAutoloaders()
+ {
+ return spl_autoload_functions();
+ }
+
+ /**
+ * Dynamic new, a simple factory.
+ * It loads and constructs a class, with provided arguments.
+ *
+ * @param bool $classname Classname.
+ * @param array $arguments Arguments for the constructor.
+ * @return object
+ */
+ public static function dnew($classname, array $arguments = [])
+ {
+ $classname = ltrim($classname, '\\');
+
+ if (false === Consistency::entityExists($classname, false)) {
+ spl_autoload_call($classname);
+ }
+
+ $class = new \ReflectionClass($classname);
+
+ if (empty($arguments) || false === $class->hasMethod('__construct')) {
+ return $class->newInstance();
+ }
+
+ return $class->newInstanceArgs($arguments);
+ }
+}
+
+/**
+ * Autoloader.
+ */
+$autoloader = new Autoloader();
+$autoloader->addNamespace('Hoa', dirname(__DIR__));
+$autoloader->register();
--- /dev/null
+# 1.17.05.02
+
+ * CI: Set up Travis. (Ivan Enderlin, 2017-03-08T09:52:17+01:00)
+ * Prelude: Remove the `(unset)` cast. (Ivan Enderlin, 2017-03-07T16:55:13+01:00)
+
+# 1.17.01.10
+
+ * Quality: Happy new year! (Alexis von Glasow, 2017-01-09T21:38:10+01:00)
+ * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-19T16:27:31+02:00)
+ * Documentation: Fix `docs` and `source` links. (Ivan Enderlin, 2016-10-05T20:26:20+02:00)
+ * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T15:56:01+02:00)
+ * Consistency: `void` is a reserved keyword now. (Ivan Enderlin, 2016-09-02T11:06:18+02:00)
+ * Consistency: Remove `trait_exists` polyfill. (Ivan Enderlin, 2016-08-23T17:26:07+02:00)
+
+# 1.16.03.03
+
+ * Add `STREAM_CRYPTO_METHOD_*` constants on PHP 5.5. (Metalaka, 2016-02-29T21:10:14+01:00)
+ * Composer: Fix `hoa/stream` dependency. (Ivan Enderlin, 2016-03-03T10:13:50+01:00)
+
+# 1.16.01.14
+
+ * Test: Write cases for flex entity in autoloader. (Ivan Enderlin, 2016-01-14T10:45:08+01:00)
+ * Autoloader: Restrict loads to mapped entities. (Ivan Enderlin, 2016-01-14T10:38:21+01:00)
+
+# 1.16.01.11
+
+ * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00)
+ * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T08:58:31+01:00)
+ * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:03:33+01:00)
+
+# 0.16.01.06
+
+ * Prelude: Introduce the prelude/preamble! (Ivan Enderlin, 2015-12-09T08:34:16+01:00)
+ * Consistency: Import last methods from `Hoa\Core`. (Ivan Enderlin, 2015-12-09T08:24:02+01:00)
+ * Quality: Fix CS. (Ivan Enderlin, 2015-12-09T06:43:22+01:00)
+ * Add a `.gitignore` file. (Metalaka, 2015-12-03T13:21:11+01:00)
+ * Autoloader: Propagate unknown entity on the stack. (Ivan Enderlin, 2015-12-03T11:04:57+01:00)
+ * Autoloader: Auto-register to support flex entity. (Ivan Enderlin, 2015-12-03T10:01:23+01:00)
+ * Composer: Force some files to load. (Ivan Enderlin, 2015-12-03T08:15:55+01:00)
+ * Test: Simplify `case_register`. (Ivan Enderlin, 2015-12-03T08:15:19+01:00)
+ * Consistency: Use a strict equality check on trait. (Ivan Enderlin, 2015-12-02T17:11:50+01:00)
+ * README: First draft. (Ivan Enderlin, 2015-12-02T08:40:34+01:00)
+ * Documentation: Update API documentation. (Ivan Enderlin, 2015-12-02T08:37:58+01:00)
+ * Autoloader: Support flex entities. (Ivan Enderlin, 2015-12-02T08:18:39+01:00)
+ * Test: Write test suite of `…nsistency\Autoloader`. (Ivan Enderlin, 2015-12-01T08:42:30+01:00)
+ * Test: Write test suite of `…sistency\Consistency`. (Ivan Enderlin, 2015-11-25T22:10:30+01:00)
+ * Test: Write test suite of `…onsistency\Xcallable`. (Ivan Enderlin, 2015-11-25T08:45:59+01:00)
+ * Test: Write test suite of `…onsistency\Exception`. (Ivan Enderlin, 2015-11-24T16:59:18+01:00)
+ * Split from `Hoa\Core`. (Ivan Enderlin, 2015-11-23T23:08:19+01:00)
+
+(first snapshot)
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency
+{
+
+/**
+ * Class Hoa\Consistency\Consistency.
+ *
+ * This class is a collection of tools to ensure foreward and backward
+ * compatibility.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Consistency
+{
+ /**
+ * Check if an entity exists (class, interface, trait…).
+ *
+ * @param string $entityName Entity name.
+ * @param bool $autoloader Run autoloader if necessary.
+ * @return bool
+ */
+ public static function entityExists($entityName, $autoloader = false)
+ {
+ return
+ class_exists($entityName, $autoloader) ||
+ interface_exists($entityName, false) ||
+ trait_exists($entityName, false);
+ }
+
+ /**
+ * Get the shortest name for an entity.
+ *
+ * @param string $entityName Entity name.
+ * @return string
+ */
+ public static function getEntityShortestName($entityName)
+ {
+ $parts = explode('\\', $entityName);
+ $count = count($parts);
+
+ if (1 >= $count) {
+ return $entityName;
+ }
+
+ if ($parts[$count - 2] === $parts[$count - 1]) {
+ return implode('\\', array_slice($parts, 0, -1));
+ }
+
+ return $entityName;
+ }
+
+ /**
+ * Declare a flex entity (for nested library).
+ *
+ * @param string $entityName Entity name.
+ * @return bool
+ */
+ public static function flexEntity($entityName)
+ {
+ return class_alias(
+ $entityName,
+ static::getEntityShortestName($entityName),
+ false
+ );
+ }
+
+ /**
+ * Whether a word is reserved or not.
+ *
+ * @param string $word Word.
+ * @return bool
+ */
+ public static function isKeyword($word)
+ {
+ static $_list = [
+ // PHP keywords.
+ '__halt_compiler',
+ 'abstract',
+ 'and',
+ 'array',
+ 'as',
+ 'bool',
+ 'break',
+ 'callable',
+ 'case',
+ 'catch',
+ 'class',
+ 'clone',
+ 'const',
+ 'continue',
+ 'declare',
+ 'default',
+ 'die',
+ 'do',
+ 'echo',
+ 'else',
+ 'elseif',
+ 'empty',
+ 'enddeclare',
+ 'endfor',
+ 'endforeach',
+ 'endif',
+ 'endswitch',
+ 'endwhile',
+ 'eval',
+ 'exit',
+ 'extends',
+ 'false',
+ 'final',
+ 'float',
+ 'for',
+ 'foreach',
+ 'function',
+ 'global',
+ 'goto',
+ 'if',
+ 'implements',
+ 'include',
+ 'include_once',
+ 'instanceof',
+ 'insteadof',
+ 'int',
+ 'interface',
+ 'isset',
+ 'list',
+ 'mixed',
+ 'namespace',
+ 'new',
+ 'null',
+ 'numeric',
+ 'object',
+ 'or',
+ 'print',
+ 'private',
+ 'protected',
+ 'public',
+ 'require',
+ 'require_once',
+ 'resource',
+ 'return',
+ 'static',
+ 'string',
+ 'switch',
+ 'throw',
+ 'trait',
+ 'true',
+ 'try',
+ 'unset',
+ 'use',
+ 'var',
+ 'void',
+ 'while',
+ 'xor',
+ 'yield',
+
+ // Compile-time constants.
+ '__class__',
+ '__dir__',
+ '__file__',
+ '__function__',
+ '__line__',
+ '__method__',
+ '__namespace__',
+ '__trait__'
+ ];
+
+ return in_array(strtolower($word), $_list);
+ }
+
+ /**
+ * Whether an ID is a valid PHP identifier.
+ *
+ * @param string $id ID.
+ * @return bool
+ */
+ public static function isIdentifier($id)
+ {
+ return 0 !== preg_match(
+ '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#',
+ $id
+ );
+ }
+
+ /**
+ * Register a register shutdown function.
+ * It may be analogous to a super static destructor.
+ *
+ * @param callable $callable Callable.
+ * @return bool
+ */
+ public static function registerShutdownFunction($callable)
+ {
+ return register_shutdown_function($callable);
+ }
+
+ /**
+ * Get PHP executable.
+ *
+ * @return string
+ */
+ public static function getPHPBinary()
+ {
+ if (defined('PHP_BINARY')) {
+ return PHP_BINARY;
+ }
+
+ if (isset($_SERVER['_'])) {
+ return $_SERVER['_'];
+ }
+
+ foreach (['', '.exe'] as $extension) {
+ if (file_exists($_ = PHP_BINDIR . DS . 'php' . $extension)) {
+ return realpath($_);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Generate an Universal Unique Identifier (UUID).
+ *
+ * @return string
+ */
+ public static function uuid()
+ {
+ return sprintf(
+ '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000,
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff)
+ );
+ }
+}
+
+}
+
+namespace
+{
+
+if (70000 > PHP_VERSION_ID && false === interface_exists('Throwable', false)) {
+ /**
+ * Implement a fake Throwable class, introduced in PHP7.0.
+ */
+ interface Throwable
+ {
+ public function getMessage();
+ public function getCode();
+ public function getFile();
+ public function getLine();
+ public function getTrace();
+ public function getPrevious();
+ public function getTraceAsString();
+ public function __toString();
+ }
+}
+
+/**
+ * Define TLSv* constants, introduced in PHP 5.5.
+ */
+if (50600 > PHP_VERSION_ID) {
+ $define = function ($constantName, $constantValue, $case = false) {
+ if (!defined($constantName)) {
+ return define($constantName, $constantValue, $case);
+ }
+
+ return false;
+ };
+
+ $define('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER', 8);
+ $define('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', 16);
+ $define('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER', 32);
+ $define('STREAM_CRYPTO_METHOD_ANY_SERVER', 62);
+
+ $define('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT', 9);
+ $define('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', 17);
+ $define('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', 33);
+ $define('STREAM_CRYPTO_METHOD_ANY_CLIENT', 63);
+}
+
+if (!function_exists('curry')) {
+ /**
+ * Curry.
+ * Example:
+ * $c = curry('str_replace', …, …, 'foobar');
+ * var_dump($c('foo', 'baz')); // bazbar
+ * $c = curry('str_replace', 'foo', 'baz', …);
+ * var_dump($c('foobarbaz')); // bazbarbaz
+ * Nested curries also work:
+ * $c1 = curry('str_replace', …, …, 'foobar');
+ * $c2 = curry($c1, 'foo', …);
+ * var_dump($c2('baz')); // bazbar
+ * Obviously, as the first argument is a callable, we can combine this with
+ * \Hoa\Consistency\Xcallable ;-).
+ * The “…” character is the HORIZONTAL ELLIPSIS Unicode character (Unicode:
+ * 2026, UTF-8: E2 80 A6).
+ *
+ * @param mixed $callable Callable (two parts).
+ * @param ... ... Arguments.
+ * @return \Closure
+ */
+ function curry($callable)
+ {
+ $arguments = func_get_args();
+ array_shift($arguments);
+ $ii = array_keys($arguments, …, true);
+
+ return function () use ($callable, $arguments, $ii) {
+ return call_user_func_array(
+ $callable,
+ array_replace($arguments, array_combine($ii, func_get_args()))
+ );
+ };
+ }
+}
+
+/**
+ * Flex entity.
+ */
+Hoa\Consistency\Consistency::flexEntity('Hoa\Consistency\Consistency');
+
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency;
+
+use Hoa\Exception as HoaException;
+
+/**
+ * Class \Hoa\Consistency\Exception.
+ *
+ * Extending the \Hoa\Exception\Exception class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends HoaException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+if (false === defined('HOA')) {
+ define('HOA', true);
+}
+
+if (false === defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50400) {
+ throw new Exception(
+ 'Hoa needs at least PHP5.4 to work; you have ' . phpversion() . '.'
+ );
+}
+
+require_once __DIR__ . DIRECTORY_SEPARATOR . 'Autoloader.php';
+require_once __DIR__ . DIRECTORY_SEPARATOR . 'Consistency.php';
+
+$define = function ($constantName, $constantValue, $case = false) {
+ if (!defined($constantName)) {
+ return define($constantName, $constantValue, $case);
+ }
+
+ return false;
+};
+
+$define('SUCCEED', true);
+$define('FAILED', false);
+$define('…', '__hoa_core_fill');
+$define('DS', DIRECTORY_SEPARATOR);
+$define('PS', PATH_SEPARATOR);
+$define('ROOT_SEPARATOR', ';');
+$define('RS', ROOT_SEPARATOR);
+$define('CRLF', "\r\n");
+$define('OS_WIN', defined('PHP_WINDOWS_VERSION_PLATFORM'));
+$define('S_64_BITS', PHP_INT_SIZE == 8);
+$define('S_32_BITS', !S_64_BITS);
+$define('PHP_INT_MIN', ~PHP_INT_MAX);
+$define('PHP_FLOAT_MIN', (float) PHP_INT_MIN);
+$define('PHP_FLOAT_MAX', (float) PHP_INT_MAX);
+$define('π', M_PI);
+$define('nil', null);
+$define('_public', 1);
+$define('_protected', 2);
+$define('_private', 4);
+$define('_static', 8);
+$define('_abstract', 16);
+$define('_pure', 32);
+$define('_final', 64);
+$define('_dynamic', ~_static);
+$define('_concrete', ~_abstract);
+$define('_overridable', ~_final);
+$define('WITH_COMPOSER', class_exists('Composer\Autoload\ClassLoader', false) ||
+ ('cli' === PHP_SAPI &&
+ file_exists(__DIR__ . DS . '..' . DS . '..' . DS . 'autoload.php')));
+
+/**
+ * Alias of \Hoa\Consistency\Xcallable.
+ *
+ * @param mixed $call First callable part.
+ * @param mixed $able Second callable part (if needed).
+ * @return mixed
+ */
+if (!function_exists('xcallable')) {
+ function xcallable($call, $able = '')
+ {
+ if ($call instanceof Hoa\Consistency\Xcallable) {
+ return $call;
+ }
+
+ return new Hoa\Consistency\Xcallable($call, $able);
+ }
+}
--- /dev/null
+<p align="center">
+ <img src="https://static.hoa-project.net/Image/Hoa.svg" alt="Hoa" width="250px" />
+</p>
+
+---
+
+<p align="center">
+ <a href="https://travis-ci.org/hoaproject/Consistency"><img src="https://img.shields.io/travis/hoaproject/Consistency/master.svg" alt="Build status" /></a>
+ <a href="https://coveralls.io/github/hoaproject/Consistency?branch=master"><img src="https://img.shields.io/coveralls/hoaproject/Consistency/master.svg" alt="Code coverage" /></a>
+ <a href="https://packagist.org/packages/hoa/consistency"><img src="https://img.shields.io/packagist/dt/hoa/consistency.svg" alt="Packagist" /></a>
+ <a href="https://hoa-project.net/LICENSE"><img src="https://img.shields.io/packagist/l/hoa/consistency.svg" alt="License" /></a>
+</p>
+<p align="center">
+ Hoa is a <strong>modular</strong>, <strong>extensible</strong> and
+ <strong>structured</strong> set of PHP libraries.<br />
+ Moreover, Hoa aims at being a bridge between industrial and research worlds.
+</p>
+
+# Hoa\Consistency
+
+[![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject)
+[![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central)
+[![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Consistency)
+[![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/consistency)
+
+This library provides a thin layer between PHP VMs and libraries to ensure
+consistency accross VM versions and library versions.
+
+[Learn more](https://central.hoa-project.net/Documentation/Library/Consistency).
+
+## Installation
+
+With [Composer](https://getcomposer.org/), to include this library into
+your dependencies, you need to
+require [`hoa/consistency`](https://packagist.org/packages/hoa/consistency):
+
+```sh
+$ composer require hoa/consistency '~1.0'
+```
+
+For more installation procedures, please read [the Source
+page](https://hoa-project.net/Source.html).
+
+## Testing
+
+Before running the test suites, the development dependencies must be installed:
+
+```sh
+$ composer install
+```
+
+Then, to run all the test suites:
+
+```sh
+$ vendor/bin/hoa test:run
+```
+
+For more information, please read the [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html).
+
+## Quick usage
+
+We propose a quick overview of how the consistency API ensures foreward and
+backward compatibility, also an overview of the [PSR-4
+autoloader](http://www.php-fig.org/psr/psr-4/) and the xcallable API.
+
+### Foreward and backward compatibility
+
+The `Hoa\Consistency\Consistency` class ensures foreward and backward
+compatibility.
+
+#### Example with keywords
+
+The `Hoa\Consistency\Consistency::isKeyword` checks whether a specific word is
+reserved by PHP or not. Let's say your current PHP version does not support the
+`callable` keyword or type declarations such as `int`, `float`, `string` etc.,
+the `isKeyword` method will tell you if they are reserved keywords: Not only
+for your current PHP version, but maybe in an incoming version.
+
+```php
+$isKeyword = Hoa\Consistency\Consistency::isKeyword('yield');
+```
+
+It avoids to write algorithms that might break in the future or for your users
+living on the edge.
+
+#### Example with identifiers
+
+PHP identifiers are defined by a regular expression. It might change in the
+future. To prevent breaking your algorithms, you can use the
+`Hoa\Consistency\Consistency::isIdentifier` method to check an identifier is
+correct regarding current PHP version:
+
+```php
+$isValidIdentifier = Hoa\Consistency\Consistency::isIdentifier('foo');
+```
+
+#### Flexible entities
+
+Flexible entities are very simple. If we declare `Foo\Bar\Bar` as a flexible
+entity, we will be able to access it with the `Foo\Bar\Bar` name or `Foo\Bar`.
+This is very useful if your architecture evolves but you want to keep the
+backward compatibility. For instance, it often happens that you create a
+`Foo\Bar\Exception` class in the `Foo/Bar/Exception.php` file. But after few
+versions, you realise other exceptions need to be introduced, so you need an
+`Exception` directory. In this case, `Foo\Bar\Exception` should move as
+`Foo\Bar\Exception\Exception`. If this latter is declared as a flexible entity,
+backward compatibility will be kept.
+
+```php
+Hoa\Consistency\Consistency::flexEntity('Foo\Bar\Exception\Exception');
+```
+
+Another example is the “entry-class” (informal naming).
+`Hoa\Consistency\Consistency` is a good example. This is more convenient to
+write `Hoa\Consistency` instead of `Hoa\Consistency\Consistency`. This is
+possible because this is a flexible entity.
+
+#### Throwable & co.
+
+The `Throwable` interface has been introduced to represent a whole new exception
+architecture in PHP. Thus, to be compatible with incoming PHP versions, you
+might want to use this interface in some cases. Hopefully, the `Throwable`
+interface will be created for you if it does not exists.
+
+```php
+try {
+ …
+} catch (Throwable $e) {
+ …
+}
+```
+
+### Autoloader
+
+`Hoa\Consistency\Autoloader` is a [PSR-4
+compatible](http://www.php-fig.org/psr/psr-4/) autoloader. It simply works as
+follows:
+ * `addNamespace` is used to map a namespace prefix to a directory,
+ * `register` is used to register the autoloader.
+
+The API also provides the `load` method to force the load of an entity,
+`unregister` to unregister the autoloader, `getRegisteredAutoloaders` to get
+a list of all registered autoloaders etc.
+
+For instance, to map the `Foo\Bar` namespace to the `Source/` directory:
+
+```php
+$autoloader = new Hoa\Consistency\Autoloader();
+$autoloader->addNamespace('Foo\Bar', 'Source');
+$autoloader->register();
+
+$baz = new Foo\Bar\Baz(); // automatically loaded!
+```
+
+### Xcallable
+
+Xcallables are “extended callables”. It is a unified API to invoke callables of
+any kinds, and also extends some Hoa's API (like
+[`Hoa\Event`](https://central.hoa-project.net/Resource/Library/Event)
+or
+[`Hoa\Stream`](https://central.hoa-project.net/Resource/Library/Stream)). It
+understands the following kinds:
+ * `'function'` as a string,
+ * `'class::method'` as a string,
+ * `'class', 'method'` as 2 string arguments,
+ * `$object, 'method'` as 2 arguments,
+ * `$object, ''` as 2 arguments, the “able” is unknown,
+ * `function (…) { … }` as a closure,
+ * `['class', 'method']` as an array of strings,
+ * `[$object, 'method']` as an array.
+
+To use it, simply instanciate the `Hoa\Consistency\Xcallable` class and use it
+as a function:
+
+```php
+$xcallable = new Hoa\Consistency\Xcallable('strtoupper');
+var_dump($xcallable('foo'));
+
+/**
+ * Will output:
+ * string(3) "FOO"
+ */
+```
+
+The `Hoa\Consistency\Xcallable::distributeArguments` method invokes the callable
+but the arguments are passed as an array:
+
+```php
+$xcallable->distributeArguments(['foo']);
+```
+
+This is also possible to get a unique hash of the callable:
+
+```php
+var_dump($xcallable->getHash());
+
+/**
+ * Will output:
+ * string(19) "function#strtoupper"
+ */
+```
+
+Finally, this is possible to get a reflection instance of the current callable
+(can be of kind [`ReflectionFunction`](http://php.net/ReflectionFunction),
+[`ReflectionClass`](http://php.net/ReflectionClass),
+[`ReflectionMethod`](http://php.net/ReflectionMethod) or
+[`ReflectionObject`](http://php.net/ReflectionObject)):
+
+```php
+var_dump($xcallable->getReflection());
+
+/**
+ * Will output:
+ * object(ReflectionFunction)#42 (1) {
+ * ["name"]=>
+ * string(10) "strtoupper"
+ * }
+ */
+```
+
+When the object is set but not the method, the latter will be deduced if
+possible. If the object is of kind
+[`Hoa\Stream`](http://central.hoa-project.net/Resource/Library/Stream), then
+according to the type of the arguments given to the callable, the
+`writeInteger`, `writeString`, `writeArray` etc. method will be used. If the
+argument is of kind `Hoa\Event\Bucket`, then the method name will be deduced
+based on the data contained inside the event bucket. This is very handy. For
+instance, the following example will work seamlessly:
+
+```php
+Hoa\Event\Event::getEvent('hoa://Event/Exception')
+ ->attach(new Hoa\File\Write('Exceptions.log'));
+```
+
+The `attach` method on `Hoa\Event\Event` transforms its argument as an
+xcallable. In this particular case, the method to call is unknown, we only have
+an object (of kind `Hoa\File\Write`). However, because this is a stream, the
+method will be deduced according to the data contained in the event bucket fired
+on the `hoa://Event/Exception` event channel.
+
+## Documentation
+
+The
+[hack book of `Hoa\Consistency`](https://central.hoa-project.net/Documentation/Library/Consistency)
+contains detailed information about how to use this library and how it works.
+
+To generate the documentation locally, execute the following commands:
+
+```sh
+$ composer require --dev hoa/devtools
+$ vendor/bin/hoa devtools:documentation --open
+```
+
+More documentation can be found on the project's website:
+[hoa-project.net](https://hoa-project.net/).
+
+## Getting help
+
+There are mainly two ways to get help:
+
+ * On the [`#hoaproject`](https://webchat.freenode.net/?channels=#hoaproject)
+ IRC channel,
+ * On the forum at [users.hoa-project.net](https://users.hoa-project.net).
+
+## Contribution
+
+Do you want to contribute? Thanks! A detailed [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html) explains
+everything you need to know.
+
+## License
+
+Hoa is under the New BSD License (BSD-3-Clause). Please, see
+[`LICENSE`](https://hoa-project.net/LICENSE) for details.
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency\Test\Unit;
+
+use Hoa\Consistency\Autoloader as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Consistency\Test\Unit\Autoloader.
+ *
+ * Test suite of the autoloader.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Autoloader extends Test\Unit\Suite
+{
+ public function case_add_namespace_prepend()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $prefix = 'Foo\Bar\\',
+ $baseDirectoryA = 'Source/Foo/Bar/',
+ $baseDirectoryB = 'Source/Foo/Bar/'
+ )
+ ->when(
+ $autoloader->addNamespace($prefix, $baseDirectoryA),
+ $result = $autoloader->addNamespace($prefix, $baseDirectoryB)
+ )
+ ->then
+ ->boolean($autoloader->hasBaseDirectory($prefix))
+ ->isTrue()
+ ->array($autoloader->getBaseDirectories($prefix))
+ ->isEqualTo([
+ $baseDirectoryB,
+ $baseDirectoryA
+ ]);
+ }
+
+ public function case_add_namespace_append()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $prefix = 'Foo\Bar\\',
+ $baseDirectoryA = 'Source/Foo/Bar/',
+ $baseDirectoryB = 'Source/Foo/Bar/'
+ )
+ ->when(
+ $autoloader->addNamespace($prefix, $baseDirectoryA),
+ $result = $autoloader->addNamespace($prefix, $baseDirectoryB)
+ )
+ ->then
+ ->boolean($autoloader->hasBaseDirectory($prefix))
+ ->isTrue()
+ ->array($autoloader->getBaseDirectories($prefix))
+ ->isEqualTo([
+ $baseDirectoryA,
+ $baseDirectoryB
+ ]);
+ }
+
+ public function case_add_namespace_with_invalid_prefix()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $prefix = '\\\\Foo\Bar',
+ $baseDirectory = 'Source/Foo/Bar/'
+ )
+ ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
+ ->then
+ ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
+ ->isTrue()
+ ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
+ ->isEqualTo([$baseDirectory]);
+ }
+
+ public function case_add_namespace_with_invalid_base_directory()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $prefix = 'Foo\Bar\\',
+ $baseDirectory = 'Source/Foo/Bar'
+ )
+ ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
+ ->then
+ ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
+ ->isTrue()
+ ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
+ ->isEqualTo(['Source/Foo/Bar/']);
+ }
+
+ public function case_add_namespace_with_crazy_invalid_base_directory()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $prefix = 'Foo\Bar\\',
+ $baseDirectory = 'Source/Foo/Bar/////'
+ )
+ ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
+ ->then
+ ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
+ ->isTrue()
+ ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
+ ->isEqualTo(['Source/Foo/Bar/']);
+ }
+
+ public function case_load()
+ {
+ $this
+ ->given(
+ $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
+ $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/Bar/'),
+ $this->calling($autoloader)->requireFile = function ($file) {
+ return $file;
+ }
+ )
+ ->when($result = $autoloader->load('Foo\Bar\Baz\Qux'))
+ ->then
+ ->string($result)
+ ->isEqualTo('Source/Foo/Bar/Baz/Qux.php');
+ }
+
+ public function case_load_invalid_entity()
+ {
+ $this
+ ->given($autoloader = new SUT())
+ ->when($result = $autoloader->load('Foo'))
+ ->then
+ ->variable($result)
+ ->isNull();
+ }
+
+ public function case_load_flex_entity()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
+ $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/'),
+ $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) {
+ $called = true;
+ $self
+ ->string($entity)
+ ->isEqualTo('Foo\Bar\Baz\Baz');
+
+ return;
+ },
+ $autoloader->register()
+ )
+ ->when($result = $autoloader->load('Foo\Bar\Baz'))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_load_unmapped_flex_entity()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
+ $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) {
+ $called = true;
+
+ return;
+ },
+ $autoloader->register()
+ )
+ ->when($result = $autoloader->load('Foo\Bar\Baz'))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->variable($called)
+ ->isNull();
+ }
+
+ public function case_require_existing_file()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+
+ $this->function->file_exists = true,
+
+ $constantName = 'HOA_TEST_' . uniqid(),
+ $filename = 'hoa://Test/Vfs/Foo?type=file',
+
+ file_put_contents($filename, '<?php define("' . $constantName . '", "BAR");')
+ )
+ ->when($result = $autoloader->requireFile($filename))
+ ->then
+ ->boolean($result)
+ ->isTrue()
+ ->string(constant($constantName))
+ ->isEqualTo('BAR');
+ }
+
+ public function case_require_not_existing_file()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $this->function->file_exists = false
+ )
+ ->when($result = $autoloader->requireFile('/hoa/flatland'))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_has_not_base_directory()
+ {
+ $this
+ ->given($autoloader = new SUT())
+ ->when($result = $autoloader->hasBaseDirectory('foo'))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_get_base_undeclared_namespace_prefix()
+ {
+ $this
+ ->given($autoloader = new SUT())
+ ->when($result = $autoloader->getBaseDirectories('foo'))
+ ->then
+ ->array($result)
+ ->isEmpty();
+ }
+
+ public function case_dnew()
+ {
+ $this
+ ->given($classname = 'Hoa\Consistency\Autoloader')
+ ->when($result = SUT::dnew($classname))
+ ->then
+ ->object($result)
+ ->isInstanceOf($classname);
+ }
+
+ public function case_dnew_unknown_class()
+ {
+ $this
+ ->given($this->function->spl_autoload_call = null)
+ ->exception(function () {
+ SUT::dnew('Foo');
+ })
+ ->isInstanceOf('ReflectionException');
+ }
+
+ public function case_get_loaded_classes()
+ {
+ $this
+ ->given(
+ $declaredClasses = get_declared_classes(),
+ $this->function->get_declared_classes = $declaredClasses
+ )
+ ->when($result = SUT::getLoadedClasses())
+ ->then
+ ->array($result)
+ ->isEqualTo($declaredClasses);
+ }
+
+ public function case_register()
+ {
+ $self = $this;
+
+ $this
+ ->given($autoloader = new SUT())
+ ->when($result = $autoloader->register())
+ ->then
+ ->boolean($result)
+ ->isTrue()
+ ->array($autoloader->getRegisteredAutoloaders())
+ ->isEqualTo(spl_autoload_functions());
+ }
+
+ public function case_unregister()
+ {
+ $this
+ ->given(
+ $autoloader = new SUT(),
+ $oldRegisteredAutoloaders = $autoloader->getRegisteredAutoloaders()
+ )
+ ->when($result = $autoloader->register())
+ ->then
+ ->boolean($result)
+ ->isTrue()
+ ->integer(count($autoloader->getRegisteredAutoloaders()))
+ ->isEqualTo(count($oldRegisteredAutoloaders) + 1)
+
+ ->when($result = $autoloader->unregister())
+ ->then
+ ->boolean($result)
+ ->isTrue()
+ ->array($autoloader->getRegisteredAutoloaders())
+ ->isEqualTo($oldRegisteredAutoloaders);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency\Test\Unit;
+
+use Hoa\Consistency\Consistency as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Consistency\Test\Unit\Consistency.
+ *
+ * Test suite of the consistency class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Consistency extends Test\Unit\Suite
+{
+ protected function _entity_exists_with_xxx($class, $interface, $trait)
+ {
+ $this
+ ->given(
+ $this->function->class_exists = $class,
+ $this->function->interface_exists = $interface,
+ $this->function->trait_exists = $trait
+ )
+ ->when($result = SUT::entityExists('foo'))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_entity_exists_with_class()
+ {
+ return $this->_entity_exists_with_xxx(true, false, false);
+ }
+
+ public function case_entity_exists_with_interface()
+ {
+ return $this->_entity_exists_with_xxx(false, true, false);
+ }
+
+ public function case_entity_exists_with_trait()
+ {
+ return $this->_entity_exists_with_xxx(false, false, true);
+ }
+
+ public function case_entity_does_not_exists()
+ {
+ $this
+ ->given(
+ $this->function->class_exists = false,
+ $this->function->interface_exists = false,
+ $this->function->trait_exists = false
+ )
+ ->when($result = SUT::entityExists('foo'))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_get_entity_shortest_name()
+ {
+ $this
+ ->when($result = SUT::getEntityShortestName('Foo\Bar\Bar'))
+ ->then
+ ->string($result)
+ ->isEqualTo('Foo\Bar');
+ }
+
+ public function case_get_entity_shortest_name_with_already_the_shortest()
+ {
+ $this
+ ->when($result = SUT::getEntityShortestName('Foo\Bar'))
+ ->then
+ ->string($result)
+ ->isEqualTo('Foo\Bar');
+ }
+
+ public function case_get_entity_shortest_name_with_no_namespace()
+ {
+ $this
+ ->when($result = SUT::getEntityShortestName('Foo'))
+ ->then
+ ->string($result)
+ ->isEqualTo('Foo');
+ }
+
+ public function case_is_keyword()
+ {
+ $this
+ ->given(
+ $keywords = [
+ '__HALT_COMPILER',
+ 'abstract',
+ 'and',
+ 'array',
+ 'as',
+ 'bool',
+ 'break',
+ 'callable',
+ 'case',
+ 'catch',
+ 'class',
+ 'clone',
+ 'const',
+ 'continue',
+ 'declare',
+ 'default',
+ 'die',
+ 'do',
+ 'echo',
+ 'else',
+ 'elseif',
+ 'empty',
+ 'enddeclare',
+ 'endfor',
+ 'endforeach',
+ 'endif',
+ 'endswitch',
+ 'endwhile',
+ 'eval',
+ 'exit',
+ 'extends',
+ 'false',
+ 'final',
+ 'float',
+ 'for',
+ 'foreach',
+ 'function',
+ 'global',
+ 'goto',
+ 'if',
+ 'implements',
+ 'include',
+ 'include_once',
+ 'instanceof',
+ 'insteadof',
+ 'int',
+ 'interface',
+ 'isset',
+ 'list',
+ 'mixed',
+ 'namespace',
+ 'new',
+ 'null',
+ 'numeric',
+ 'object',
+ 'or',
+ 'print',
+ 'private',
+ 'protected',
+ 'public',
+ 'require',
+ 'require_once',
+ 'resource',
+ 'return',
+ 'static',
+ 'string',
+ 'switch',
+ 'throw',
+ 'trait',
+ 'true',
+ 'try',
+ 'unset',
+ 'use',
+ 'var',
+ 'void',
+ 'while',
+ 'xor',
+ 'yield',
+ '__CLASS__',
+ '__DIR__',
+ '__FILE__',
+ '__FUNCTION__',
+ '__LINE__',
+ '__METHOD__',
+ '__NAMESPACE__',
+ '__TRAIT__'
+ ]
+ )
+ ->when(function () use ($keywords) {
+ foreach ($keywords as $keyword) {
+ $this
+ ->boolean(SUT::isKeyword($keyword))
+ ->isTrue();
+ }
+ });
+ }
+
+ public function case_is_identifier()
+ {
+ $this
+ ->given($_identifier = $this->realdom->regex('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#'))
+ ->when(function () use ($_identifier) {
+ foreach ($this->sampleMany($_identifier, 1000) as $identifier) {
+ $this
+ ->boolean(SUT::isIdentifier($identifier))
+ ->isTrue();
+ }
+ });
+ }
+
+ public function case_register_shutdown_function()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $callable = function () {
+ },
+ $this->function->register_shutdown_function = function ($_callable) use (&$called, $self, &$callable) {
+ $called = true;
+
+ $self
+ ->variable($_callable)
+ ->isEqualTo($callable);
+
+ return true;
+ }
+ )
+ ->when($result = SUT::registerShutdownFunction($callable))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_get_php_binary_with_constant()
+ {
+ $this
+ ->given($this->constant->PHP_BINARY = '/foo/php')
+ ->when($result = SUT::getPHPBinary())
+ ->then
+ ->string($result)
+ ->isEqualTo('/foo/php');
+ }
+
+ public function case_get_php_binary_with_server()
+ {
+ $this
+ ->given(
+ $this->function->defined = false,
+ $_SERVER['_'] = '/bar/php'
+ )
+ ->when($result = SUT::getPHPBinary())
+ ->then
+ ->string($result)
+ ->isEqualTo('/bar/php');
+ }
+
+ public function case_get_php_binary_with_bin_directory()
+ {
+ unset($_SERVER['_']);
+
+ $this
+ ->given(
+ $this->function->defined = false,
+ $this->function->file_exists = true,
+ $this->function->realpath = '/baz/php'
+ )
+ ->when($result = SUT::getPHPBinary())
+ ->then
+ ->string($result)
+ ->isEqualTo('/baz/php');
+ }
+
+ public function case_uuid()
+ {
+ $this
+ ->given($this->function->mt_rand = 42)
+ ->when($result = SUT::uuid())
+ ->then
+ ->string($result)
+ ->isEqualTo('002a002a-002a-402a-802a-002a002a002a');
+ }
+
+ public function case_uuid_all_differents()
+ {
+ $this
+ ->when(function () {
+ $uuids = [];
+
+ for ($i = 0; $i < 10000; ++$i) {
+ $uuids[] = SUT::uuid();
+ }
+
+ $this
+ ->integer(count($uuids))
+ ->isEqualTo(count(array_unique($uuids)));
+ });
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency\Test\Unit;
+
+use Hoa\Consistency\Exception as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Consistency\Test\Unit\Exception.
+ *
+ * Test suite of the exception.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends Test\Unit\Suite
+{
+ public function case_hoa_exception()
+ {
+ $this
+ ->when($result = new SUT('foo', 0))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Exception\Exception');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency\Test\Unit;
+
+use Hoa\Consistency\Xcallable as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Consistency\Test\Unit\Xcallable.
+ *
+ * Test suite of the xcallable class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Xcallable extends Test\Unit\Suite
+{
+ public function case_form_function()
+ {
+ $this
+ ->when($result = new SUT('strtoupper'))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->string($result->getValidCallback())
+ ->isEqualTo('strtoupper')
+ ->string($result->getHash())
+ ->isEqualTo('function#strtoupper')
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionFunction')
+ ->string($reflection->getName())
+ ->isEqualTo('strtoupper');
+ }
+
+ public function case_form_class___method()
+ {
+ $this
+ ->when($result = new SUT(__CLASS__ . '::strtoupper'))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->array($result->getValidCallback())
+ ->isEqualTo([__CLASS__, 'strtoupper'])
+ ->string($result->getHash())
+ ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('strtoupper');
+ }
+
+ public function case_form_class_method()
+ {
+ $this
+ ->when($result = new SUT(__CLASS__, 'strtoupper'))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->array($result->getValidCallback())
+ ->isEqualTo([__CLASS__, 'strtoupper'])
+ ->string($result->getHash())
+ ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('strtoupper');
+ }
+
+ public function case_form_object_method()
+ {
+ $this
+ ->when($result = new SUT($this, 'strtolower'))
+ ->then
+ ->string($result('FOO'))
+ ->isEqualTo('foo')
+ ->array($result->getValidCallback())
+ ->isEqualTo([$this, 'strtolower'])
+ ->string($result->getHash())
+ ->matches(
+ '/^object\([^:]+\)#' .
+ preg_quote(__CLASS__) .
+ '::strtolower$/'
+ )
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('strtolower');
+ }
+
+ public function case_form_object_invoke()
+ {
+ $this
+ ->when($result = new SUT($this))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->array($result->getValidCallback())
+ ->isEqualTo([$this, '__invoke'])
+ ->string($result->getHash())
+ ->matches(
+ '/^object\([^:]+\)#' .
+ preg_quote(__CLASS__) .
+ '::__invoke$/'
+ )
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('__invoke');
+ }
+
+ public function case_form_closure()
+ {
+ $this
+ ->given(
+ $closure = function ($string) {
+ return strtoupper($string);
+ }
+ )
+ ->when($result = new SUT($closure))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->object($result->getValidCallback())
+ ->isIdenticalTo($closure)
+ ->string($result->getHash())
+ ->matches('/^closure\([^:]+\)$/')
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionFunction')
+ ->string($reflection->getName())
+ ->isEqualTo('Hoa\Consistency\Test\Unit\{closure}');
+ }
+
+ public function case_form_array_of_class_method()
+ {
+ $this
+ ->when($result = new SUT([__CLASS__, 'strtoupper']))
+ ->then
+ ->string($result('foo'))
+ ->isEqualTo('FOO')
+ ->array($result->getValidCallback())
+ ->isEqualTo([__CLASS__, 'strtoupper'])
+ ->string($result->getHash())
+ ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('strtoupper');
+ }
+
+ public function case_form_array_of_object_method()
+ {
+ $this
+ ->when($result = new SUT([$this, 'strtolower']))
+ ->then
+ ->string($result('FOO'))
+ ->isEqualTo('foo')
+ ->array($result->getValidCallback())
+ ->isEqualTo([$this, 'strtolower'])
+ ->string($result->getHash())
+ ->matches(
+ '/^object\([^:]+\)#' .
+ preg_quote(__CLASS__) .
+ '::strtolower$/'
+ )
+ ->isEqualTo($result . '')
+ ->object($reflection = $result->getReflection())
+ ->isInstanceOf('ReflectionMethod')
+ ->string($reflection->getName())
+ ->isEqualTo('strtolower');
+ }
+
+ public function case_form_able_not_a_string()
+ {
+ $this
+ ->exception(function () {
+ new SUT(__CLASS__, 123);
+ })
+ ->isInstanceOf('Hoa\Consistency\Exception');
+ }
+
+ public function case_form_function_not_defined()
+ {
+ $this
+ ->exception(function () {
+ new SUT('__hoa_test_undefined_function__');
+ })
+ ->isInstanceOf('Hoa\Consistency\Exception');
+ }
+
+ public function case_form_able_cannot_be_deduced()
+ {
+ $this
+ ->given($this->function->method_exists = false)
+ ->exception(function () {
+ new SUT($this);
+ })
+ ->isInstanceOf('Hoa\Consistency\Exception');
+ }
+
+ public function case_invoke()
+ {
+ $this
+ ->given(
+ $callable = new SUT(
+ function ($x, $y, $z) {
+ return [$x, $y, $z];
+ }
+ )
+ )
+ ->when($result = $callable(7, [4.2], 'foo'))
+ ->then
+ ->array($result)
+ ->isEqualTo([7, [4.2], 'foo']);
+ }
+
+ public function case_distribute_arguments()
+ {
+ $this
+ ->given(
+ $callable = new SUT(
+ function ($x, $y, $z) {
+ return [$x, $y, $z];
+ }
+ )
+ )
+ ->when($result = $callable->distributeArguments([7, [4.2], 'foo']))
+ ->then
+ ->array($result)
+ ->isEqualTo([7, [4.2], 'foo']);
+ }
+
+ protected function _get_valid_callback_stream_xxx($argument, $method)
+ {
+ $this
+ ->given(
+ $stream = new \Mock\Hoa\Stream\IStream\Out(),
+ $arguments = [$argument],
+ $xcallable = new SUT($stream)
+ )
+ ->when($result = $xcallable->getValidCallback($arguments))
+ ->then
+ ->array($result)
+ ->isEqualTo([$stream, $method]);
+ }
+
+ public function case_get_valid_callback_stream_character()
+ {
+ return $this->_get_valid_callback_stream_xxx('f', 'writeCharacter');
+ }
+
+ public function case_get_valid_callback_stream_string()
+ {
+ return $this->_get_valid_callback_stream_xxx('foo', 'writeString');
+ }
+
+ public function case_get_valid_callback_stream_boolean()
+ {
+ return $this->_get_valid_callback_stream_xxx(true, 'writeBoolean');
+ }
+
+ public function case_get_valid_callback_stream_integer()
+ {
+ return $this->_get_valid_callback_stream_xxx(7, 'writeInteger');
+ }
+
+ public function case_get_valid_callback_stream_array()
+ {
+ return $this->_get_valid_callback_stream_xxx([4, 2], 'writeArray');
+ }
+
+ public function case_get_valid_callback_stream_float()
+ {
+ return $this->_get_valid_callback_stream_xxx(4.2, 'writeFloat');
+ }
+
+ public function case_get_valid_callback_stream_other()
+ {
+ return $this->_get_valid_callback_stream_xxx($this, 'writeAll');
+ }
+
+ public static function strtoupper($string)
+ {
+ return strtoupper($string);
+ }
+
+ public function strtolower($string)
+ {
+ return strtolower($string);
+ }
+
+ public function __invoke($string)
+ {
+ return strtoupper($string);
+ }
+
+ public function __toString()
+ {
+ return 'hello';
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Consistency;
+
+use Hoa\Event;
+use Hoa\Stream;
+
+/**
+ * Class Hoa\Consistency\Xcallable.
+ *
+ * Build a callable object, i.e. function, class::method, object->method or
+ * closure, they all have the same behaviour. This callable is an extension of
+ * native PHP callable (aka callback) to integrate Hoa's structures.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Xcallable
+{
+ /**
+ * Callback, with the PHP format.
+ *
+ * @var mixed
+ */
+ protected $_callback = null;
+
+ /**
+ * Callable hash.
+ *
+ * @var string
+ */
+ protected $_hash = null;
+
+
+
+ /**
+ * Build a callback.
+ * Accepted forms:
+ * * `'function'`,
+ * * `'class::method'`,
+ * * `'class', 'method'`,
+ * * `$object, 'method'`,
+ * * `$object, ''`,
+ * * `function (…) { … }`,
+ * * `['class', 'method']`,
+ * * `[$object, 'method']`.
+ *
+ * @param mixed $call First callable part.
+ * @param mixed $able Second callable part (if needed).
+ */
+ public function __construct($call, $able = '')
+ {
+ if ($call instanceof \Closure) {
+ $this->_callback = $call;
+
+ return;
+ }
+
+ if (!is_string($able)) {
+ throw new Exception(
+ 'Bad callback form; the able part must be a string.',
+ 0
+ );
+ }
+
+ if ('' === $able) {
+ if (is_string($call)) {
+ if (false === strpos($call, '::')) {
+ if (!function_exists($call)) {
+ throw new Exception(
+ 'Bad callback form; function %s does not exist.',
+ 1,
+ $call
+ );
+ }
+
+ $this->_callback = $call;
+
+ return;
+ }
+
+ list($call, $able) = explode('::', $call);
+ } elseif (is_object($call)) {
+ if ($call instanceof Stream\IStream\Out) {
+ $able = null;
+ } elseif (method_exists($call, '__invoke')) {
+ $able = '__invoke';
+ } else {
+ throw new Exception(
+ 'Bad callback form; an object but without a known ' .
+ 'method.',
+ 2
+ );
+ }
+ } elseif (is_array($call) && isset($call[0])) {
+ if (!isset($call[1])) {
+ return $this->__construct($call[0]);
+ }
+
+ return $this->__construct($call[0], $call[1]);
+ } else {
+ throw new Exception(
+ 'Bad callback form.',
+ 3
+ );
+ }
+ }
+
+ $this->_callback = [$call, $able];
+
+ return;
+ }
+
+ /**
+ * Call the callable.
+ *
+ * @param ...
+ * @return mixed
+ */
+ public function __invoke()
+ {
+ $arguments = func_get_args();
+ $valid = $this->getValidCallback($arguments);
+
+ return call_user_func_array($valid, $arguments);
+ }
+
+ /**
+ * Distribute arguments according to an array.
+ *
+ * @param array $arguments Arguments.
+ * @return mixed
+ */
+ public function distributeArguments(array $arguments)
+ {
+ return call_user_func_array([$this, '__invoke'], $arguments);
+ }
+
+ /**
+ * Get a valid callback in the PHP meaning.
+ *
+ * @param array &$arguments Arguments (could determine method on an
+ * object if not precised).
+ * @return mixed
+ */
+ public function getValidCallback(array &$arguments = [])
+ {
+ $callback = $this->_callback;
+ $head = null;
+
+ if (isset($arguments[0])) {
+ $head = &$arguments[0];
+ }
+
+ // If method is undetermined, we find it (we understand event bucket and
+ // stream).
+ if (null !== $head &&
+ is_array($callback) &&
+ null === $callback[1]) {
+ if ($head instanceof Event\Bucket) {
+ $head = $head->getData();
+ }
+
+ switch ($type = gettype($head)) {
+ case 'string':
+ if (1 === strlen($head)) {
+ $method = 'writeCharacter';
+ } else {
+ $method = 'writeString';
+ }
+
+ break;
+
+ case 'boolean':
+ case 'integer':
+ case 'array':
+ $method = 'write' . ucfirst($type);
+
+ break;
+
+ case 'double':
+ $method = 'writeFloat';
+
+ break;
+
+ default:
+ $method = 'writeAll';
+ $head = $head . "\n";
+ }
+
+ $callback[1] = $method;
+ }
+
+ return $callback;
+ }
+
+ /**
+ * Get hash.
+ * Will produce:
+ * * function#…;
+ * * class#…::…;
+ * * object(…)#…::…;
+ * * closure(…).
+ *
+ * @return string
+ */
+ public function getHash()
+ {
+ if (null !== $this->_hash) {
+ return $this->_hash;
+ }
+
+ $_ = &$this->_callback;
+
+ if (is_string($_)) {
+ return $this->_hash = 'function#' . $_;
+ }
+
+ if (is_array($_)) {
+ return
+ $this->_hash =
+ (is_object($_[0])
+ ? 'object(' . spl_object_hash($_[0]) . ')' .
+ '#' . get_class($_[0])
+ : 'class#' . $_[0]) .
+ '::' .
+ (null !== $_[1]
+ ? $_[1]
+ : '???');
+ }
+
+ return $this->_hash = 'closure(' . spl_object_hash($_) . ')';
+ }
+
+ /**
+ * Get appropriated reflection instance.
+ *
+ * @param ...
+ * @return \Reflector
+ */
+ public function getReflection()
+ {
+ $arguments = func_get_args();
+ $valid = $this->getValidCallback($arguments);
+
+ if (is_string($valid)) {
+ return new \ReflectionFunction($valid);
+ }
+
+ if ($valid instanceof \Closure) {
+ return new \ReflectionFunction($valid);
+ }
+
+ if (is_array($valid)) {
+ if (is_string($valid[0])) {
+ if (false === method_exists($valid[0], $valid[1])) {
+ return new \ReflectionClass($valid[0]);
+ }
+
+ return new \ReflectionMethod($valid[0], $valid[1]);
+ }
+
+ $object = new \ReflectionObject($valid[0]);
+
+ if (null === $valid[1]) {
+ return $object;
+ }
+
+ return $object->getMethod($valid[1]);
+ }
+ }
+
+ /**
+ * Return the hash.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getHash();
+ }
+}
--- /dev/null
+{
+ "name" : "hoa/consistency",
+ "description": "The Hoa\\Consistency library.",
+ "type" : "library",
+ "keywords" : ["library", "consistency", "autoloader", "entity", "flex",
+ "keyword", "callable"],
+ "homepage" : "https://hoa-project.net/",
+ "license" : "BSD-3-Clause",
+ "authors" : [
+ {
+ "name" : "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name" : "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "support": {
+ "email" : "support@hoa-project.net",
+ "irc" : "irc://chat.freenode.net/hoaproject",
+ "forum" : "https://users.hoa-project.net/",
+ "docs" : "https://central.hoa-project.net/Documentation/Library/Consistency",
+ "source": "https://central.hoa-project.net/Resource/Library/Consistency"
+ },
+ "require": {
+ "php" : ">=5.5.0",
+ "hoa/exception": "~1.0"
+ },
+ "require-dev": {
+ "hoa/stream": "~1.0",
+ "hoa/test" : "~2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Consistency\\": "."
+ },
+ "files": ["Prelude.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ }
+}
--- /dev/null
+/vendor/
+/composer.lock
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+/**
+ * Class \Hoa\Event\Bucket.
+ *
+ * This class is the object which is transmit through event channels.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Bucket
+{
+ /**
+ * Source object.
+ *
+ * @var \Hoa\Event\Source
+ */
+ protected $_source = null;
+
+ /**
+ * Data.
+ *
+ * @var mixed
+ */
+ protected $_data = null;
+
+
+
+ /**
+ * Set data.
+ *
+ * @param mixed $data Data.
+ */
+ public function __construct($data = null)
+ {
+ $this->setData($data);
+
+ return;
+ }
+
+ /**
+ * Send this object on the event channel.
+ *
+ * @param string $eventId Event ID.
+ * @param \Hoa\Event\Source $source Source.
+ * @return void
+ */
+ public function send($eventId, Source $source)
+ {
+ return Event::notify($eventId, $source, $this);
+ }
+
+ /**
+ * Set source.
+ *
+ * @param \Hoa\Event\Source $source Source.
+ * @return \Hoa\Event\Source
+ */
+ public function setSource(Source $source)
+ {
+ $old = $this->_source;
+ $this->_source = $source;
+
+ return $old;
+ }
+
+ /**
+ * Get source.
+ *
+ * @return \Hoa\Event\Source
+ */
+ public function getSource()
+ {
+ return $this->_source;
+ }
+
+ /**
+ * Set data.
+ *
+ * @param mixed $data Data.
+ * @return mixed
+ */
+ public function setData($data)
+ {
+ $old = $this->_data;
+ $this->_data = $data;
+
+ return $old;
+ }
+
+ /**
+ * Get data.
+ *
+ * @return mixed
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+}
--- /dev/null
+# 1.17.01.13
+
+ * Quality: Happy new year! (Alexis von Glasow, 2017-01-11T23:09:35+01:00)
+
+# 1.16.11.19
+
+ * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-18T16:34:43+02:00)
+ * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T20:36:30+02:00)
+
+# 1.16.03.15
+
+ * Fix a typo (Metalaka, 2016-01-28T09:05:40+01:00)
+ * CHANGELOG: Add a missing newline. (Ivan Enderlin, 2016-01-11T09:37:32+01:00)
+
+# 1.16.01.11
+
+ * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00)
+ * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T09:01:10+01:00)
+ * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:14:54+01:00)
+ * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T11:10:00+01:00)
+ * Documentation: Fix typos. (Ivan Enderlin, 2015-11-23T22:05:56+01:00)
+ * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:40:34+01:00)
+
+# 0.15.11.23
+
+ * Documentation: Fix typos. (Ivan Enderlin, 2015-11-23T22:05:56+01:00)
+ * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:40:34+01:00)
+ * Test: Fix a conflict between 2 test suites. (Ivan Enderlin, 2015-11-23T21:29:28+01:00)
+ * Test: Write test suite for `Hoa\Event\Listens`. (Ivan Enderlin, 2015-11-23T21:25:49+01:00)
+ * Listener: Add the `Listens` trait. (Ivan Enderlin, 2015-11-20T06:45:11+01:00)
+ * Documentation: Fix a typo. (Ivan Enderlin, 2015-11-23T13:01:56+01:00)
+ * Improve readability by adding constants to registered events container. (Metalaka, 2015-11-20T19:53:32+01:00)
+ * Add a `.gitignore` file. (Metalaka, 2015-11-20T19:37:52+01:00)
+ * README: Complete description and add usage. (Ivan Enderlin, 2015-11-13T08:43:08+01:00)
+ * Test: Write test suite for `Hoa\Event\Event`. (Ivan Enderlin, 2015-11-11T17:04:41+01:00)
+ * Test: Write test suite for `Hoa\Event\Exception`. (Ivan Enderlin, 2015-11-11T16:46:12+01:00)
+ * Composer: Add a keyword. (Ivan Enderlin, 2015-11-11T13:38:35+01:00)
+ * Test: Write test suite for `Hoa\Event\Listener`. (Ivan Enderlin, 2015-11-11T13:27:40+01:00)
+ * Test: Write test suite for `Hoa\Event\Source`. (Ivan Enderlin, 2015-11-10T21:56:37+01:00)
+ * Test: Write test suite for `Hoa\Event\Listenable`. (Ivan Enderlin, 2015-11-10T21:56:24+01:00)
+ * Test: Write test suite for `Hoa\Event\Bucket`. (Ivan Enderlin, 2015-11-10T21:55:45+01:00)
+ * Split from `Hoa\Core`. (Ivan Enderlin, 2015-11-10T21:28:31+01:00)
+
+(first snapshot)
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+use Hoa\Consistency;
+
+/**
+ * Class \Hoa\Event\Event.
+ *
+ * Events are asynchronous at registration, anonymous at use (until we
+ * receive a bucket) and useful to largely spread data through components
+ * without any known connection between them.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Event
+{
+ /**
+ * Event ID key.
+ *
+ * @const int
+ */
+ const KEY_EVENT = 0;
+
+ /**
+ * Source object key.
+ *
+ * @const int
+ */
+ const KEY_SOURCE = 1;
+
+ /**
+ * Static register of all observable objects, i.e. \Hoa\Event\Source
+ * object, i.e. object that can send event.
+ *
+ * @var array
+ */
+ private static $_register = [];
+
+ /**
+ * Callables, i.e. observer objects.
+ *
+ * @var array
+ */
+ protected $_callable = [];
+
+
+
+ /**
+ * Privatize the constructor.
+ *
+ */
+ private function __construct()
+ {
+ return;
+ }
+
+ /**
+ * Manage multiton of events, with the principle of asynchronous
+ * attachments.
+ *
+ * @param string $eventId Event ID.
+ * @return \Hoa\Event\Event
+ */
+ public static function getEvent($eventId)
+ {
+ if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
+ self::$_register[$eventId] = [
+ self::KEY_EVENT => new self(),
+ self::KEY_SOURCE => null
+ ];
+ }
+
+ return self::$_register[$eventId][self::KEY_EVENT];
+ }
+
+ /**
+ * Declare a new object in the observable collection.
+ * Note: Hoa's libraries use hoa://Event/AnID for their observable objects;
+ *
+ * @param string $eventId Event ID.
+ * @param \Hoa\Event\Source|string $source Observable object or class.
+ * @return void
+ * @throws \Hoa\Event\Exception
+ */
+ public static function register($eventId, $source)
+ {
+ if (true === self::eventExists($eventId)) {
+ throw new Exception(
+ 'Cannot redeclare an event with the same ID, i.e. the event ' .
+ 'ID %s already exists.',
+ 0,
+ $eventId
+ );
+ }
+
+ if (is_object($source) && !($source instanceof Source)) {
+ throw new Exception(
+ 'The source must implement \Hoa\Event\Source ' .
+ 'interface; given %s.',
+ 1,
+ get_class($source)
+ );
+ } else {
+ $reflection = new \ReflectionClass($source);
+
+ if (false === $reflection->implementsInterface('\Hoa\Event\Source')) {
+ throw new Exception(
+ 'The source must implement \Hoa\Event\Source ' .
+ 'interface; given %s.',
+ 2,
+ $source
+ );
+ }
+ }
+
+ if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
+ self::$_register[$eventId][self::KEY_EVENT] = new self();
+ }
+
+ self::$_register[$eventId][self::KEY_SOURCE] = $source;
+
+ return;
+ }
+
+ /**
+ * Undeclare an object in the observable collection.
+ *
+ * @param string $eventId Event ID.
+ * @param bool $hard If false, just delete the source, else,
+ * delete source and attached callables.
+ * @return void
+ */
+ public static function unregister($eventId, $hard = false)
+ {
+ if (false !== $hard) {
+ unset(self::$_register[$eventId]);
+ } else {
+ self::$_register[$eventId][self::KEY_SOURCE] = null;
+ }
+
+ return;
+ }
+
+ /**
+ * Attach an object to an event.
+ * It can be a callable or an accepted callable form (please, see the
+ * \Hoa\Consistency\Xcallable class).
+ *
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Event
+ */
+ public function attach($callable)
+ {
+ $callable = xcallable($callable);
+ $this->_callable[$callable->getHash()] = $callable;
+
+ return $this;
+ }
+
+ /**
+ * Detach an object to an event.
+ * Please see $this->attach() method.
+ *
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Event
+ */
+ public function detach($callable)
+ {
+ unset($this->_callable[xcallable($callable)->getHash()]);
+
+ return $this;
+ }
+
+ /**
+ * Check if at least one callable is attached to an event.
+ *
+ * @return bool
+ */
+ public function isListened()
+ {
+ return !empty($this->_callable);
+ }
+
+ /**
+ * Notify, i.e. send data to observers.
+ *
+ * @param string $eventId Event ID.
+ * @param \Hoa\Event\Source $source Source.
+ * @param \Hoa\Event\Bucket $data Data.
+ * @return void
+ * @throws \Hoa\Event\Exception
+ */
+ public static function notify($eventId, Source $source, Bucket $data)
+ {
+ if (false === self::eventExists($eventId)) {
+ throw new Exception(
+ 'Event ID %s does not exist, cannot send notification.',
+ 3,
+ $eventId
+ );
+ }
+
+ $data->setSource($source);
+ $event = self::getEvent($eventId);
+
+ foreach ($event->_callable as $callable) {
+ $callable($data);
+ }
+
+ return;
+ }
+
+ /**
+ * Check whether an event exists.
+ *
+ * @param string $eventId Event ID.
+ * @return bool
+ */
+ public static function eventExists($eventId)
+ {
+ return
+ array_key_exists($eventId, self::$_register) &&
+ self::$_register[$eventId][self::KEY_SOURCE] !== null;
+ }
+}
+
+/**
+ * Flex entity.
+ */
+Consistency::flexEntity('Hoa\Event\Event');
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+use Hoa\Exception as HoaException;
+
+/**
+ * Class \Hoa\Event\Exception.
+ *
+ * Extending the \Hoa\Exception\Exception class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends HoaException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+/**
+ * Interface \Hoa\Event\Listenable.
+ *
+ * Each object which is listenable must implement this interface.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+interface Listenable extends Source
+{
+ /**
+ * Attach a callable to a listenable component.
+ *
+ * @param string $listenerId Listener ID.
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Listenable
+ */
+ public function on($listenerId, $callable);
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+/**
+ * Class \Hoa\Event\Listener.
+ *
+ * A contrario of events, listeners are synchronous, identified at use and
+ * useful for close interactions between one or some components.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Listener
+{
+ /**
+ * Source of listener (for Bucket).
+ *
+ * @var \Hoa\Event\Listenable
+ */
+ protected $_source = null;
+
+ /**
+ * All listener IDs and associated listeners.
+ *
+ * @var array
+ */
+ protected $_callables = [];
+
+
+
+ /**
+ * Build a listener.
+ *
+ * @param \Hoa\Event\Listenable $source Source (for Bucket).
+ * @param array $ids Accepted ID.
+ */
+ public function __construct(Listenable $source, array $ids)
+ {
+ $this->_source = $source;
+ $this->addIds($ids);
+
+ return;
+ }
+
+ /**
+ * Add acceptable ID (or reset).
+ *
+ * @param array $ids Accepted ID.
+ * @return void
+ */
+ public function addIds(array $ids)
+ {
+ foreach ($ids as $id) {
+ $this->_callables[$id] = [];
+ }
+
+ return;
+ }
+
+ /**
+ * Attach a callable to a listenable component.
+ *
+ * @param string $listenerId Listener ID.
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Listener
+ * @throws \Hoa\Event\Exception
+ */
+ public function attach($listenerId, $callable)
+ {
+ if (false === $this->listenerExists($listenerId)) {
+ throw new Exception(
+ 'Cannot listen %s because it is not defined.',
+ 0,
+ $listenerId
+ );
+ }
+
+ $callable = xcallable($callable);
+ $this->_callables[$listenerId][$callable->getHash()] = $callable;
+
+ return $this;
+ }
+
+ /**
+ * Detach a callable from a listenable component.
+ *
+ * @param string $listenerId Listener ID.
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Listener
+ */
+ public function detach($listenerId, $callable)
+ {
+ unset($this->_callables[$listenerId][xcallable($callable)->getHash()]);
+
+ return $this;
+ }
+
+ /**
+ * Detach all callables from a listenable component.
+ *
+ * @param string $listenerId Listener ID.
+ * @return \Hoa\Event\Listener
+ */
+ public function detachAll($listenerId)
+ {
+ unset($this->_callables[$listenerId]);
+
+ return $this;
+ }
+
+ /**
+ * Check if a listener exists.
+ *
+ * @param string $listenerId Listener ID.
+ * @return bool
+ */
+ public function listenerExists($listenerId)
+ {
+ return array_key_exists($listenerId, $this->_callables);
+ }
+
+ /**
+ * Send/fire a bucket to a listener.
+ *
+ * @param string $listenerId Listener ID.
+ * @param \Hoa\Event\Bucket $data Data.
+ * @return array
+ * @throws \Hoa\Event\Exception
+ */
+ public function fire($listenerId, Bucket $data)
+ {
+ if (false === $this->listenerExists($listenerId)) {
+ throw new Exception(
+ 'Cannot fire on %s because it is not defined.',
+ 1,
+ $listenerId
+ );
+ }
+
+ $data->setSource($this->_source);
+ $out = [];
+
+ foreach ($this->_callables[$listenerId] as $callable) {
+ $out[] = $callable($data);
+ }
+
+ return $out;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+/**
+ * Trait \Hoa\Event\Listens.
+ *
+ * Implementation of a listener.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+trait Listens
+{
+ /**
+ * Listener instance.
+ *
+ * @var \Hoa\Event\Listener
+ */
+ protected $_listener = null;
+
+
+
+ /**
+ * Attach a callable to a listenable component.
+ *
+ * @param string $listenerId Listener ID.
+ * @param mixed $callable Callable.
+ * @return \Hoa\Event\Listenable
+ */
+ public function on($listenerId, $callable)
+ {
+ $listener = $this->getListener();
+
+ if (null === $listener) {
+ throw new Exception(
+ 'Cannot attach a callable to the listener %s because ' .
+ 'it has not been initialized yet.',
+ 0,
+ get_class($this)
+ );
+ }
+
+ $listener->attach($listenerId, $callable);
+
+ return $this;
+ }
+
+ /**
+ * Set listener.
+ *
+ * @param \Hoa\Event\Listener $listener Listener.
+ * @return \Hoa\Event\Listener
+ */
+ protected function setListener(Listener $listener)
+ {
+ $old = $this->_listener;
+ $this->_listener = $listener;
+
+ return $old;
+ }
+
+ /**
+ * Get listener.
+ *
+ * @return \Hoa\Event\Listener
+ */
+ protected function getListener()
+ {
+ return $this->_listener;
+ }
+}
--- /dev/null
+<p align="center">
+ <img src="https://static.hoa-project.net/Image/Hoa.svg" alt="Hoa" width="250px" />
+</p>
+
+---
+
+<p align="center">
+ <a href="https://travis-ci.org/hoaproject/event"><img src="https://img.shields.io/travis/hoaproject/event/master.svg" alt="Build status" /></a>
+ <a href="https://coveralls.io/github/hoaproject/event?branch=master"><img src="https://img.shields.io/coveralls/hoaproject/event/master.svg" alt="Code coverage" /></a>
+ <a href="https://packagist.org/packages/hoa/event"><img src="https://img.shields.io/packagist/dt/hoa/event.svg" alt="Packagist" /></a>
+ <a href="https://hoa-project.net/LICENSE"><img src="https://img.shields.io/packagist/l/hoa/event.svg" alt="License" /></a>
+</p>
+<p align="center">
+ Hoa is a <strong>modular</strong>, <strong>extensible</strong> and
+ <strong>structured</strong> set of PHP libraries.<br />
+ Moreover, Hoa aims at being a bridge between industrial and research worlds.
+</p>
+
+# Hoa\Event
+
+[![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject)
+[![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central)
+[![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Event)
+[![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/event)
+
+This library allows to use events and listeners in PHP. This is an observer
+design-pattern implementation.
+
+[Learn more](https://central.hoa-project.net/Documentation/Library/Event).
+
+## Installation
+
+With [Composer](https://getcomposer.org/), to include this library into
+your dependencies, you need to
+require [`hoa/event`](https://packagist.org/packages/hoa/event):
+
+```sh
+$ composer require hoa/event '~1.0'
+```
+
+For more installation procedures, please read [the Source
+page](https://hoa-project.net/Source.html).
+
+## Testing
+
+Before running the test suites, the development dependencies must be installed:
+
+```sh
+$ composer install
+```
+
+Then, to run all the test suites:
+
+```sh
+$ vendor/bin/hoa test:run
+```
+
+For more information, please read the [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html).
+
+## Quick usage
+
+We propose a quick overview of how to use events and listeners.
+
+### Events
+
+An event is:
+ * **Asynchronous** when registering, because the observable may not exist yet
+ while observers start to observe,
+ * **Anonymous** when using, because the observable has no idea how many and
+ what observers are observing,
+ * It aims at a **large** diffusion of data through isolated components.
+ Wherever is the observable, we can observe its data.
+
+In Hoa, an event channel has the following form:
+`hoa://Event/LibraryName/AnId:pseudo-class#anAnchor`. For instance, the
+`hoa://Event/Exception` channel contains all exceptions that have been thrown.
+The `hoa://Event/Stream/StreamName:close-before` contains all streams that are
+about to close. Thus, the following example will observe all thrown exceptions:
+
+```php
+Hoa\Event\Event::getEvent('hoa://Event/Exception')->attach(
+ function (Hoa\Event\Bucket $bucket) {
+ var_dump(
+ $bucket->getSource(),
+ $bucket->getData()
+ );
+ }
+);
+```
+
+Because `attach` expects a callable and because Hoa's callable implementation is
+smart, we can directly attach a stream to an event, like:
+
+```php
+Hoa\Event\Event::getEvent('hoa://Event/Exception')->attach(
+ new Hoa\File\Write('Foo.log')
+);
+```
+
+This way, all exceptions will be printed on the `Foo.log` file.
+
+### Listeners
+
+Contrary to an event, a listener is:
+ * **Synchronous** when registering, because the observable must exist before
+ observers can observe,
+ * **Identified** when using, because the observable knows how many observers
+ are observing,
+ * It aims at a **close** diffusion of data. The observers must have an access
+ to the observable to observe.
+
+The `Hoa\Event\Listenable` interface requires the `on` method to be present to
+register a listener to a listener ID. For instance, the following example
+listens the `message` listener ID, i.e. when a message is received by the
+WebSocket server, the closure is executed:
+
+```php
+$server = new Hoa\Websocket\Server(…);
+$server->on('message', function (Hoa\Event\Bucket $bucket) {
+ var_dump(
+ $bucket->getSource(),
+ $bucket->getData()
+ );
+});
+```
+
+## Documentation
+
+The
+[hack book of `Hoa\Event`](https://central.hoa-project.net/Documentation/Library/Event) contains
+detailed information about how to use this library and how it works.
+
+To generate the documentation locally, execute the following commands:
+
+```sh
+$ composer require --dev hoa/devtools
+$ vendor/bin/hoa devtools:documentation --open
+```
+
+More documentation can be found on the project's website:
+[hoa-project.net](https://hoa-project.net/).
+
+## Getting help
+
+There are mainly two ways to get help:
+
+ * On the [`#hoaproject`](https://webchat.freenode.net/?channels=#hoaproject)
+ IRC channel,
+ * On the forum at [users.hoa-project.net](https://users.hoa-project.net).
+
+## Contribution
+
+Do you want to contribute? Thanks! A detailed [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html) explains
+everything you need to know.
+
+## License
+
+Hoa is under the New BSD License (BSD-3-Clause). Please, see
+[`LICENSE`](https://hoa-project.net/LICENSE) for details.
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event;
+
+/**
+ * Interface \Hoa\Event\Source.
+ *
+ * Each object which is listenable must implement this interface.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+interface Source
+{
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Event as LUT;
+use Hoa\Event\Bucket as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Bucket.
+ *
+ * Test suite of the bucket.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Bucket extends Test\Unit\Suite
+{
+ public function case_constructor()
+ {
+ $this
+ ->when($result = new SUT('foo'))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Event\Bucket')
+ ->string($result->getData())
+ ->isEqualTo('foo');
+ }
+
+ public function case_send()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ LUT::register($eventId, $source),
+
+ $bucket = new SUT('foo'),
+
+ LUT::getEvent($eventId)->attach(
+ function (SUT $receivedBucket) use ($self, $bucket, &$called) {
+ $called = true;
+
+ $self
+ ->object($receivedBucket)
+ ->isIdenticalTo($bucket);
+ }
+ )
+ )
+ ->when($result = $bucket->send($eventId, $source))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_set_source()
+ {
+ $this
+ ->given(
+ $bucket = new SUT(),
+ $sourceA = new \Mock\Hoa\Event\Source()
+ )
+ ->when($result = $bucket->setSource($sourceA))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->object($bucket->getSource())
+ ->isIdenticalTo($sourceA)
+
+ ->given($sourceB = new \Mock\Hoa\Event\Source())
+ ->when($result = $bucket->setSource($sourceB))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($sourceA)
+ ->object($bucket->getSource())
+ ->isIdenticalTo($sourceB);
+ }
+
+ public function case_set_data()
+ {
+ $this
+ ->given(
+ $bucket = new SUT(),
+ $datumA = 'foo'
+ )
+ ->when($result = $bucket->setData($datumA))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->string($bucket->getData())
+ ->isEqualTo($datumA)
+
+ ->given($datumB = 'bar')
+ ->when($result = $bucket->setData($datumB))
+ ->then
+ ->string($result)
+ ->isEqualTo($datumA)
+ ->string($bucket->getData())
+ ->isEqualTo($datumB);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Event as LUT;
+use Hoa\Event\Event as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Event.
+ *
+ * Test suite of the event class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Event extends Test\Unit\Suite
+{
+ public function case_multiton()
+ {
+ $this
+ ->given($eventId = 'hoa://Event/Test')
+ ->when($result = SUT::getEvent($eventId))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Event\Event')
+ ->object(SUT::getEvent($eventId))
+ ->isIdenticalTo($result);
+ }
+
+ public function case_register_source_instance()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source()
+ )
+ ->when($result = SUT::register($eventId, $source))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean(SUT::eventExists($eventId))
+ ->isTrue();
+ }
+
+ public function case_register_source_name()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = 'Mock\Hoa\Event\Source'
+ )
+ ->when($result = SUT::register($eventId, $source))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean(SUT::eventExists($eventId))
+ ->isTrue();
+ }
+
+ public function case_register_redeclare()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ SUT::register($eventId, $source)
+ )
+ ->exception(function () use ($eventId, $source) {
+ SUT::register($eventId, $source);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+
+ public function case_register_not_a_source_instance()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \StdClass()
+ )
+ ->exception(function () use ($eventId, $source) {
+ $result = SUT::register($eventId, $source);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+
+ public function case_register_not_a_source_name()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = 'StdClass'
+ )
+ ->exception(function () use ($eventId, $source) {
+ $result = SUT::register($eventId, $source);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+
+ public function case_unregister()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ SUT::register($eventId, $source)
+ )
+ ->when($result = SUT::unregister($eventId))
+ ->then
+ ->boolean(SUT::eventExists($eventId))
+ ->isFalse();
+ }
+
+ public function case_unregister_hard()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ SUT::register($eventId, $source),
+ $event = SUT::getEvent($eventId)
+ )
+ ->when($result = SUT::unregister($eventId, true))
+ ->then
+ ->boolean(SUT::eventExists($eventId))
+ ->isFalse()
+ ->object(SUT::getEvent($eventId))
+ ->isNotIdenticalTo($event);
+ }
+
+ public function case_unregister_not_registered()
+ {
+ $this
+ ->given($eventId = 'hoa://Event/Test')
+ ->when($result = SUT::unregister($eventId))
+ ->then
+ ->variable($result)
+ ->isNull();
+ }
+
+ public function case_attach()
+ {
+ $this
+ ->given(
+ $event = SUT::getEvent('hoa://Event/Test'),
+ $callable = function () { }
+ )
+ ->when($result = $event->attach($callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($event)
+ ->boolean($event->isListened())
+ ->isTrue();
+ }
+
+ public function case_detach()
+ {
+ $this
+ ->given(
+ $event = SUT::getEvent('hoa://Event/Test'),
+ $callable = function () { },
+ $event->attach($callable)
+ )
+ ->when($result = $event->detach($callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($event)
+ ->boolean($event->isListened())
+ ->isFalse();
+ }
+
+ public function case_detach_unattached()
+ {
+ $this
+ ->given(
+ $event = SUT::getEvent('hoa://Event/Test'),
+ $callable = function () { }
+ )
+ ->when($result = $event->detach($callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($event)
+ ->boolean($event->isListened())
+ ->isFalse();
+ }
+
+ public function case_is_listened()
+ {
+ $this
+ ->given($event = SUT::getEvent('hoa://Event/Test'))
+ ->when($result = $event->isListened())
+ ->then
+ ->boolean($event->isListened())
+ ->isFalse();
+ }
+
+ public function case_notify()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ $bucket = new LUT\Bucket(),
+
+ SUT::register($eventId, $source),
+ SUT::getEvent($eventId)->attach(
+ function (LUT\Bucket $receivedBucket) use ($self, $source, $bucket, &$called) {
+ $called = true;
+
+ $this
+ ->object($receivedBucket)
+ ->isIdenticalTo($bucket)
+ ->object($receivedBucket->getSource())
+ ->isIdenticalTo($source);
+ }
+ )
+ )
+ ->when($result = SUT::notify($eventId, $source, $bucket))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_notify_unregistered_event_id()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ $data = new LUT\Bucket()
+ )
+ ->exception(function () use ($eventId, $source, $data) {
+ SUT::notify($eventId, $source, $data);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+
+ public function case_event_exists()
+ {
+ $this
+ ->given(
+ $eventId = 'hoa://Event/Test',
+ $source = new \Mock\Hoa\Event\Source(),
+ SUT::register($eventId, $source)
+ )
+ ->when($result = SUT::eventExists($eventId))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_event_not_exists()
+ {
+ $this
+ ->given($eventId = 'hoa://Event/Test')
+ ->when($result = SUT::eventExists($eventId))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Event\Exception as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Exception.
+ *
+ * Test suite of the exception.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends Test\Unit\Suite
+{
+ public function case_hoa_exception()
+ {
+ $this
+ ->when($result = new SUT('foo', 0))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Exception\Exception');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Listenable.
+ *
+ * Test suite of the listenable interface.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Listenable extends Test\Unit\Suite
+{
+ public function case_interface()
+ {
+ $this
+ ->when($result = new \Mock\Hoa\Event\Listenable())
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Event\Listenable');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Event as LUT;
+use Hoa\Event\Listener as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Listener.
+ *
+ * Test suite of the listener.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Listener extends Test\Unit\Suite
+{
+ public function case_constructor()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $ids = ['foo', 'bar', 'baz']
+ )
+ ->when($result = new SUT($source, $ids))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Event\Listener')
+ ->boolean($result->listenerExists('foo'))
+ ->isTrue()
+ ->boolean($result->listenerExists('bar'))
+ ->isTrue()
+ ->boolean($result->listenerExists('baz'))
+ ->isTrue();
+ }
+
+ public function case_attach()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'foo',
+ $listener = new SUT($source, ['foo', 'bar']),
+ $callable = function () {
+ return 42;
+ }
+ )
+ ->when($result = $listener->attach($listenerId, $callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener)
+ ->array($listener->fire($listenerId, new LUT\Bucket()))
+ ->isEqualTo([42]);
+ }
+
+ public function case_attach_to_an_undefined_listener()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'bar',
+ $listener = new SUT($source, ['foo', 'baz']),
+ $callable = function () { }
+ )
+ ->exception(function () use ($listener, $listenerId, $callable) {
+ $listener->attach($listenerId, $callable);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+
+ public function case_detach()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'foo',
+ $listener = new SUT($source, ['foo', 'bar']),
+ $callable = function () {
+ return 42;
+ },
+ $listener->attach($listenerId, $callable)
+ )
+ ->when($result = $listener->detach($listenerId, $callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener)
+ ->array($listener->fire($listenerId, new LUT\Bucket()))
+ ->isEmpty();
+ }
+
+ public function case_detach_an_undefined_listener()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'bar',
+ $listener = new SUT($source, ['foo', 'baz']),
+ $callable = function () { }
+ )
+ ->when($result = $listener->detach($listenerId, $callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener);
+ }
+
+ public function case_detach_all()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'foo',
+ $listener = new SUT($source, ['foo', 'bar'])
+ )
+ ->when($result = $listener->detachAll($listenerId))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener)
+ ->boolean($listener->listenerExists($listenerId))
+ ->isFalse();
+ }
+
+ public function case_detach_all_with_an_undefined_listener()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $listenerId = 'bar',
+ $listener = new SUT($source, ['foo', 'baz'])
+ )
+ ->when($result = $listener->detachAll($listenerId))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener);
+ }
+
+ public function case_listener_exists()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $ids = [],
+ $listener = new SUT($source, $ids)
+ )
+ ->when($listener->addIds(['foo']))
+ ->then
+ ->boolean($listener->listenerExists('foo'))
+ ->isTrue()
+ ->boolean($listener->listenerExists('bar'))
+ ->isFalse()
+
+ ->when($listener->addIds(['bar']))
+ ->then
+ ->boolean($listener->listenerExists('bar'))
+ ->isTrue();
+ }
+
+ public function case_fire()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $ids = ['foo', 'bar'],
+ $listener = new SUT($source, $ids),
+
+ $listenerId = 'foo',
+ $bucket = new LUT\Bucket(),
+ $listener->attach(
+ $listenerId,
+ function (LUT\Bucket $receivedBucket) use ($self, $bucket, $source, &$called) {
+ $called = true;
+
+ $self
+ ->object($receivedBucket)
+ ->isIdenticalTo($bucket)
+ ->object($receivedBucket->getSource())
+ ->isIdenticalTo($source);
+
+ return 42;
+ }
+ )
+ )
+ ->when($result = $listener->fire($listenerId, $bucket))
+ ->then
+ ->array($result)
+ ->isEqualTo([42])
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_fire_an_undefined_listenerId()
+ {
+ $this
+ ->given(
+ $source = new \Mock\Hoa\Event\Listenable(),
+ $ids = [],
+ $listener = new SUT($source, $ids)
+ )
+ ->exception(function () use ($listener) {
+ $listener->fire('foo', new LUT\Bucket());
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Event as LUT;
+use Hoa\Event\Listens as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Listens.
+ *
+ * Test suite of the listens trait.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Listens extends Test\Unit\Suite
+{
+ public function case_set_listener()
+ {
+ $this
+ ->given(
+ $listenable = new _Listenable(),
+ $listener = new LUT\Listener($listenable, ['foo'])
+ )
+ ->when($result = $listenable->_setListener($listener))
+ ->then
+ ->variable($result)
+ ->isNull();
+ }
+
+ public function case_get_listener()
+ {
+ $this
+ ->given(
+ $listenable = new _Listenable(),
+ $listener = new LUT\Listener($listenable, ['foo']),
+ $listenable->_setListener($listener)
+ )
+ ->when($result = $listenable->_getListener())
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listener);
+ }
+
+ public function case_on()
+ {
+ $this
+ ->given(
+ $listenable = new _Listenable(),
+ $listener = new LUT\Listener($listenable, ['foo']),
+ $listenable->_setListener($listener),
+ $callable = function () use (&$called) {
+ $called = true;
+
+ return 42;
+ }
+ )
+ ->when($result = $listenable->on('foo', $callable))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($listenable)
+
+ ->when($listenable->doSomethingThatFires())
+ ->then
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_on_unregistered_listener()
+ {
+ $this
+ ->given(
+ $listenable = new _Listenable(),
+ $listener = new LUT\Listener($listenable, ['foo']),
+ $listenable->_setListener($listener)
+ )
+ ->exception(function () use ($listenable) {
+ $listenable->on('bar', null);
+ })
+ ->isInstanceOf('Hoa\Event\Exception');
+ }
+}
+
+class _Listenable implements LUT\Listenable
+{
+ use SUT;
+
+ public function _setListener(LUT\Listener $listener)
+ {
+ return $this->setListener($listener);
+ }
+
+ public function _getListener()
+ {
+ return $this->getListener();
+ }
+
+ public function doSomethingThatFires()
+ {
+ $this->getListener()->fire('foo', new LUT\Bucket('bar'));
+
+ return;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Event\Test\Unit;
+
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Event\Test\Unit\Source.
+ *
+ * Test suite of the source interface.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Source extends Test\Unit\Suite
+{
+ public function case_interface()
+ {
+ $this
+ ->when($result = new \Mock\Hoa\Event\Source())
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Event\Source');
+ }
+}
--- /dev/null
+{
+ "name" : "hoa/event",
+ "description": "The Hoa\\Event library.",
+ "type" : "library",
+ "keywords" : ["library", "event", "listener", "observer"],
+ "homepage" : "https://hoa-project.net/",
+ "license" : "BSD-3-Clause",
+ "authors" : [
+ {
+ "name" : "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name" : "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "support": {
+ "email" : "support@hoa-project.net",
+ "irc" : "irc://chat.freenode.net/hoaproject",
+ "forum" : "https://users.hoa-project.net/",
+ "docs" : "https://central.hoa-project.net/Documentation/Library/Event",
+ "source": "https://central.hoa-project.net/Resource/Library/Event"
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/exception" : "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Event\\": "."
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ }
+}
--- /dev/null
+/vendor/
+/composer.lock
--- /dev/null
+# 1.17.01.16
+
+ * Quality: Happy new year! (Ivan Enderlin, 2017-01-16T08:52:04+01:00)
+
+# 1.16.11.08
+
+ * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-19T16:31:55+02:00)
+ * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T20:39:14+02:00)
+
+# 1.16.01.11
+
+ * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00)
+ * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T09:01:35+01:00)
+ * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:16:03+01:00)
+ * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T11:11:27+01:00)
+
+# 0.15.11.23
+
+ * Fix phpDoc and a mistake on a static call. (Metalaka, 2015-11-21T17:44:35+01:00)
+ * Add a `.gitignore` file. (Metalaka, 2015-11-21T17:39:21+01:00)
+ * Test: Add test cases for the uncaught handler. (Ivan Enderlin, 2015-11-18T21:48:20+01:00)
+ * Idle: Add uncaught handler. (Ivan Enderlin, 2015-11-18T21:47:40+01:00)
+ * README: Add a better description and new usages. (Ivan Enderlin, 2015-11-18T21:36:03+01:00)
+ * Test: Write test suite of `Hoa\Exception\Group`. (Ivan Enderlin, 2015-11-18T08:28:04+01:00)
+ * Test: Write test suite of `Hoa\Exception\Error`. (Ivan Enderlin, 2015-11-17T22:30:14+01:00)
+ * Test: Write test suite of `…\Exception\Exception`. (Ivan Enderlin, 2015-11-17T22:03:31+01:00)
+ * Test: Write test suite of `Hoa\Exception\Idle`. (Ivan Enderlin, 2015-11-17T21:41:07+01:00)
+ * Split from `Hoa\Core`. (Ivan Enderlin, 2015-11-13T08:58:45+01:00)
+
+(first snapshot)
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception;
+
+/**
+ * Class \Hoa\Exception\Error.
+ *
+ * This exception is the equivalent representation of PHP errors.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Error extends Exception
+{
+ /**
+ * Constructor.
+ *
+ * @param string $message Message.
+ * @param int $code Code (the ID).
+ * @param string $file File.
+ * @param int $line Line.
+ * @param array $trace Trace.
+ */
+ public function __construct(
+ $message,
+ $code,
+ $file,
+ $line,
+ array $trace = []
+ ) {
+ $this->file = $file;
+ $this->line = $line;
+ $this->_trace = $trace;
+
+ parent::__construct($message, $code);
+
+ return;
+ }
+
+ /**
+ * Enable error handler: Transform PHP error into `\Hoa\Exception\Error`.
+ *
+ * @param bool $enable Enable.
+ * @return mixed
+ */
+ public static function enableErrorHandler($enable = true)
+ {
+ if (false === $enable) {
+ return restore_error_handler();
+ }
+
+ return set_error_handler(
+ function ($no, $str, $file = null, $line = null, $ctx = null) {
+ if (0 === ($no & error_reporting())) {
+ return;
+ }
+
+ $trace = debug_backtrace();
+ array_shift($trace);
+ array_shift($trace);
+
+ throw new Error($str, $no, $file, $line, $trace);
+ }
+ );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception;
+
+use Hoa\Consistency;
+use Hoa\Event;
+
+/**
+ * Class \Hoa\Exception\Exception.
+ *
+ * Each exception must extend \Hoa\Exception\Exception.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends Idle implements Event\Source
+{
+ /**
+ * Create an exception.
+ * An exception is built with a formatted message, a code (an ID), and an
+ * array that contains the list of formatted string for the message. If
+ * chaining, we can add a previous exception.
+ *
+ * @param string $message Formatted message.
+ * @param int $code Code (the ID).
+ * @param array $arguments Arguments to format message.
+ * @param \Throwable $previous Previous exception in chaining.
+ */
+ public function __construct(
+ $message,
+ $code = 0,
+ $arguments = [],
+ $previous = null
+ ) {
+ parent::__construct($message, $code, $arguments, $previous);
+
+ if (false === Event::eventExists('hoa://Event/Exception')) {
+ Event::register('hoa://Event/Exception', __CLASS__);
+ }
+
+ $this->send();
+
+ return;
+ }
+
+ /**
+ * Send the exception on hoa://Event/Exception.
+ *
+ * @return void
+ */
+ public function send()
+ {
+ Event::notify(
+ 'hoa://Event/Exception',
+ $this,
+ new Event\Bucket($this)
+ );
+
+ return;
+ }
+}
+
+/**
+ * Flex entity.
+ */
+Consistency::flexEntity('Hoa\Exception\Exception');
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception;
+
+/**
+ * Class \Hoa\Exception\Group.
+ *
+ * This is an exception that contains a group of exceptions.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Group extends Exception implements \ArrayAccess, \IteratorAggregate, \Countable
+{
+ /**
+ * All exceptions (stored in a stack for transactions).
+ *
+ * @var \SplStack
+ */
+ protected $_group = null;
+
+
+
+ /**
+ * Create an exception.
+ *
+ * @param string $message Formatted message.
+ * @param int $code Code (the ID).
+ * @param array $arguments Arguments to format message.
+ * @param \Exception $previous Previous exception in chaining.
+ */
+ public function __construct(
+ $message,
+ $code = 0,
+ $arguments = [],
+ \Exception $previous = null
+ ) {
+ parent::__construct($message, $code, $arguments, $previous);
+ $this->_group = new \SplStack();
+ $this->beginTransaction();
+
+ return;
+ }
+
+ /**
+ * Raise an exception as a string.
+ *
+ * @param bool $previous Whether raise previous exception if exists.
+ * @return string
+ */
+ public function raise($previous = false)
+ {
+ $out = parent::raise($previous);
+
+ if (0 >= count($this)) {
+ return $out;
+ }
+
+ $out .= "\n\n" . 'Contains the following exceptions:';
+
+ foreach ($this as $exception) {
+ $out .=
+ "\n\n" . ' • ' .
+ str_replace(
+ "\n",
+ "\n" . ' ',
+ $exception->raise($previous)
+ );
+ }
+
+ return $out;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return \Hoa\Exception\Group
+ */
+ public function beginTransaction()
+ {
+ $this->_group->push(new \ArrayObject());
+
+ return $this;
+ }
+
+ /**
+ * Rollback a transaction.
+ *
+ * @return \Hoa\Exception\Group
+ */
+ public function rollbackTransaction()
+ {
+ if (1 >= count($this->_group)) {
+ return $this;
+ }
+
+ $this->_group->pop();
+
+ return $this;
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return \Hoa\Exception\Group
+ */
+ public function commitTransaction()
+ {
+ if (false === $this->hasUncommittedExceptions()) {
+ $this->_group->pop();
+
+ return $this;
+ }
+
+ foreach ($this->_group->pop() as $index => $exception) {
+ $this[$index] = $exception;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Check if there is uncommitted exceptions.
+ *
+ * @return bool
+ */
+ public function hasUncommittedExceptions()
+ {
+ return
+ 1 < count($this->_group) &&
+ 0 < count($this->_group->top());
+ }
+
+ /**
+ * Check if an index in the group exists.
+ *
+ * @param mixed $index Index.
+ * @return bool
+ */
+ public function offsetExists($index)
+ {
+ foreach ($this->_group as $group) {
+ if (isset($group[$index])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get an exception from the group.
+ *
+ * @param mixed $index Index.
+ * @return Exception
+ */
+ public function offsetGet($index)
+ {
+ foreach ($this->_group as $group) {
+ if (isset($group[$index])) {
+ return $group[$index];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Set an exception in the group.
+ *
+ * @param mixed $index Index.
+ * @param Exception $exception Exception.
+ * @return void
+ */
+ public function offsetSet($index, $exception)
+ {
+ if (!($exception instanceof \Exception)) {
+ return null;
+ }
+
+ $group = $this->_group->top();
+
+ if (null === $index ||
+ true === is_int($index)) {
+ $group[] = $exception;
+ } else {
+ $group[$index] = $exception;
+ }
+
+ return;
+ }
+
+ /**
+ * Remove an exception in the group.
+ *
+ * @param mixed $index Index.
+ * @return void
+ */
+ public function offsetUnset($index)
+ {
+ foreach ($this->_group as $group) {
+ if (isset($group[$index])) {
+ unset($group[$index]);
+ }
+ }
+
+ return;
+ }
+
+ /**
+ * Get committed exceptions in the group.
+ *
+ * @return \ArrayObject
+ */
+ public function getExceptions()
+ {
+ return $this->_group->bottom();
+ }
+
+ /**
+ * Get an iterator over all exceptions (committed or not).
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return $this->getExceptions()->getIterator();
+ }
+
+ /**
+ * Count the number of committed exceptions.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->getExceptions());
+ }
+
+ /**
+ * Count the stack size, i.e. the number of opened transactions.
+ *
+ * @return int
+ */
+ public function getStackSize()
+ {
+ return count($this->_group);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception;
+
+/**
+ * Class \Hoa\Exception\Idle.
+ *
+ * `\Hoa\Exception\Idle` is the mother exception class of libraries. The only
+ * difference between `\Hoa\Exception\Idle` and its directly child
+ * `\Hoa\Exception` is that the latter fires events after beeing constructed.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Idle extends \Exception
+{
+ /**
+ * Delay processing on arguments.
+ *
+ * @var array
+ */
+ protected $_tmpArguments = null;
+
+ /**
+ * Arguments to format message.
+ *
+ * @var array
+ */
+ protected $_arguments = null;
+
+ /**
+ * Backtrace.
+ *
+ * @var array
+ */
+ protected $_trace = null;
+
+ /**
+ * Previous.
+ *
+ * @var \Exception
+ */
+ protected $_previous = null;
+
+ /**
+ * Original message.
+ *
+ * @var string
+ */
+ protected $_rawMessage = null;
+
+
+
+ /**
+ * Create an exception.
+ * An exception is built with a formatted message, a code (an ID) and an
+ * array that contains the list of formatted strings for the message. If
+ * chaining, we can add a previous exception.
+ *
+ * @param string $message Formatted message.
+ * @param int $code Code (the ID).
+ * @param array $arguments Arguments to format message.
+ * @param \Exception $previous Previous exception in chaining.
+ */
+ public function __construct(
+ $message,
+ $code = 0,
+ $arguments = [],
+ \Exception $previous = null
+ ) {
+ $this->_tmpArguments = $arguments;
+ parent::__construct($message, $code, $previous);
+ $this->_rawMessage = $message;
+ $this->message = @vsprintf($message, $this->getArguments());
+
+ return;
+ }
+
+ /**
+ * Get the backtrace.
+ * Do not use \Exception::getTrace() any more.
+ *
+ * @return array
+ */
+ public function getBacktrace()
+ {
+ if (null === $this->_trace) {
+ $this->_trace = $this->getTrace();
+ }
+
+ return $this->_trace;
+ }
+
+ /**
+ * Get previous.
+ * Do not use \Exception::getPrevious() any more.
+ *
+ * @return \Exception
+ */
+ public function getPreviousThrow()
+ {
+ if (null === $this->_previous) {
+ $this->_previous = $this->getPrevious();
+ }
+
+ return $this->_previous;
+ }
+
+ /**
+ * Get arguments for the message.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ if (null === $this->_arguments) {
+ $arguments = $this->_tmpArguments;
+
+ if (!is_array($arguments)) {
+ $arguments = [$arguments];
+ }
+
+ foreach ($arguments as &$value) {
+ if (null === $value) {
+ $value = '(null)';
+ }
+ }
+
+ $this->_arguments = $arguments;
+ unset($this->_tmpArguments);
+ }
+
+ return $this->_arguments;
+ }
+
+ /**
+ * Get the raw message.
+ *
+ * @return string
+ */
+ public function getRawMessage()
+ {
+ return $this->_rawMessage;
+ }
+
+ /**
+ * Get the message already formatted.
+ *
+ * @return string
+ */
+ public function getFormattedMessage()
+ {
+ return $this->getMessage();
+ }
+
+ /**
+ * Get the source of the exception (class, method, function, main etc.).
+ *
+ * @return string
+ */
+ public function getFrom()
+ {
+ $trace = $this->getBacktrace();
+ $from = '{main}';
+
+ if (!empty($trace)) {
+ $t = $trace[0];
+ $from = '';
+
+ if (isset($t['class'])) {
+ $from .= $t['class'] . '::';
+ }
+
+ if (isset($t['function'])) {
+ $from .= $t['function'] . '()';
+ }
+ }
+
+ return $from;
+ }
+
+ /**
+ * Raise an exception as a string.
+ *
+ * @param bool $previous Whether raise previous exception if exists.
+ * @return string
+ */
+ public function raise($previous = false)
+ {
+ $message = $this->getFormattedMessage();
+ $trace = $this->getBacktrace();
+ $file = '/dev/null';
+ $line = -1;
+ $pre = $this->getFrom();
+
+ if (!empty($trace)) {
+ $file = isset($trace['file']) ? $trace['file'] : null;
+ $line = isset($trace['line']) ? $trace['line'] : null;
+ }
+
+ $pre .= ': ';
+
+ try {
+ $out =
+ $pre . '(' . $this->getCode() . ') ' . $message . "\n" .
+ 'in ' . $this->getFile() . ' at line ' .
+ $this->getLine() . '.';
+ } catch (\Exception $e) {
+ $out =
+ $pre . '(' . $this->getCode() . ') ' . $message . "\n" .
+ 'in ' . $file . ' around line ' . $line . '.';
+ }
+
+ if (true === $previous &&
+ null !== $previous = $this->getPreviousThrow()) {
+ $out .=
+ "\n\n" . ' ⬇' . "\n\n" .
+ 'Nested exception (' . get_class($previous) . '):' . "\n" .
+ ($previous instanceof self
+ ? $previous->raise(true)
+ : $previous->getMessage());
+ }
+
+ return $out;
+ }
+
+ /**
+ * Catch uncaught exception (only \Hoa\Exception\Idle and children).
+ *
+ * @param \Throwable $exception The exception.
+ * @return void
+ * @throws \Throwable
+ */
+ public static function uncaught($exception)
+ {
+ if (!($exception instanceof self)) {
+ throw $exception;
+ }
+
+ while (0 < ob_get_level()) {
+ ob_end_flush();
+ }
+
+ echo
+ 'Uncaught exception (' . get_class($exception) . '):' . "\n" .
+ $exception->raise(true);
+
+ return;
+ }
+
+ /**
+ * String representation of object.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->raise();
+ }
+
+ /**
+ * Enable uncaught exception handler.
+ * This is restricted to Hoa's exceptions only.
+ *
+ * @param bool $enable Enable.
+ * @return mixed
+ */
+ public static function enableUncaughtHandler($enable = true)
+ {
+ if (false === $enable) {
+ return restore_exception_handler();
+ }
+
+ return set_exception_handler(function ($exception) {
+ return self::uncaught($exception);
+ });
+ }
+}
--- /dev/null
+<p align="center">
+ <img src="https://static.hoa-project.net/Image/Hoa.svg" alt="Hoa" width="250px" />
+</p>
+
+---
+
+<p align="center">
+ <a href="https://travis-ci.org/hoaproject/exception"><img src="https://img.shields.io/travis/hoaproject/exception/master.svg" alt="Build status" /></a>
+ <a href="https://coveralls.io/github/hoaproject/exception?branch=master"><img src="https://img.shields.io/coveralls/hoaproject/exception/master.svg" alt="Code coverage" /></a>
+ <a href="https://packagist.org/packages/hoa/exception"><img src="https://img.shields.io/packagist/dt/hoa/exception.svg" alt="Packagist" /></a>
+ <a href="https://hoa-project.net/LICENSE"><img src="https://img.shields.io/packagist/l/hoa/exception.svg" alt="License" /></a>
+</p>
+<p align="center">
+ Hoa is a <strong>modular</strong>, <strong>extensible</strong> and
+ <strong>structured</strong> set of PHP libraries.<br />
+ Moreover, Hoa aims at being a bridge between industrial and research worlds.
+</p>
+
+# Hoa\Exception
+
+[![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject)
+[![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central)
+[![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Exception)
+[![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/exception)
+
+This library allows to use advanced exceptions. It provides generic exceptions
+(that are sent over the `hoa://Event/Exception` event channel), idle exceptions
+(that are not sent over an event channel), uncaught exception handlers, errors
+to exceptions handler and group of exceptions (with transactions).
+
+[Learn more](https://central.hoa-project.net/Documentation/Library/Exception).
+
+## Installation
+
+With [Composer](https://getcomposer.org/), to include this library into
+your dependencies, you need to
+require [`hoa/exception`](https://packagist.org/packages/hoa/exception):
+
+```sh
+$ composer require hoa/exception '~1.0'
+```
+
+For more installation procedures, please read [the Source
+page](https://hoa-project.net/Source.html).
+
+## Testing
+
+Before running the test suites, the development dependencies must be installed:
+
+```sh
+$ composer install
+```
+
+Then, to run all the test suites:
+
+```sh
+$ vendor/bin/hoa test:run
+```
+
+For more information, please read the [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html).
+
+## Quick usage
+
+We propose a quick overview of how to use generic exceptions, how to listen all
+thrown exceptions through events and how to use group of exceptions.
+
+### Generic exceptions
+
+An exception is constitued of:
+ * A message,
+ * A code (optional),
+ * A list of arguments for the message (à la `printf`, optional),
+ * A previous exception (optional).
+
+Thus, the following example builds an exception:
+
+```php
+$exception = new Hoa\Exception\Exception('Hello %s!', 0, 'world');
+```
+
+The exception message will be: `Hello world!`. The “raise” message (with all
+information, not only the message) is:
+
+```
+{main}: (0) Hello world!
+in … at line ….
+```
+
+Previous exceptions are shown too, for instance:
+
+```php
+$previous = new Hoa\Exception\Exception('Hello previous.');
+$exception = new Hoa\Exception\Exception('Hello %s!', 0, 'world', $previous);
+
+echo $exception->raise(true);
+
+/**
+ * Will output:
+ * {main}: (0) Hello world!
+ * in … at line ….
+ *
+ * ⬇
+ *
+ * Nested exception (Hoa\Exception\Exception):
+ * {main}: (0) Hello previous.
+ * in … at line ….
+ */
+```
+
+### Listen exceptions through events
+
+Most exceptions in Hoa extend `Hoa\Exception\Exception`, which fire themselves
+on the `hoa://Event/Exception` event channel (please, see [the `Hoa\Event`
+library](http://central.hoa-project.net/Resource/Library/Event)). Consequently,
+we can listen for all exceptions that are thrown in the application by writing:
+
+```php
+Hoa\Event\Event::getEvent('hoa://Event/Exception')->attach(
+ function (Hoa\Event\Bucket $bucket) {
+ $exception = $bucket->getData();
+ // …
+ }
+);
+```
+
+Only the `Hoa\Exception\Idle` exceptions are not fired on the channel event.
+
+### Group and transactions
+
+Groups of exceptions are represented by the `Hoa\Exception\Group`. A group is an
+exception that contains one or many exceptions. A transactional API is provided
+to add more exceptions in the group with the following methods:
+ * `beginTransaction` to start a transaction,
+ * `rollbackTransaction` to remove all newly added exceptions since
+ `beginTransaction` call,
+ * `commitTransaction` to merge all newly added exceptions in the previous
+ transaction,
+ * `hasUncommittedExceptions` to check whether they are pending exceptions or
+ not.
+
+For instance, if an exceptional behavior is due to several reasons, a group of
+exceptions can be thrown instead of one exception. Group can be nested too,
+which is useful to represent a tree of exceptions. Thus:
+
+```php
+// A group of exceptions.
+$group = new Hoa\Exception\Group('Failed because of several reasons.');
+$group['first'] = new Hoa\Exception\Exception('First reason');
+$group['second'] = new Hoa\Exception\Exception('Second reason');
+
+// Can nest another group.
+$group['third'] = new Hoa\Exception\Group('Third reason');
+$group['third']['fourth'] = new Hoa\Exception\Exception('Fourth reason');
+
+echo $group->raise(true);
+
+/**
+ * Will output:
+ * {main}: (0) Failed because of several reasons.
+ * in … at line ….
+ *
+ * Contains the following exceptions:
+ *
+ * • {main}: (0) First reason
+ * in … at line ….
+ *
+ * • {main}: (0) Second reason
+ * in … at line ….
+ *
+ * • {main}: (0) Third reason
+ * in … at line ….
+ *
+ * Contains the following exceptions:
+ *
+ * • {main}: (0) Fourth reason
+ * in … at line ….
+ */
+```
+
+The following example uses a transaction to add new exceptions in the group:
+
+```php
+$group = new Hoa\Exception\Group('Failed because of several reasons.');
+$group[] = new Hoa\Exception\Exception('Always present.');
+
+$group->beginTransaction();
+
+$group[] = new Hoa\Exception\Exception('Might be present.');
+
+if (true === $condition) {
+ $group->commitTransaction();
+} else {
+ $group->rollbackTransaction();
+}
+```
+
+## Documentation
+
+The
+[hack book of `Hoa\Exception`](https://central.hoa-project.net/Documentation/Library/Exception)
+contains detailed information about how to use this library and how it works.
+
+To generate the documentation locally, execute the following commands:
+
+```sh
+$ composer require --dev hoa/devtools
+$ vendor/bin/hoa devtools:documentation --open
+```
+
+More documentation can be found on the project's website:
+[hoa-project.net](https://hoa-project.net/).
+
+## Getting help
+
+There are mainly two ways to get help:
+
+ * On the [`#hoaproject`](https://webchat.freenode.net/?channels=#hoaproject)
+ IRC channel,
+ * On the forum at [users.hoa-project.net](https://users.hoa-project.net).
+
+## Contribution
+
+Do you want to contribute? Thanks! A detailed [contributor
+guide](https://hoa-project.net/Literature/Contributor/Guide.html) explains
+everything you need to know.
+
+## License
+
+Hoa is under the New BSD License (BSD-3-Clause). Please, see
+[`LICENSE`](https://hoa-project.net/LICENSE) for details.
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception\Test\Unit;
+
+use Hoa\Exception\Error as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Exception\Test\Unit\Error.
+ *
+ * Test suite of the error class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Error extends Test\Unit\Suite
+{
+ public function case_is_an_exception()
+ {
+ $this
+ ->when($result = new SUT('foo', 42, '/hoa/flatland', 153))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Exception\Exception');
+ }
+
+ public function case_get_message()
+ {
+ $this
+ ->given($exception = new SUT('foo', 42, '/hoa/flatland', 153))
+ ->when($result = $exception->raise())
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ '{main}: (42) foo' . "\n" .
+ 'in /hoa/flatland at line 153.'
+ );
+ }
+
+ public function case_disable_error_handler()
+ {
+ $this
+ ->given(
+ $this->function->restore_error_handler = function () use (&$called) {
+ $called = true;
+
+ return null;
+ }
+ )
+ ->when($result = SUT::enableErrorHandler(false))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_enable_error_handler()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $this->function->set_error_handler = function ($handler) use ($self, &$called) {
+ $called = true;
+
+ $self
+ ->object($handler)
+ ->isInstanceOf('Closure')
+ ->let($reflection = new \ReflectionObject($handler))
+ ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters())
+ ->hasSize(5)
+ ->string($invokeParameters[0]->getName())
+ ->isEqualTo('no')
+ ->string($invokeParameters[1]->getName())
+ ->isEqualTo('str')
+ ->string($invokeParameters[2]->getName())
+ ->isEqualTo('file')
+ ->boolean($invokeParameters[2]->isOptional())
+ ->isTrue()
+ ->string($invokeParameters[3]->getName())
+ ->isEqualTo('line')
+ ->boolean($invokeParameters[3]->isOptional())
+ ->isTrue()
+ ->string($invokeParameters[4]->getName())
+ ->isEqualTo('ctx')
+ ->boolean($invokeParameters[4]->isOptional())
+ ->isTrue();
+
+ return null;
+ }
+ )
+ ->when($result = SUT::enableErrorHandler())
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_error_handler()
+ {
+ $this
+ ->given(SUT::enableErrorHandler())
+ ->exception(function () {
+ ++$i;
+ })
+ ->isInstanceOf('Hoa\Exception\Error')
+ ->hasMessage('Undefined variable: i');
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception\Test\Unit;
+
+use Hoa\Event;
+use Hoa\Exception\Exception as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Exception\Test\Unit\Exception.
+ *
+ * Test suite of the exception class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Exception extends Test\Unit\Suite
+{
+ public function case_is_an_idle_exception()
+ {
+ $this
+ ->when($result = new SUT('foo'))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Exception\Idle');
+ }
+
+ public function case_event_is_registered()
+ {
+ $this
+ ->given(new SUT('foo'))
+ ->when($result = Event::eventExists('hoa://Event/Exception'))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_event_is_sent()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ Event::getEvent('hoa://Event/Exception')->attach(
+ function (Event\Bucket $bucket) use ($self, &$called) {
+ $called = true;
+
+ $self
+ ->object($bucket->getSource())
+ ->isInstanceOf('Hoa\Exception\Exception')
+ ->string($bucket->getSource()->getMessage())
+ ->isEqualTo('foo')
+ ->object($bucket->getData())
+ ->isIdenticalTo($bucket->getSource());
+ }
+ )
+ )
+ ->when(new SUT('foo'))
+ ->then
+ ->boolean($called)
+ ->isTrue();
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception\Test\Unit;
+
+use Hoa\Exception\Group as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Exception\Test\Unit\Group.
+ *
+ * Test suite of the group class.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Group extends Test\Unit\Suite
+{
+ public function case_is_an_exception_arrayaccess_iteratoraggregate_countable()
+ {
+ $this
+ ->when($result = new SUT('foo'))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Hoa\Exception\Exception')
+ ->isInstanceOf('ArrayAccess')
+ ->isInstanceOf('IteratorAggregate')
+ ->isInstanceOf('Countable');
+ }
+
+ public function case_constructor()
+ {
+ $this
+ ->given(
+ $message = 'foo %s %d %s',
+ $code = 7,
+ $arguments = ['arg', 42, null],
+ $previous = new SUT('previous')
+ )
+ ->when($result = new SUT($message, $code, $arguments, $previous), $line = __LINE__)
+ ->then
+ ->string($result->getMessage())
+ ->isEqualTo('foo arg 42 (null)')
+ ->integer($result->getCode())
+ ->isEqualTo(7)
+ ->array($result->getArguments())
+ ->isEqualTo(['arg', 42, '(null)'])
+ ->object($result->getPreviousThrow())
+ ->isIdenticalTo($previous)
+ ->boolean($result->hasUncommittedExceptions())
+ ->isFalse();
+ }
+
+ public function case_raise_zero_exception()
+ {
+ $this
+ ->given($group = new SUT('foo'), $line = __LINE__)
+ ->when($result = $group->raise())
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $line . '.'
+ );
+ }
+
+ public function case_raise_one_exception()
+ {
+ $this
+ ->given(
+ $exception1 = new SUT('bar'), $barLine = __LINE__,
+ $group = new SUT('foo'), $fooLine = __LINE__,
+ $group[] = $exception1
+ )
+ ->when($result = $group->raise())
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" .
+ 'Contains the following exceptions:' . "\n\n" .
+ ' • ' . __METHOD__ . '(): (0) bar' . "\n" .
+ ' in ' . __FILE__ . ' at line ' . $barLine . '.'
+ );
+ }
+
+ public function case_raise_more_exceptions()
+ {
+ $this
+ ->given(
+ $exception1 = new SUT('bar'), $barLine = __LINE__,
+ $exception2 = new SUT('baz'), $bazLine = __LINE__,
+ $group = new SUT('foo'), $fooLine = __LINE__,
+ $group[] = $exception1,
+ $group[] = $exception2
+ )
+ ->when($result = $group->raise())
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" .
+ 'Contains the following exceptions:' . "\n\n" .
+ ' • ' . __METHOD__ . '(): (0) bar' . "\n" .
+ ' in ' . __FILE__ . ' at line ' . $barLine . '.' . "\n\n" .
+ ' • ' . __METHOD__ . '(): (0) baz' . "\n" .
+ ' in ' . __FILE__ . ' at line ' . $bazLine . '.'
+ );
+ }
+
+ public function case_begin_transaction()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $oldStackSize = $group->getStackSize()
+ )
+ ->when(
+ $result = $group->beginTransaction(),
+ $stackSize = $group->getStackSize()
+ )
+ ->then
+ ->integer($oldStackSize)
+ ->isEqualTo(1)
+ ->object($result)
+ ->isIdenticalTo($group)
+ ->integer($stackSize)
+ ->isEqualTo($oldStackSize + 1);
+ }
+
+ public function case_rollback_transaction_with_an_empty_stack()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $oldStackSize = $group->getStackSize()
+ )
+ ->when(
+ $result = $group->rollbackTransaction(),
+ $stackSize = $group->getStackSize()
+ )
+ ->then
+ ->integer($oldStackSize)
+ ->isEqualTo(1)
+ ->object($result)
+ ->isIdenticalTo($group)
+ ->integer($stackSize)
+ ->isEqualTo($oldStackSize);
+ }
+
+ public function case_rollback_transaction()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $oldStackSize = $group->getStackSize(),
+ $group->rollbackTransaction()
+ )
+ ->when(
+ $result = $group->rollbackTransaction(),
+ $stackSize = $group->getStackSize()
+ )
+ ->then
+ ->integer($oldStackSize)
+ ->isEqualTo(3)
+ ->object($result)
+ ->isIdenticalTo($group)
+ ->integer($stackSize)
+ ->isEqualTo($oldStackSize - 2);
+ }
+
+ public function case_commit_transaction_with_an_empty_stack()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $oldCount = count($group),
+ $oldStackSize = $group->getStackSize()
+ )
+ ->when(
+ $result = $group->commitTransaction(),
+ $count = count($group),
+ $stackSize = $group->getStackSize()
+ )
+ ->then
+ ->integer($oldCount)
+ ->isEqualTo(0)
+ ->integer($oldStackSize)
+ ->isEqualTo(2)
+ ->object($result)
+ ->isIdenticalTo($group)
+ ->integer($count)
+ ->isEqualTo($oldCount)
+ ->integer($stackSize)
+ ->isEqualTo($oldStackSize - 1);
+ }
+
+ public function case_commit_transaction()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $exception1 = new SUT('bar'),
+ $exception2 = new SUT('baz'),
+ $group[] = $exception1,
+ $group[] = $exception2,
+ $oldCount = count($group),
+ $oldStackSize = $group->getStackSize()
+ )
+ ->when(
+ $result = $group->commitTransaction(),
+ $count = count($group),
+ $stackSize = $group->getStackSize()
+ )
+ ->then
+ ->integer($oldCount)
+ ->isEqualTo(0)
+ ->integer($oldStackSize)
+ ->isEqualTo(2)
+ ->object($result)
+ ->isIdenticalTo($group)
+ ->integer($count)
+ ->isEqualTo($oldCount + 2)
+ ->integer($stackSize)
+ ->isEqualTo($oldStackSize - 1)
+ ->array(iterator_to_array($group->getIterator()))
+ ->isEqualTo([
+ 0 => $exception1,
+ 1 => $exception2
+ ]);
+ }
+
+ public function case_has_uncommitted_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group[] = new SUT('bar')
+ )
+ ->when($result = $group->hasUncommittedExceptions())
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_has_no_uncommitted_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction()
+ )
+ ->when($result = $group->hasUncommittedExceptions())
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_has_no_uncommitted_exceptions_with_empty_stack()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group[] = new SUT('bar')
+ )
+ ->when($result = $group->hasUncommittedExceptions())
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_offset_exists_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($result = $group->offsetExists('bar'))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_offset_does_not_exist_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($result = $group->offsetExists('baz'))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_offset_exists()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($result = $group->offsetExists('bar'))
+ ->then
+ ->boolean($result)
+ ->isTrue();
+ }
+
+ public function case_offset_does_not_exist()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($result = $group->offsetExists('baz'))
+ ->then
+ ->boolean($result)
+ ->isFalse();
+ }
+
+ public function case_offset_get_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $group['bar'] = $exception1
+ )
+ ->when($result = $group->offsetGet('bar'))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_offset_get_does_not_exist_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $group['bar'] = $exception1
+ )
+ ->when($result = $group->offsetGet('baz'))
+ ->then
+ ->variable($result)
+ ->isNull();
+ }
+
+ public function case_offset_get()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $exception1 = new SUT('bar'),
+ $group['bar'] = $exception1
+ )
+ ->when($result = $group->offsetGet('bar'))
+ ->then
+ ->object($result)
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_offset_get_does_not_exist()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $exception1 = new SUT('bar'),
+ $group['bar'] = $exception1
+ )
+ ->when($result = $group->offsetGet('baz'))
+ ->then
+ ->variable($result)
+ ->isNull();
+ }
+
+ public function case_offset_set_not_an_exception()
+ {
+ $this
+ ->given($group = new SUT('foo'))
+ ->when($group->offsetSet('bar', new \StdClass()))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isFalse();
+ }
+
+ public function case_offset_set()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar')
+ )
+ ->when($result = $group->offsetExists('bar'))
+ ->then
+ ->boolean($result)
+ ->isFalse()
+
+ ->when($group->offsetSet('bar', $exception1))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isTrue()
+ ->object($group->offsetGet('bar'))
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_offset_set_with_a_null_index()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar')
+ )
+ ->when($group->offsetSet(null, $exception1))
+ ->then
+ ->boolean($group->offsetExists(0))
+ ->isTrue()
+ ->object($group->offsetGet(0))
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_offset_set_with_an_integer_index()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar')
+ )
+ ->when($group->offsetSet(42, $exception1))
+ ->then
+ ->boolean($group->offsetExists(42))
+ ->isFalse()
+ ->boolean($group->offsetExists(0))
+ ->isTrue()
+ ->object($group->offsetGet(0))
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_offset_unset_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($group->offsetUnset('bar'))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isFalse();
+ }
+
+ public function case_offset_unset_does_not_exist_with_no_uncommited_exceptions()
+ {
+ $this
+ ->given($group = new SUT('foo'))
+ ->when($group->offsetUnset('bar'))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isFalse();
+ }
+
+ public function case_offset_unset()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction(),
+ $group['bar'] = new SUT('bar')
+ )
+ ->when($result = $group->offsetUnset('bar'))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isFalse();
+ }
+
+ public function case_offset_unset_does_not_exist()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $group->beginTransaction(),
+ $group->beginTransaction()
+ )
+ ->when($result = $group->offsetUnset('bar'))
+ ->then
+ ->boolean($group->offsetExists('bar'))
+ ->isFalse();
+ }
+
+ public function case_get_exceptions()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $exception2 = new SUT('baz'),
+ $group['bar'] = $exception1,
+ $group->beginTransaction(),
+ $group['baz'] = $exception2
+ )
+ ->when($result = $group->getExceptions())
+ ->then
+ ->object($result)
+ ->isInstanceOf('ArrayObject')
+ ->object($result['bar'])
+ ->isIdenticalTo($exception1);
+ }
+
+ public function case_get_iterator()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $group['bar'] = $exception1
+ )
+ ->when($result = $group->getIterator())
+ ->then
+ ->object($result)
+ ->isInstanceOf('ArrayIterator')
+ ->array(iterator_to_array($result))
+ ->isEqualTo([
+ 'bar' => $exception1
+ ]);
+ }
+
+ public function case_count()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $exception2 = new SUT('baz'),
+ $group['bar'] = $exception1,
+ $group->beginTransaction(),
+ $group['baz'] = $exception2
+ )
+ ->when($result = count($group))
+ ->then
+ ->integer($result)
+ ->isEqualTo(1);
+ }
+
+ public function get_get_stack_size()
+ {
+ $this
+ ->given(
+ $group = new SUT('foo'),
+ $exception1 = new SUT('bar'),
+ $exception2 = new SUT('baz'),
+ $group['bar'] = $exception1,
+ $group->beginTransaction(),
+ $group['baz'] = $exception2
+ )
+ ->when($result = $group->getStackSize())
+ ->then
+ ->integer($result)
+ ->isEqualTo(2);
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * Hoa
+ *
+ *
+ * @license
+ *
+ * New BSD License
+ *
+ * Copyright © 2007-2017, Hoa community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the Hoa nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace Hoa\Exception\Test\Unit;
+
+use Hoa\Exception\Idle as SUT;
+use Hoa\Test;
+
+/**
+ * Class \Hoa\Exception\Test\Unit\Idle.
+ *
+ * Test suite of the idle exception.
+ *
+ * @copyright Copyright © 2007-2017 Hoa community
+ * @license New BSD License
+ */
+class Idle extends Test\Unit\Suite
+{
+ public function case_is_a_real_exception()
+ {
+ $this
+ ->when($result = new SUT('foo'))
+ ->then
+ ->object($result)
+ ->isInstanceOf('Exception');
+ }
+
+ public function case_get_backtrace()
+ {
+ $this
+ ->given($exception = new SUT('foo'))
+ ->when($result = $exception->getBacktrace())
+ ->then
+ ->array($result)
+ ->hasKey(0)
+ ->array($result[0])
+ ->hasKey('file')
+ ->hasKey('line')
+ ->hasKey('function')
+ ->hasKey('class')
+ ->hasKey('type')
+ ->hasKey('args');
+ }
+
+ public function case_get_previous_throw()
+ {
+ $this
+ ->given(
+ $previous = new SUT('previous'),
+ $exception = new SUT('foo', 0, [], $previous)
+ )
+ ->when($result = $exception->getPreviousThrow())
+ ->then
+ ->object($result)
+ ->isIdenticalTo($previous);
+ }
+
+ public function case_get_arguments()
+ {
+ $this
+ ->given($exception = new SUT('foo', 0, ['arg', 42, null]))
+ ->when($result = $exception->getArguments())
+ ->then
+ ->array($result)
+ ->isEqualTo(['arg', 42, '(null)']);
+ }
+
+ public function case_get_arguments_from_a_string()
+ {
+ $this
+ ->given($exception = new SUT('foo', 0, 'arg'))
+ ->when($result = $exception->getArguments())
+ ->then
+ ->array($result)
+ ->isEqualTo(['arg']);
+ }
+
+ public function case_get_raw_message()
+ {
+ $this
+ ->given(
+ $message = 'foo %s',
+ $exception = new SUT($message)
+ )
+ ->when($result = $exception->getRawMessage())
+ ->then
+ ->string($result)
+ ->isEqualTo($message);
+ }
+
+ public function case_get_formatted_message()
+ {
+ $this
+ ->given(
+ $message = 'foo %s',
+ $exception = new SUT($message, 0, 'bar')
+ )
+ ->when($result = $exception->getFormattedMessage())
+ ->then
+ ->string($result)
+ ->isEqualTo($exception->getMessage())
+ ->isEqualTo('foo bar');
+ }
+
+ public function case_get_from_object()
+ {
+ $this
+ ->given($exception = new SUT('foo'))
+ ->when($result = $exception->getFrom())
+ ->then
+ ->string($result)
+ ->isEqualTo(__METHOD__ . '()');
+ }
+
+ public function case_raise()
+ {
+ $this
+ ->given($exception = new SUT('foo'), $line = __LINE__)
+ ->when($result = $exception->raise())
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $line . '.'
+ );
+ }
+
+ public function case_raise_with_previous()
+ {
+ $this
+ ->given(
+ $previous = new SUT('previous'), $previousLine = __LINE__,
+ $exception = new SUT('foo', 0, [], $previous), $line = __LINE__
+ )
+ ->when($result = $exception->raise(true))
+ ->then
+ ->string($result)
+ ->isEqualTo(
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $line . '.' . "\n\n" .
+ ' ⬇' . "\n\n" .
+ 'Nested exception (' . get_class($previous) . '):' . "\n" .
+ __METHOD__ . '(): (0) previous' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $previousLine . '.'
+ );
+ }
+
+ public function case_uncaught()
+ {
+ $this
+ ->given(
+ $this->function->ob_get_level = 0,
+ $exception = new SUT('foo'), $line = __LINE__
+ )
+ ->when($result = SUT::uncaught($exception))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->output
+ ->isEqualTo(
+ 'Uncaught exception (' . get_class($exception) . '):' . "\n" .
+ __METHOD__ . '(): (0) foo' . "\n" .
+ 'in ' . __FILE__ . ' at line ' . $line . '.'
+ );
+ }
+
+ public function case_uncaught_not_Hoa()
+ {
+ $this
+ ->exception(function () {
+ SUT::uncaught(new \Exception('foo'));
+ })
+ ->isInstanceOf('Exception')
+ ->output
+ ->isEmpty();
+ }
+
+ public function case_to_string()
+ {
+ $this
+ ->given($exception = new SUT('foo'))
+ ->when($result = $exception->__toString())
+ ->then
+ ->string($result)
+ ->isEqualTo($exception->raise());
+ }
+
+ public function case_disable_uncaught_handler()
+ {
+ $this
+ ->given(
+ $this->function->restore_exception_handler = function () use (&$called) {
+ $called = true;
+
+ return null;
+ }
+ )
+ ->when($result = SUT::enableUncaughtHandler(false))
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+
+ public function case_enable_uncaught_handler()
+ {
+ $self = $this;
+
+ $this
+ ->given(
+ $this->function->set_exception_handler = function ($handler) use ($self, &$called) {
+ $called = true;
+
+ $self
+ ->object($handler)
+ ->isInstanceOf('Closure')
+ ->let($reflection = new \ReflectionObject($handler))
+ ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters())
+ ->hasSize(1)
+ ->string($invokeParameters[0]->getName())
+ ->isEqualTo('exception');
+
+ return null;
+ }
+ )
+ ->when($result = SUT::enableUncaughtHandler())
+ ->then
+ ->variable($result)
+ ->isNull()
+ ->boolean($called)
+ ->isTrue();
+ }
+}
--- /dev/null
+{
+ "name" : "hoa/exception",
+ "description": "The Hoa\\Exception library.",
+ "type" : "library",
+ "keywords" : ["library", "exception"],
+ "homepage" : "https://hoa-project.net/",
+ "license" : "BSD-3-Clause",
+ "authors" : [
+ {
+ "name" : "Ivan Enderlin",
+ "email": "ivan.enderlin@hoa-project.net"
+ },
+ {
+ "name" : "Hoa community",
+ "homepage": "https://hoa-project.net/"
+ }
+ ],
+ "support": {
+ "email" : "support@hoa-project.net",
+ "irc" : "irc://chat.freenode.net/hoaproject",
+ "forum" : "https://users.hoa-project.net/",
+ "docs" : "https://central.hoa-project.net/Documentation/Library/Exception",
+ "source": "https://central.hoa-project.net/Resource/Library/Exception"
+ },
+ "require": {
+ "hoa/consistency": "~1.0",
+ "hoa/event" : "~1.0"
+ },
+ "require-dev": {
+ "hoa/test": "~2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Hoa\\Exception\\": "."
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ }
+}
--- /dev/null
+The MIT License (MIT)
+
+Copyright (c) 2015 Paragon Initiative Enterprises
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
--- /dev/null
+{
+ "name": "paragonie/random_compat",
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "random",
+ "polyfill",
+ "pseudorandom"
+ ],
+ "license": "MIT",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "email": "info@paragonie.com",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "autoload": {
+ "files": [
+ "lib/random.php"
+ ]
+ }
+}
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
+pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
+-----END PUBLIC KEY-----
--- /dev/null
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.22 (MingW32)
+
+iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
+QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
+1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
+NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
+NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
+JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
+=B6+8
+-----END PGP SIGNATURE-----
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('RandomCompat_strlen')) {
+ if (
+ defined('MB_OVERLOAD_STRING')
+ &&
+ ((int) ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING
+ ) {
+ /**
+ * strlen() implementation that isn't brittle to mbstring.func_overload
+ *
+ * This version uses mb_strlen() in '8bit' mode to treat strings as raw
+ * binary rather than UTF-8, ISO-8859-1, etc
+ *
+ * @param string $binary_string
+ *
+ * @throws TypeError
+ *
+ * @return int
+ */
+ function RandomCompat_strlen($binary_string)
+ {
+ if (!is_string($binary_string)) {
+ throw new TypeError(
+ 'RandomCompat_strlen() expects a string'
+ );
+ }
+
+ return (int) mb_strlen($binary_string, '8bit');
+ }
+
+ } else {
+ /**
+ * strlen() implementation that isn't brittle to mbstring.func_overload
+ *
+ * This version just used the default strlen()
+ *
+ * @param string $binary_string
+ *
+ * @throws TypeError
+ *
+ * @return int
+ */
+ function RandomCompat_strlen($binary_string)
+ {
+ if (!is_string($binary_string)) {
+ throw new TypeError(
+ 'RandomCompat_strlen() expects a string'
+ );
+ }
+ return (int) strlen($binary_string);
+ }
+ }
+}
+
+if (!is_callable('RandomCompat_substr')) {
+
+ if (
+ defined('MB_OVERLOAD_STRING')
+ &&
+ ((int) ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING
+ ) {
+ /**
+ * substr() implementation that isn't brittle to mbstring.func_overload
+ *
+ * This version uses mb_substr() in '8bit' mode to treat strings as raw
+ * binary rather than UTF-8, ISO-8859-1, etc
+ *
+ * @param string $binary_string
+ * @param int $start
+ * @param int|null $length (optional)
+ *
+ * @throws TypeError
+ *
+ * @return string
+ */
+ function RandomCompat_substr($binary_string, $start, $length = null)
+ {
+ if (!is_string($binary_string)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): First argument should be a string'
+ );
+ }
+
+ if (!is_int($start)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Second argument should be an integer'
+ );
+ }
+
+ if ($length === null) {
+ /**
+ * mb_substr($str, 0, NULL, '8bit') returns an empty string on
+ * PHP 5.3, so we have to find the length ourselves.
+ */
+ /** @var int $length */
+ $length = RandomCompat_strlen($binary_string) - $start;
+ } elseif (!is_int($length)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Third argument should be an integer, or omitted'
+ );
+ }
+
+ // Consistency with PHP's behavior
+ if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
+ return '';
+ }
+ if ($start > RandomCompat_strlen($binary_string)) {
+ return '';
+ }
+
+ return (string) mb_substr(
+ (string) $binary_string,
+ (int) $start,
+ (int) $length,
+ '8bit'
+ );
+ }
+
+ } else {
+
+ /**
+ * substr() implementation that isn't brittle to mbstring.func_overload
+ *
+ * This version just uses the default substr()
+ *
+ * @param string $binary_string
+ * @param int $start
+ * @param int|null $length (optional)
+ *
+ * @throws TypeError
+ *
+ * @return string
+ */
+ function RandomCompat_substr($binary_string, $start, $length = null)
+ {
+ if (!is_string($binary_string)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): First argument should be a string'
+ );
+ }
+
+ if (!is_int($start)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Second argument should be an integer'
+ );
+ }
+
+ if ($length !== null) {
+ if (!is_int($length)) {
+ throw new TypeError(
+ 'RandomCompat_substr(): Third argument should be an integer, or omitted'
+ );
+ }
+
+ return (string) substr(
+ (string )$binary_string,
+ (int) $start,
+ (int) $length
+ );
+ }
+
+ return (string) substr(
+ (string) $binary_string,
+ (int) $start
+ );
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('RandomCompat_intval')) {
+
+ /**
+ * Cast to an integer if we can, safely.
+ *
+ * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
+ * (non-inclusive), it will sanely cast it to an int. If you it's equal to
+ * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
+ * lose precision, so the <= and => operators might accidentally let a float
+ * through.
+ *
+ * @param int|float $number The number we want to convert to an int
+ * @param bool $fail_open Set to true to not throw an exception
+ *
+ * @return float|int
+ * @psalm-suppress InvalidReturnType
+ *
+ * @throws TypeError
+ */
+ function RandomCompat_intval($number, $fail_open = false)
+ {
+ if (is_int($number) || is_float($number)) {
+ $number += 0;
+ } elseif (is_numeric($number)) {
+ /** @psalm-suppress InvalidOperand */
+ $number += 0;
+ }
+ /** @var int|float $number */
+
+ if (
+ is_float($number)
+ &&
+ $number > ~PHP_INT_MAX
+ &&
+ $number < PHP_INT_MAX
+ ) {
+ $number = (int) $number;
+ }
+
+ if (is_int($number)) {
+ return (int) $number;
+ } elseif (!$fail_open) {
+ throw new TypeError(
+ 'Expected an integer.'
+ );
+ }
+ return $number;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!class_exists('Error', false)) {
+ // We can't really avoid making this extend Exception in PHP 5.
+ class Error extends Exception
+ {
+
+ }
+}
+
+if (!class_exists('TypeError', false)) {
+ if (is_subclass_of('Error', 'Exception')) {
+ class TypeError extends Error
+ {
+
+ }
+ } else {
+ class TypeError extends Exception
+ {
+
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * @version 2.0.17
+ * @released 2018-07-04
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!defined('PHP_VERSION_ID')) {
+ // This constant was introduced in PHP 5.2.7
+ $RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
+ define(
+ 'PHP_VERSION_ID',
+ $RandomCompatversion[0] * 10000
+ + $RandomCompatversion[1] * 100
+ + $RandomCompatversion[2]
+ );
+ $RandomCompatversion = null;
+}
+
+/**
+ * PHP 7.0.0 and newer have these functions natively.
+ */
+if (PHP_VERSION_ID >= 70000) {
+ return;
+}
+
+if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
+ define('RANDOM_COMPAT_READ_BUFFER', 8);
+}
+
+$RandomCompatDIR = dirname(__FILE__);
+
+require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php';
+require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php';
+require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php';
+
+if (!is_callable('random_bytes')) {
+ /**
+ * PHP 5.2.0 - 5.6.x way to implement random_bytes()
+ *
+ * We use conditional statements here to define the function in accordance
+ * to the operating environment. It's a micro-optimization.
+ *
+ * In order of preference:
+ * 1. Use libsodium if available.
+ * 2. fread() /dev/urandom if available (never on Windows)
+ * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
+ * 4. COM('CAPICOM.Utilities.1')->GetRandom()
+ *
+ * See RATIONALE.md for our reasoning behind this particular order
+ */
+ if (extension_loaded('libsodium')) {
+ // See random_bytes_libsodium.php
+ if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php';
+ } elseif (method_exists('Sodium', 'randombytes_buf')) {
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php';
+ }
+ }
+
+ /**
+ * Reading directly from /dev/urandom:
+ */
+ if (DIRECTORY_SEPARATOR === '/') {
+ // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
+ // way to exclude Windows.
+ $RandomCompatUrandom = true;
+ $RandomCompat_basedir = ini_get('open_basedir');
+
+ if (!empty($RandomCompat_basedir)) {
+ $RandomCompat_open_basedir = explode(
+ PATH_SEPARATOR,
+ strtolower($RandomCompat_basedir)
+ );
+ $RandomCompatUrandom = (array() !== array_intersect(
+ array('/dev', '/dev/', '/dev/urandom'),
+ $RandomCompat_open_basedir
+ ));
+ $RandomCompat_open_basedir = null;
+ }
+
+ if (
+ !is_callable('random_bytes')
+ &&
+ $RandomCompatUrandom
+ &&
+ @is_readable('/dev/urandom')
+ ) {
+ // Error suppression on is_readable() in case of an open_basedir
+ // or safe_mode failure. All we care about is whether or not we
+ // can read it at this point. If the PHP environment is going to
+ // panic over trying to see if the file can be read in the first
+ // place, that is not helpful to us here.
+
+ // See random_bytes_dev_urandom.php
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php';
+ }
+ // Unset variables after use
+ $RandomCompat_basedir = null;
+ } else {
+ $RandomCompatUrandom = false;
+ }
+
+ /**
+ * mcrypt_create_iv()
+ *
+ * We only want to use mcypt_create_iv() if:
+ *
+ * - random_bytes() hasn't already been defined
+ * - the mcrypt extensions is loaded
+ * - One of these two conditions is true:
+ * - We're on Windows (DIRECTORY_SEPARATOR !== '/')
+ * - We're not on Windows and /dev/urandom is readabale
+ * (i.e. we're not in a chroot jail)
+ * - Special case:
+ * - If we're not on Windows, but the PHP version is between
+ * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
+ * hang indefinitely. This is bad.
+ * - If we're on Windows, we want to use PHP >= 5.3.7 or else
+ * we get insufficient entropy errors.
+ */
+ if (
+ !is_callable('random_bytes')
+ &&
+ // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
+ (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
+ &&
+ // Prevent this code from hanging indefinitely on non-Windows;
+ // see https://bugs.php.net/bug.php?id=69833
+ (
+ DIRECTORY_SEPARATOR !== '/' ||
+ (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
+ )
+ &&
+ extension_loaded('mcrypt')
+ ) {
+ // See random_bytes_mcrypt.php
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php';
+ }
+ $RandomCompatUrandom = null;
+
+ /**
+ * This is a Windows-specific fallback, for when the mcrypt extension
+ * isn't loaded.
+ */
+ if (
+ !is_callable('random_bytes')
+ &&
+ extension_loaded('com_dotnet')
+ &&
+ class_exists('COM')
+ ) {
+ $RandomCompat_disabled_classes = preg_split(
+ '#\s*,\s*#',
+ strtolower(ini_get('disable_classes'))
+ );
+
+ if (!in_array('com', $RandomCompat_disabled_classes)) {
+ try {
+ $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
+ if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
+ // See random_bytes_com_dotnet.php
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php';
+ }
+ } catch (com_exception $e) {
+ // Don't try to use it.
+ }
+ }
+ $RandomCompat_disabled_classes = null;
+ $RandomCompatCOMtest = null;
+ }
+
+ /**
+ * throw new Exception
+ */
+ if (!is_callable('random_bytes')) {
+ /**
+ * We don't have any more options, so let's throw an exception right now
+ * and hope the developer won't let it fail silently.
+ *
+ * @param mixed $length
+ * @psalm-suppress InvalidReturnType
+ * @throws Exception
+ * @return string
+ */
+ function random_bytes($length)
+ {
+ unset($length); // Suppress "variable not used" warnings.
+ throw new Exception(
+ 'There is no suitable CSPRNG installed on your system'
+ );
+ return '';
+ }
+ }
+}
+
+if (!is_callable('random_int')) {
+ require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php';
+}
+
+$RandomCompatDIR = null;
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('random_bytes')) {
+ /**
+ * Windows with PHP < 5.3.0 will not have the function
+ * openssl_random_pseudo_bytes() available, so let's use
+ * CAPICOM to work around this deficiency.
+ *
+ * @param int $bytes
+ *
+ * @throws Exception
+ *
+ * @return string
+ */
+ function random_bytes($bytes)
+ {
+ try {
+ /** @var int $bytes */
+ $bytes = RandomCompat_intval($bytes);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_bytes(): $bytes must be an integer'
+ );
+ }
+
+ if ($bytes < 1) {
+ throw new Error(
+ 'Length must be greater than 0'
+ );
+ }
+
+ /** @var string $buf */
+ $buf = '';
+ if (!class_exists('COM')) {
+ throw new Error(
+ 'COM does not exist'
+ );
+ }
+ /** @var COM $util */
+ $util = new COM('CAPICOM.Utilities.1');
+ $execCount = 0;
+
+ /**
+ * Let's not let it loop forever. If we run N times and fail to
+ * get N bytes of random data, then CAPICOM has failed us.
+ */
+ do {
+ $buf .= base64_decode((string) $util->GetRandom($bytes, 0));
+ if (RandomCompat_strlen($buf) >= $bytes) {
+ /**
+ * Return our random entropy buffer here:
+ */
+ return (string) RandomCompat_substr($buf, 0, $bytes);
+ }
+ ++$execCount;
+ } while ($execCount < $bytes);
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
+ define('RANDOM_COMPAT_READ_BUFFER', 8);
+}
+
+if (!is_callable('random_bytes')) {
+ /**
+ * Unless open_basedir is enabled, use /dev/urandom for
+ * random numbers in accordance with best practices
+ *
+ * Why we use /dev/urandom and not /dev/random
+ * @ref https://www.2uo.de/myths-about-urandom
+ * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
+ *
+ * @param int $bytes
+ *
+ * @throws Exception
+ *
+ * @return string
+ */
+ function random_bytes($bytes)
+ {
+ /** @var resource $fp */
+ static $fp = null;
+
+ /**
+ * This block should only be run once
+ */
+ if (empty($fp)) {
+ /**
+ * We don't want to ever read C:\dev\random, only /dev/urandom on
+ * Unix-like operating systems. While we guard against this
+ * condition in random.php, it doesn't hurt to be defensive in depth
+ * here.
+ *
+ * To that end, we only try to open /dev/urandom if we're on a Unix-
+ * like operating system (which means the directory separator is set
+ * to "/" not "\".
+ */
+ if (DIRECTORY_SEPARATOR === '/') {
+ if (!is_readable('/dev/urandom')) {
+ throw new Exception(
+ 'Environment misconfiguration: ' .
+ '/dev/urandom cannot be read.'
+ );
+ }
+ /**
+ * We use /dev/urandom if it is a char device.
+ * We never fall back to /dev/random
+ */
+ /** @var resource|bool $fp */
+ $fp = fopen('/dev/urandom', 'rb');
+ if (is_resource($fp)) {
+ /** @var array<string, int> $st */
+ $st = fstat($fp);
+ if (($st['mode'] & 0170000) !== 020000) {
+ fclose($fp);
+ $fp = false;
+ }
+ }
+ }
+
+ if (is_resource($fp)) {
+ /**
+ * stream_set_read_buffer() does not exist in HHVM
+ *
+ * If we don't set the stream's read buffer to 0, PHP will
+ * internally buffer 8192 bytes, which can waste entropy
+ *
+ * stream_set_read_buffer returns 0 on success
+ */
+ if (is_callable('stream_set_read_buffer')) {
+ stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
+ }
+ if (is_callable('stream_set_chunk_size')) {
+ stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
+ }
+ }
+ }
+
+ try {
+ /** @var int $bytes */
+ $bytes = RandomCompat_intval($bytes);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_bytes(): $bytes must be an integer'
+ );
+ }
+
+ if ($bytes < 1) {
+ throw new Error(
+ 'Length must be greater than 0'
+ );
+ }
+
+ /**
+ * This if() block only runs if we managed to open a file handle
+ *
+ * It does not belong in an else {} block, because the above
+ * if (empty($fp)) line is logic that should only be run once per
+ * page load.
+ */
+ if (is_resource($fp)) {
+ /**
+ * @var int
+ */
+ $remaining = $bytes;
+
+ /**
+ * @var string|bool
+ */
+ $buf = '';
+
+ /**
+ * We use fread() in a loop to protect against partial reads
+ */
+ do {
+ /**
+ * @var string|bool
+ */
+ $read = fread($fp, $remaining);
+ if (!is_string($read)) {
+ /**
+ * We cannot safely read from the file. Exit the
+ * do-while loop and trigger the exception condition
+ *
+ * @var string|bool
+ */
+ $buf = false;
+ break;
+ }
+ /**
+ * Decrease the number of bytes returned from remaining
+ */
+ $remaining -= RandomCompat_strlen($read);
+ /**
+ * @var string $buf
+ */
+ $buf .= $read;
+ } while ($remaining > 0);
+
+ /**
+ * Is our result valid?
+ * @var string|bool $buf
+ */
+ if (is_string($buf)) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ /**
+ * Return our random entropy buffer here:
+ */
+ return $buf;
+ }
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Error reading from source device'
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('random_bytes')) {
+ /**
+ * If the libsodium PHP extension is loaded, we'll use it above any other
+ * solution.
+ *
+ * libsodium-php project:
+ * @ref https://github.com/jedisct1/libsodium-php
+ *
+ * @param int $bytes
+ *
+ * @throws Exception
+ *
+ * @return string
+ */
+ function random_bytes($bytes)
+ {
+ try {
+ /** @var int $bytes */
+ $bytes = RandomCompat_intval($bytes);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_bytes(): $bytes must be an integer'
+ );
+ }
+
+ if ($bytes < 1) {
+ throw new Error(
+ 'Length must be greater than 0'
+ );
+ }
+
+ /**
+ * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
+ * generated in one invocation.
+ */
+ /** @var string|bool $buf */
+ if ($bytes > 2147483647) {
+ $buf = '';
+ for ($i = 0; $i < $bytes; $i += 1073741824) {
+ $n = ($bytes - $i) > 1073741824
+ ? 1073741824
+ : $bytes - $i;
+ $buf .= \Sodium\randombytes_buf($n);
+ }
+ } else {
+ /** @var string|bool $buf */
+ $buf = \Sodium\randombytes_buf($bytes);
+ }
+
+ if (is_string($buf)) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ return $buf;
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('random_bytes')) {
+ /**
+ * If the libsodium PHP extension is loaded, we'll use it above any other
+ * solution.
+ *
+ * libsodium-php project:
+ * @ref https://github.com/jedisct1/libsodium-php
+ *
+ * @param int $bytes
+ *
+ * @throws Exception
+ *
+ * @return string
+ */
+ function random_bytes($bytes)
+ {
+ try {
+ /** @var int $bytes */
+ $bytes = RandomCompat_intval($bytes);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_bytes(): $bytes must be an integer'
+ );
+ }
+
+ if ($bytes < 1) {
+ throw new Error(
+ 'Length must be greater than 0'
+ );
+ }
+
+ /**
+ * @var string
+ */
+ $buf = '';
+
+ /**
+ * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
+ * generated in one invocation.
+ */
+ if ($bytes > 2147483647) {
+ for ($i = 0; $i < $bytes; $i += 1073741824) {
+ $n = ($bytes - $i) > 1073741824
+ ? 1073741824
+ : $bytes - $i;
+ $buf .= Sodium::randombytes_buf((int) $n);
+ }
+ } else {
+ $buf .= Sodium::randombytes_buf((int) $bytes);
+ }
+
+ if (is_string($buf)) {
+ if (RandomCompat_strlen($buf) === $bytes) {
+ return $buf;
+ }
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+if (!is_callable('random_bytes')) {
+ /**
+ * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
+ *
+ * @ref https://bugs.php.net/bug.php?id=55169
+ * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
+ *
+ * @param int $bytes
+ *
+ * @throws Exception
+ *
+ * @return string
+ */
+ function random_bytes($bytes)
+ {
+ try {
+ /** @var int $bytes */
+ $bytes = RandomCompat_intval($bytes);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_bytes(): $bytes must be an integer'
+ );
+ }
+
+ if ($bytes < 1) {
+ throw new Error(
+ 'Length must be greater than 0'
+ );
+ }
+
+ /** @var string|bool $buf */
+ $buf = @mcrypt_create_iv((int) $bytes, (int) MCRYPT_DEV_URANDOM);
+ if (
+ is_string($buf)
+ &&
+ RandomCompat_strlen($buf) === $bytes
+ ) {
+ /**
+ * Return our random entropy buffer here:
+ */
+ return $buf;
+ }
+
+ /**
+ * If we reach here, PHP has failed us.
+ */
+ throw new Exception(
+ 'Could not gather sufficient random data'
+ );
+ }
+}
--- /dev/null
+<?php
+
+if (!is_callable('random_int')) {
+ /**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+ /**
+ * Fetch a random integer between $min and $max inclusive
+ *
+ * @param int $min
+ * @param int $max
+ *
+ * @throws Exception
+ *
+ * @return int
+ */
+ function random_int($min, $max)
+ {
+ /**
+ * Type and input logic checks
+ *
+ * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
+ * (non-inclusive), it will sanely cast it to an int. If you it's equal to
+ * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
+ * lose precision, so the <= and => operators might accidentally let a float
+ * through.
+ */
+
+ try {
+ /** @var int $min */
+ $min = RandomCompat_intval($min);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_int(): $min must be an integer'
+ );
+ }
+
+ try {
+ /** @var int $max */
+ $max = RandomCompat_intval($max);
+ } catch (TypeError $ex) {
+ throw new TypeError(
+ 'random_int(): $max must be an integer'
+ );
+ }
+
+ /**
+ * Now that we've verified our weak typing system has given us an integer,
+ * let's validate the logic then we can move forward with generating random
+ * integers along a given range.
+ */
+ if ($min > $max) {
+ throw new Error(
+ 'Minimum value must be less than or equal to the maximum value'
+ );
+ }
+
+ if ($max === $min) {
+ return (int) $min;
+ }
+
+ /**
+ * Initialize variables to 0
+ *
+ * We want to store:
+ * $bytes => the number of random bytes we need
+ * $mask => an integer bitmask (for use with the &) operator
+ * so we can minimize the number of discards
+ */
+ $attempts = $bits = $bytes = $mask = $valueShift = 0;
+ /** @var int $attempts */
+ /** @var int $bits */
+ /** @var int $bytes */
+ /** @var int $mask */
+ /** @var int $valueShift */
+
+ /**
+ * At this point, $range is a positive number greater than 0. It might
+ * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
+ * a float and we will lose some precision.
+ *
+ * @var int|float $range
+ */
+ $range = $max - $min;
+
+ /**
+ * Test for integer overflow:
+ */
+ if (!is_int($range)) {
+
+ /**
+ * Still safely calculate wider ranges.
+ * Provided by @CodesInChaos, @oittaa
+ *
+ * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
+ *
+ * We use ~0 as a mask in this case because it generates all 1s
+ *
+ * @ref https://eval.in/400356 (32-bit)
+ * @ref http://3v4l.org/XX9r5 (64-bit)
+ */
+ $bytes = PHP_INT_SIZE;
+ /** @var int $mask */
+ $mask = ~0;
+
+ } else {
+
+ /**
+ * $bits is effectively ceil(log($range, 2)) without dealing with
+ * type juggling
+ */
+ while ($range > 0) {
+ if ($bits % 8 === 0) {
+ ++$bytes;
+ }
+ ++$bits;
+ $range >>= 1;
+ /** @var int $mask */
+ $mask = $mask << 1 | 1;
+ }
+ $valueShift = $min;
+ }
+
+ /** @var int $val */
+ $val = 0;
+ /**
+ * Now that we have our parameters set up, let's begin generating
+ * random integers until one falls between $min and $max
+ */
+ /** @psalm-suppress RedundantCondition */
+ do {
+ /**
+ * The rejection probability is at most 0.5, so this corresponds
+ * to a failure probability of 2^-128 for a working RNG
+ */
+ if ($attempts > 128) {
+ throw new Exception(
+ 'random_int: RNG is broken - too many rejections'
+ );
+ }
+
+ /**
+ * Let's grab the necessary number of random bytes
+ */
+ $randomByteString = random_bytes($bytes);
+
+ /**
+ * Let's turn $randomByteString into an integer
+ *
+ * This uses bitwise operators (<< and |) to build an integer
+ * out of the values extracted from ord()
+ *
+ * Example: [9F] | [6D] | [32] | [0C] =>
+ * 159 + 27904 + 3276800 + 201326592 =>
+ * 204631455
+ */
+ $val &= 0;
+ for ($i = 0; $i < $bytes; ++$i) {
+ $val |= ord($randomByteString[$i]) << ($i * 8);
+ }
+ /** @var int $val */
+
+ /**
+ * Apply mask
+ */
+ $val &= $mask;
+ $val += $valueShift;
+
+ ++$attempts;
+ /**
+ * If $val overflows to a floating point number,
+ * ... or is larger than $max,
+ * ... or smaller than $min,
+ * then try again.
+ */
+ } while (!is_int($val) || $val > $max || $val < $min);
+
+ return (int) $val;
+ }
+}
--- /dev/null
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "422b05a6ce122760976256ff21e9381b",
+ "content-hash": "3bd75e9c1741d7c0c0930855e5b96abb",
+ "packages": [
+ {
+ "name": "paragonie/constant_time_encoding",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/constant_time_encoding.git",
+ "reference": "fdb1e311153233315e0f7699711e3845d81ed00f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/fdb1e311153233315e0f7699711e3845d81ed00f",
+ "reference": "fdb1e311153233315e0f7699711e3845d81ed00f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3|^7"
+ },
+ "require-dev": {
+ "paragonie/random_compat": "^1.4|^2.0",
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ParagonIE\\ConstantTime\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com",
+ "role": "Maintainer"
+ },
+ {
+ "name": "Steve 'Sc00bz' Thomas",
+ "email": "steve@tobtu.com",
+ "homepage": "https://www.tobtu.com",
+ "role": "Original Developer"
+ }
+ ],
+ "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+ "keywords": [
+ "base16",
+ "base32",
+ "base32_decode",
+ "base32_encode",
+ "base64",
+ "base64_decode",
+ "base64_encode",
+ "bin2hex",
+ "encoding",
+ "hex",
+ "hex2bin",
+ "rfc4648"
+ ],
+ "time": "2016-04-08 16:58:39"
+ },
+ {
+ "name": "paragonie/random_compat",
+ "version": "v2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf",
+ "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/random.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "pseudorandom",
+ "random"
+ ],
+ "time": "2016-04-03 06:00:07"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2015-06-14 21:17:01"
+ },
+ {
+ "name": "michelf/php-markdown",
+ "version": "1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/michelf/php-markdown.git",
+ "reference": "156e56ee036505ec637d761ee62dc425d807183c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/michelf/php-markdown/zipball/156e56ee036505ec637d761ee62dc425d807183c",
+ "reference": "156e56ee036505ec637d761ee62dc425d807183c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-lib": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Michelf": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Michel Fortin",
+ "email": "michel.fortin@michelf.ca",
+ "homepage": "https://michelf.ca/",
+ "role": "Developer"
+ },
+ {
+ "name": "John Gruber",
+ "homepage": "https://daringfireball.net/"
+ }
+ ],
+ "description": "PHP Markdown",
+ "homepage": "https://michelf.ca/projects/php-markdown/",
+ "keywords": [
+ "markdown"
+ ],
+ "time": "2015-12-24 01:37:31"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v0.9.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb",
+ "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "PHPParser": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "time": "2014-07-23 18:24:17"
+ },
+ {
+ "name": "phing/phing",
+ "version": "2.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phingofficial/phing.git",
+ "reference": "7dd73c83c377623def54b58121f46b4dcb35dd61"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phingofficial/phing/zipball/7dd73c83c377623def54b58121f46b4dcb35dd61",
+ "reference": "7dd73c83c377623def54b58121f46b4dcb35dd61",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "lastcraft/simpletest": "@dev",
+ "mikey179/vfsstream": "^1.6",
+ "pdepend/pdepend": "2.x",
+ "pear/archive_tar": "1.4.x",
+ "pear/http_request2": "dev-trunk",
+ "pear/net_growl": "dev-trunk",
+ "pear/pear-core-minimal": "1.10.1",
+ "pear/versioncontrol_git": "@dev",
+ "pear/versioncontrol_svn": "~0.5",
+ "phpdocumentor/phpdocumentor": "2.x",
+ "phploc/phploc": "~2.0.6",
+ "phpmd/phpmd": "~2.2",
+ "phpunit/phpunit": ">=3.7",
+ "sebastian/git": "~1.0",
+ "sebastian/phpcpd": "2.x",
+ "squizlabs/php_codesniffer": "~2.2",
+ "symfony/yaml": "~2.7"
+ },
+ "suggest": {
+ "pdepend/pdepend": "PHP version of JDepend",
+ "pear/archive_tar": "Tar file management class",
+ "pear/versioncontrol_git": "A library that provides OO interface to handle Git repository",
+ "pear/versioncontrol_svn": "A simple OO-style interface for Subversion, the free/open-source version control system",
+ "phpdocumentor/phpdocumentor": "Documentation Generator for PHP",
+ "phploc/phploc": "A tool for quickly measuring the size of a PHP project",
+ "phpmd/phpmd": "PHP version of PMD tool",
+ "phpunit/php-code-coverage": "Library that provides collection, processing, and rendering functionality for PHP code coverage information",
+ "phpunit/phpunit": "The PHP Unit Testing Framework",
+ "sebastian/phpcpd": "Copy/Paste Detector (CPD) for PHP code",
+ "tedivm/jshrink": "Javascript Minifier built in PHP"
+ },
+ "bin": [
+ "bin/phing"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.14.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "classes/phing/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "include-path": [
+ "classes"
+ ],
+ "license": [
+ "LGPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Michiel Rook",
+ "email": "mrook@php.net"
+ },
+ {
+ "name": "Phing Community",
+ "homepage": "https://www.phing.info/trac/wiki/Development/Contributors"
+ }
+ ],
+ "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.",
+ "homepage": "https://www.phing.info/",
+ "keywords": [
+ "build",
+ "phing",
+ "task",
+ "tool"
+ ],
+ "time": "2016-03-10 21:39:23"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "dflydev/markdown": "~1.0",
+ "erusev/parsedown": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "phpDocumentor": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "mike.vanriel@naenius.com"
+ }
+ ],
+ "time": "2015-02-03 12:10:50"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "~2.0",
+ "sebastian/comparator": "~1.1",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2016-02-15 07:46:21"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "2.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "~1.3",
+ "sebastian/environment": "^1.3.2",
+ "sebastian/version": "~1.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "~4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.2.1",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-10-06 15:47:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2015-06-21 13:08:43"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21 13:50:34"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2016-05-12 18:03:57"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2015-09-15 10:49:45"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "4.8.26",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=5.3.3",
+ "phpspec/prophecy": "^1.3.1",
+ "phpunit/php-code-coverage": "~2.1",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "^1.0.6",
+ "phpunit/phpunit-mock-objects": "~2.3",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "~1.3",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/version": "~1.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.8.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-05-17 03:09:28"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "2.3.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": ">=5.3.3",
+ "phpunit/php-text-template": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2015-10-02 06:51:40"
+ },
+ {
+ "name": "pimple/pimple",
+ "version": "v2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/silexphp/Pimple.git",
+ "reference": "ea22fb2880faf7b7b0e17c9809c6fe25b071fd76"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/silexphp/Pimple/zipball/ea22fb2880faf7b7b0e17c9809c6fe25b071fd76",
+ "reference": "ea22fb2880faf7b7b0e17c9809c6fe25b071fd76",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Pimple": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
+ "homepage": "http://pimple.sensiolabs.org",
+ "keywords": [
+ "container",
+ "dependency injection"
+ ],
+ "time": "2014-07-24 07:10:08"
+ },
+ {
+ "name": "sami/sami",
+ "version": "v2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FriendsOfPHP/Sami.git",
+ "reference": "fa58b324f41aa2aefe21dac4f22d8c98965fc012"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/fa58b324f41aa2aefe21dac4f22d8c98965fc012",
+ "reference": "fa58b324f41aa2aefe21dac4f22d8c98965fc012",
+ "shasum": ""
+ },
+ "require": {
+ "michelf/php-markdown": "~1.3",
+ "nikic/php-parser": "0.9.*",
+ "php": ">=5.3.0",
+ "pimple/pimple": "2.*",
+ "symfony/console": "~2.1",
+ "symfony/filesystem": "~2.1",
+ "symfony/finder": "~2.1",
+ "symfony/process": "~2.1",
+ "symfony/yaml": "~2.1",
+ "twig/twig": "1.*"
+ },
+ "bin": [
+ "sami.php"
+ ],
+ "type": "application",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Sami": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Sami, an API documentation generator",
+ "homepage": "http://sami.sensiolabs.org",
+ "keywords": [
+ "phpdoc"
+ ],
+ "time": "2014-06-25 12:05:18"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2015-07-26 15:48:44"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-12-08 07:14:41"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "1.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2016-05-17 03:18:57"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+ "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2015-06-21 07:55:53"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2015-10-12 03:26:01"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-11-11 19:50:13"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2015-06-21 13:59:46"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "2.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1bcdf03b068a530ac1962ce671dead356eeba43b",
+ "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "bin": [
+ "scripts/phpcs",
+ "scripts/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "CodeSniffer.php",
+ "CodeSniffer/CLI.php",
+ "CodeSniffer/Exception.php",
+ "CodeSniffer/File.php",
+ "CodeSniffer/Fixer.php",
+ "CodeSniffer/Report.php",
+ "CodeSniffer/Reporting.php",
+ "CodeSniffer/Sniff.php",
+ "CodeSniffer/Tokens.php",
+ "CodeSniffer/Reports/",
+ "CodeSniffer/Tokenizers/",
+ "CodeSniffer/DocGenerators/",
+ "CodeSniffer/Standards/AbstractPatternSniff.php",
+ "CodeSniffer/Standards/AbstractScopeSniff.php",
+ "CodeSniffer/Standards/AbstractVariableSniff.php",
+ "CodeSniffer/Standards/IncorrectPatternException.php",
+ "CodeSniffer/Standards/Generic/Sniffs/",
+ "CodeSniffer/Standards/MySource/Sniffs/",
+ "CodeSniffer/Standards/PEAR/Sniffs/",
+ "CodeSniffer/Standards/PSR1/Sniffs/",
+ "CodeSniffer/Standards/PSR2/Sniffs/",
+ "CodeSniffer/Standards/Squiz/Sniffs/",
+ "CodeSniffer/Standards/Zend/Sniffs/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "http://www.squizlabs.com/php-codesniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2016-04-03 22:58:34"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v2.8.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/48221d3de4dc22d2cd57c97e8b9361821da86609",
+ "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.1|~3.0.0",
+ "symfony/process": "~2.1|~3.0.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-26 12:00:47"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v2.8.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "dee379131dceed90a429e951546b33edfe7dccbb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/dee379131dceed90a429e951546b33edfe7dccbb",
+ "reference": "dee379131dceed90a429e951546b33edfe7dccbb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-12 18:01:21"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v2.8.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "ca24cf2cd4e3826f571e0067e535758e73807aa1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/ca24cf2cd4e3826f571e0067e535758e73807aa1",
+ "reference": "ca24cf2cd4e3826f571e0067e535758e73807aa1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-10 10:53:53"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-05-18 14:26:46"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v2.8.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "1276bd9be89be039748cf753a2137f4ef149cd74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1276bd9be89be039748cf753a2137f4ef149cd74",
+ "reference": "1276bd9be89be039748cf753a2137f4ef149cd74",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-14 15:22:22"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v2.8.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
+ "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-29 19:00:15"
+ },
+ {
+ "name": "twig/twig",
+ "version": "v1.24.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/Twig.git",
+ "reference": "3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8",
+ "reference": "3e5aa30ebfbafd5951fb1b01e338e1800ce7e0e8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.7"
+ },
+ "require-dev": {
+ "symfony/debug": "~2.7",
+ "symfony/phpunit-bridge": "~2.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.24-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Twig_": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Armin Ronacher",
+ "email": "armin.ronacher@active-4.com",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Twig Team",
+ "homepage": "http://twig.sensiolabs.org/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
+ "homepage": "http://twig.sensiolabs.org",
+ "keywords": [
+ "templating"
+ ],
+ "time": "2016-01-25 21:22:18"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.3.3"
+ },
+ "platform-dev": []
+}
--- /dev/null
+<?php
+/**
+ * Miccrosoft BLOB Formatted RSA Key Handler
+ *
+ * More info:
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx
+ *
+ * PHP version 5
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use phpseclib\Math\BigInteger;
+
+/**
+ * Microsoft BLOB Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class MSBLOB
+{
+ /**#@+
+ * @access private
+ */
+ /**
+ * Public/Private Key Pair
+ */
+ const PRIVATEKEYBLOB = 0x7;
+ /**
+ * Public Key
+ */
+ const PUBLICKEYBLOB = 0x6;
+ /**
+ * Public Key
+ */
+ const PUBLICKEYBLOBEX = 0xA;
+ /**
+ * RSA public key exchange algorithm
+ */
+ const CALG_RSA_KEYX = 0x0000A400;
+ /**
+ * RSA public key exchange algorithm
+ */
+ const CALG_RSA_SIGN = 0x00002400;
+ /**
+ * Public Key
+ */
+ const RSA1 = 0x31415352;
+ /**
+ * Private Key
+ */
+ const RSA2 = 0x32415352;
+ /**#@-*/
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_string($key)) {
+ return false;
+ }
+
+ $key = Base64::decode($key);
+
+ if (!is_string($key) || strlen($key) < 20) {
+ return false;
+ }
+
+ // PUBLICKEYSTRUC publickeystruc
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
+ extract(unpack('atype/aversion/vreserved/Valgo', self::_string_shift($key, 8)));
+ switch (ord($type)) {
+ case self::PUBLICKEYBLOB:
+ case self::PUBLICKEYBLOBEX:
+ $publickey = true;
+ break;
+ case self::PRIVATEKEYBLOB:
+ $publickey = false;
+ break;
+ default:
+ return false;
+ }
+
+ $components = array('isPublicKey' => $publickey);
+
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
+ switch ($algo) {
+ case self::CALG_RSA_KEYX:
+ case self::CALG_RSA_SIGN:
+ break;
+ default:
+ return false;
+ }
+
+ // RSAPUBKEY rsapubkey
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
+ // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
+ extract(unpack('Vmagic/Vbitlen/a4pubexp', self::_string_shift($key, 12)));
+ switch ($magic) {
+ case self::RSA2:
+ $components['isPublicKey'] = false;
+ case self::RSA1:
+ break;
+ default:
+ return false;
+ }
+
+ $baseLength = $bitlen / 16;
+ if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
+ return false;
+ }
+
+ $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
+ // BYTE modulus[rsapubkey.bitlen/8]
+ $components['modulus'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256);
+
+ if ($publickey) {
+ return $components;
+ }
+
+ $components['isPublicKey'] = false;
+
+ // BYTE prime1[rsapubkey.bitlen/16]
+ $components['primes'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
+ // BYTE prime2[rsapubkey.bitlen/16]
+ $components['primes'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256);
+ // BYTE exponent1[rsapubkey.bitlen/16]
+ $components['exponents'] = array(1 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
+ // BYTE exponent2[rsapubkey.bitlen/16]
+ $components['exponents'][] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256);
+ // BYTE coefficient[rsapubkey.bitlen/16]
+ $components['coefficients'] = array(2 => new BigInteger(strrev(self::_string_shift($key, $bitlen / 16)), 256));
+ if (isset($components['privateExponent'])) {
+ $components['publicExponent'] = $components['privateExponent'];
+ }
+ // BYTE privateExponent[rsapubkey.bitlen/8]
+ $components['privateExponent'] = new BigInteger(strrev(self::_string_shift($key, $bitlen / 8)), 256);
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @param \phpseclib\Math\BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
+ {
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key.= pack('VVa*', self::RSA2, 8 * strlen($n), $e);
+ $key.= $n;
+ $key.= strrev($primes[1]->toBytes());
+ $key.= strrev($primes[2]->toBytes());
+ $key.= strrev($exponents[1]->toBytes());
+ $key.= strrev($exponents[2]->toBytes());
+ $key.= strrev($coefficients[1]->toBytes());
+ $key.= strrev($d->toBytes());
+
+ return Base64::encode($key);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key.= pack('VVa*', self::RSA1, 8 * strlen($n), $e);
+ $key.= $n;
+
+ return Base64::encode($key);
+ }
+
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ * @access private
+ */
+ static function _string_shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+}
--- /dev/null
+<?php
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use phpseclib\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class OpenSSH
+{
+ /**
+ * Default comment
+ *
+ * @var string
+ * @access private
+ */
+ static $comment = 'phpseclib-generated-key';
+
+ /**
+ * Sets the default comment
+ *
+ * @access public
+ * @param string $comment
+ */
+ static function setComment($comment)
+ {
+ self::$comment = str_replace(array("\r", "\n"), '', $comment);
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_string($key)) {
+ return false;
+ }
+
+ $parts = explode(' ', $key, 3);
+
+ $key = isset($parts[1]) ? Base64::decode($parts[1]) : Base64::decode($parts[0]);
+ if ($key === false) {
+ return false;
+ }
+
+ $comment = isset($parts[2]) ? $parts[2] : false;
+
+ if (substr($key, 0, 11) != "\0\0\0\7ssh-rsa") {
+ return false;
+ }
+ self::_string_shift($key, 11);
+ if (strlen($key) <= 4) {
+ return false;
+ }
+ extract(unpack('Nlength', self::_string_shift($key, 4)));
+ if (strlen($key) <= $length) {
+ return false;
+ }
+ $publicExponent = new BigInteger(self::_string_shift($key, $length), -256);
+ if (strlen($key) <= 4) {
+ return false;
+ }
+ extract(unpack('Nlength', self::_string_shift($key, 4)));
+ if (strlen($key) != $length) {
+ return false;
+ }
+ $modulus = new BigInteger(self::_string_shift($key, $length), -256);
+
+ return array(
+ 'isPublicKey' => true,
+ 'modulus' => $modulus,
+ 'publicExponent' => $publicExponent,
+ 'comment' => $comment
+ );
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $publicExponent = $e->toBytes(true);
+ $modulus = $n->toBytes(true);
+
+ // from <http://tools.ietf.org/html/rfc4253#page-15>:
+ // string "ssh-rsa"
+ // mpint e
+ // mpint n
+ $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus);
+ $RSAPublicKey = 'ssh-rsa ' . Base64::encode($RSAPublicKey) . ' ' . self::$comment;
+
+ return $RSAPublicKey;
+ }
+
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ * @access private
+ */
+ static function _string_shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+}
--- /dev/null
+<?php
+/**
+ * PKCS Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use ParagonIE\ConstantTime\Hex;
+use phpseclib\Crypt\AES;
+use phpseclib\Crypt\Base;
+use phpseclib\Crypt\DES;
+use phpseclib\Crypt\TripleDES;
+use phpseclib\Math\BigInteger;
+
+/**
+ * PKCS Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+abstract class PKCS
+{
+ /**#@+
+ * @access private
+ * @see \phpseclib\Crypt\RSA::createKey()
+ */
+ /**
+ * ASN1 Integer
+ */
+ const ASN1_INTEGER = 2;
+ /**
+ * ASN1 Bit String
+ */
+ const ASN1_BITSTRING = 3;
+ /**
+ * ASN1 Octet String
+ */
+ const ASN1_OCTETSTRING = 4;
+ /**
+ * ASN1 Object Identifier
+ */
+ const ASN1_OBJECT = 6;
+ /**
+ * ASN1 Sequence (with the constucted bit set)
+ */
+ const ASN1_SEQUENCE = 48;
+ /**#@-*/
+
+ /**#@+
+ * @access private
+ */
+ /**
+ * Auto-detect the format
+ */
+ const MODE_ANY = 0;
+ /**
+ * Require base64-encoded PEM's be supplied
+ */
+ const MODE_PEM = 1;
+ /**
+ * Require raw DER's be supplied
+ */
+ const MODE_DER = 2;
+ /**#@-*/
+
+ /**
+ * Is the key a base-64 encoded PEM, DER or should it be auto-detected?
+ *
+ * @access private
+ * @param int
+ */
+ static $format = self::MODE_ANY;
+
+ /**
+ * Returns the mode constant corresponding to the mode string
+ *
+ * @access public
+ * @param string $mode
+ * @return int
+ * @throws \UnexpectedValueException if the block cipher mode is unsupported
+ */
+ static function getEncryptionMode($mode)
+ {
+ switch ($mode) {
+ case 'CBC':
+ return Base::MODE_CBC;
+ case 'ECB':
+ return Base::MODE_ECB;
+ case 'CFB':
+ return Base::MODE_CFB;
+ case 'OFB':
+ return Base::MODE_OFB;
+ case 'CTR':
+ return Base::MODE_CTR;
+ }
+ throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
+ }
+
+ /**
+ * Returns a cipher object corresponding to a string
+ *
+ * @access public
+ * @param string $algo
+ * @return string
+ * @throws \UnexpectedValueException if the encryption algorithm is unsupported
+ */
+ static function getEncryptionObject($algo)
+ {
+ $modes = '(CBC|ECB|CFB|OFB|CTR)';
+ switch (true) {
+ case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
+ $cipher = new AES(self::getEncryptionMode($matches[2]));
+ $cipher->setKeyLength($matches[1]);
+ return $cipher;
+ case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
+ return new TripleDES(self::getEncryptionMode($matches[1]));
+ case preg_match("#^DES-$modes$#", $algo, $matches):
+ return new DES(self::getEncryptionMode($matches[1]));
+ default:
+ throw new \UnexpectedValueException('Unsupported encryption algorithmn');
+ }
+ }
+
+ /**
+ * Generate a symmetric key for PKCS#1 keys
+ *
+ * @access public
+ * @param string $password
+ * @param string $iv
+ * @param int $length
+ * @return string
+ */
+ static function generateSymmetricKey($password, $iv, $length)
+ {
+ $symkey = '';
+ $iv = substr($iv, 0, 8);
+ while (strlen($symkey) < $length) {
+ $symkey.= md5($symkey . $password . $iv, true);
+ }
+ return substr($symkey, 0, $length);
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_string($key)) {
+ return false;
+ }
+
+ $components = array('isPublicKey' => strpos($key, 'PUBLIC') !== false);
+
+ /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
+ "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
+ protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
+ two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
+
+ http://tools.ietf.org/html/rfc1421#section-4.6.1.1
+ http://tools.ietf.org/html/rfc1421#section-4.6.1.3
+
+ DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
+ DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
+ function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
+ own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
+ implementation are part of the standard, as well.
+
+ * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
+ if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
+ $iv = Hex::decode(trim($matches[2]));
+ // remove the Proc-Type / DEK-Info sections as they're no longer needed
+ $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
+ $ciphertext = self::_extractBER($key);
+ if ($ciphertext === false) {
+ $ciphertext = $key;
+ }
+ $crypto = self::getEncryptionObject($matches[1]);
+ $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
+ $crypto->setIV($iv);
+ $key = $crypto->decrypt($ciphertext);
+ if ($key === false) {
+ return false;
+ }
+ } else {
+ if (self::$format != self::MODE_DER) {
+ $decoded = self::_extractBER($key);
+ if ($decoded !== false) {
+ $key = $decoded;
+ } elseif (self::$format == self::MODE_PEM) {
+ return false;
+ }
+ }
+ }
+
+ if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) {
+ return false;
+ }
+ if (self::_decodeLength($key) != strlen($key)) {
+ return false;
+ }
+
+ $tag = ord(self::_string_shift($key));
+ /* intended for keys for which OpenSSL's asn1parse returns the following:
+
+ 0:d=0 hl=4 l= 631 cons: SEQUENCE
+ 4:d=1 hl=2 l= 1 prim: INTEGER :00
+ 7:d=1 hl=2 l= 13 cons: SEQUENCE
+ 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
+ 20:d=2 hl=2 l= 0 prim: NULL
+ 22:d=1 hl=4 l= 609 prim: OCTET STRING
+
+ ie. PKCS8 keys */
+
+ if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") {
+ self::_string_shift($key, 3);
+ $tag = self::ASN1_SEQUENCE;
+ }
+
+ if ($tag == self::ASN1_SEQUENCE) {
+ $temp = self::_string_shift($key, self::_decodeLength($key));
+ if (ord(self::_string_shift($temp)) != self::ASN1_OBJECT) {
+ return false;
+ }
+ $length = self::_decodeLength($temp);
+ switch (self::_string_shift($temp, $length)) {
+ case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
+ break;
+ case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC
+ /*
+ PBEParameter ::= SEQUENCE {
+ salt OCTET STRING (SIZE(8)),
+ iterationCount INTEGER }
+ */
+ if (ord(self::_string_shift($temp)) != self::ASN1_SEQUENCE) {
+ return false;
+ }
+ if (self::_decodeLength($temp) != strlen($temp)) {
+ return false;
+ }
+ self::_string_shift($temp); // assume it's an octet string
+ $salt = self::_string_shift($temp, self::_decodeLength($temp));
+ if (ord(self::_string_shift($temp)) != self::ASN1_INTEGER) {
+ return false;
+ }
+ self::_decodeLength($temp);
+ list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT));
+ self::_string_shift($key); // assume it's an octet string
+ $length = self::_decodeLength($key);
+ if (strlen($key) != $length) {
+ return false;
+ }
+
+ $crypto = new DES(DES::MODE_CBC);
+ $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount);
+ $key = $crypto->decrypt($key);
+ if ($key === false) {
+ return false;
+ }
+ return self::load($key);
+ default:
+ return false;
+ }
+ /* intended for keys for which OpenSSL's asn1parse returns the following:
+
+ 0:d=0 hl=4 l= 290 cons: SEQUENCE
+ 4:d=1 hl=2 l= 13 cons: SEQUENCE
+ 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
+ 17:d=2 hl=2 l= 0 prim: NULL
+ 19:d=1 hl=4 l= 271 prim: BIT STRING */
+ $tag = ord(self::_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
+ self::_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
+ // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of
+ // unused bits in the final subsequent octet. The number shall be in the range zero to seven."
+ // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2)
+ if ($tag == self::ASN1_BITSTRING) {
+ self::_string_shift($key);
+ }
+ if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) {
+ return false;
+ }
+ if (self::_decodeLength($key) != strlen($key)) {
+ return false;
+ }
+ $tag = ord(self::_string_shift($key));
+ }
+ if ($tag != self::ASN1_INTEGER) {
+ return false;
+ }
+
+ $length = self::_decodeLength($key);
+ $temp = self::_string_shift($key, $length);
+ if (strlen($temp) != 1 || ord($temp) > 2) {
+ $components['modulus'] = new BigInteger($temp, 256);
+ self::_string_shift($key); // skip over self::ASN1_INTEGER
+ $length = self::_decodeLength($key);
+ $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(self::_string_shift($key, $length), 256);
+
+ return $components;
+ }
+ if (ord(self::_string_shift($key)) != self::ASN1_INTEGER) {
+ return false;
+ }
+ $length = self::_decodeLength($key);
+ $components['modulus'] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['publicExponent'] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['privateExponent'] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['primes'] = array(1 => new BigInteger(self::_string_shift($key, $length), 256));
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['primes'][] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['exponents'] = array(1 => new BigInteger(self::_string_shift($key, $length), 256));
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['exponents'][] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['coefficients'] = array(2 => new BigInteger(self::_string_shift($key, $length), 256));
+
+ if (!empty($key)) {
+ if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) {
+ return false;
+ }
+ self::_decodeLength($key);
+ while (!empty($key)) {
+ if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) {
+ return false;
+ }
+ self::_decodeLength($key);
+ $key = substr($key, 1);
+ $length = self::_decodeLength($key);
+ $components['primes'][] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['exponents'][] = new BigInteger(self::_string_shift($key, $length), 256);
+ self::_string_shift($key);
+ $length = self::_decodeLength($key);
+ $components['coefficients'][] = new BigInteger(self::_string_shift($key, $length), 256);
+ }
+ }
+
+ return $components;
+ }
+
+ /**
+ * Require base64-encoded PEM's be supplied
+ *
+ * @see self::load()
+ * @access public
+ */
+ static function requirePEM()
+ {
+ self::$format = self::MODE_PEM;
+ }
+
+ /**
+ * Require raw DER's be supplied
+ *
+ * @see self::load()
+ * @access public
+ */
+ static function requireDER()
+ {
+ self::$format = self::MODE_DER;
+ }
+
+ /**
+ * Accept any format and auto detect the format
+ *
+ * This is the default setting
+ *
+ * @see self::load()
+ * @access public
+ */
+ static function requireAny()
+ {
+ self::$format = self::MODE_ANY;
+ }
+
+ /**
+ * DER-decode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @access private
+ * @param string $string
+ * @return int
+ */
+ static function _decodeLength(&$string)
+ {
+ $length = ord(self::_string_shift($string));
+ if ($length & 0x80) { // definite length, long form
+ $length&= 0x7F;
+ $temp = self::_string_shift($string, $length);
+ list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
+ }
+ return $length;
+ }
+
+ /**
+ * DER-encode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @access private
+ * @param int $length
+ * @return string
+ */
+ static function _encodeLength($length)
+ {
+ if ($length <= 0x7F) {
+ return chr($length);
+ }
+
+ $temp = ltrim(pack('N', $length), chr(0));
+ return pack('Ca*', 0x80 | strlen($temp), $temp);
+ }
+
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ * @access private
+ */
+ static function _string_shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+
+ /**
+ * Extract raw BER from Base64 encoding
+ *
+ * @access private
+ * @param string $str
+ * @return string
+ */
+ static function _extractBER($str)
+ {
+ /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
+ * above and beyond the ceritificate.
+ * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
+ *
+ * Bag Attributes
+ * localKeyID: 01 00 00 00
+ * subject=/O=organization/OU=org unit/CN=common name
+ * issuer=/O=organization/CN=common name
+ */
+ $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
+ // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
+ $temp = preg_replace('#-+[^-]+-+#', '', $temp);
+ // remove new lines
+ $temp = str_replace(array("\r", "\n", ' '), '', $temp);
+ $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false;
+ return $temp != false ? $temp : $str;
+ }
+}
--- /dev/null
+<?php
+/**
+ * PKCS#1 Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by File/X509.php
+ *
+ * Has the following header:
+ *
+ * -----BEGIN RSA PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pem format (as specified by -m)
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use ParagonIE\ConstantTime\Hex;
+use phpseclib\Crypt\AES;
+use phpseclib\Crypt\DES;
+use phpseclib\Crypt\Random;
+use phpseclib\Crypt\TripleDES;
+use phpseclib\Math\BigInteger;
+
+/**
+ * PKCS#1 Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class PKCS1 extends PKCS
+{
+ /**
+ * Default encryption algorithm
+ *
+ * @var string
+ * @access private
+ */
+ static $defaultEncryptionAlgorithm = 'DES-EDE3-CBC';
+
+ /**
+ * Sets the default encryption algorithm
+ *
+ * @access public
+ * @param string $algo
+ */
+ static function setEncryptionAlgorithm($algo)
+ {
+ self::$defaultEncryptionAlgorithm = $algo;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @param \phpseclib\Math\BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
+ {
+ $num_primes = count($primes);
+ $raw = array(
+ 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
+ 'modulus' => $n->toBytes(true),
+ 'publicExponent' => $e->toBytes(true),
+ 'privateExponent' => $d->toBytes(true),
+ 'prime1' => $primes[1]->toBytes(true),
+ 'prime2' => $primes[2]->toBytes(true),
+ 'exponent1' => $exponents[1]->toBytes(true),
+ 'exponent2' => $exponents[2]->toBytes(true),
+ 'coefficient' => $coefficients[2]->toBytes(true)
+ );
+
+ $components = array();
+ foreach ($raw as $name => $value) {
+ $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($value)), $value);
+ }
+
+ $RSAPrivateKey = implode('', $components);
+
+ if ($num_primes > 2) {
+ $OtherPrimeInfos = '';
+ for ($i = 3; $i <= $num_primes; $i++) {
+ // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
+ //
+ // OtherPrimeInfo ::= SEQUENCE {
+ // prime INTEGER, -- ri
+ // exponent INTEGER, -- di
+ // coefficient INTEGER -- ti
+ // }
+ $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
+ $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
+ $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
+ $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
+ }
+ $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
+ }
+
+ $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
+
+ if (!empty($password) || is_string($password)) {
+ $cipher = self::getEncryptionObject(self::$defaultEncryptionAlgorithm);
+ $iv = Random::string($cipher->getBlockLength() >> 3);
+ $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
+ $cipher->setIV($iv);
+ $iv = strtoupper(Hex::encode($iv));
+ $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
+ "Proc-Type: 4,ENCRYPTED\r\n" .
+ "DEK-Info: " . self::$defaultEncryptionAlgorithm . ",$iv\r\n" .
+ "\r\n" .
+ chunk_split(Base64::encode($cipher->encrypt($RSAPrivateKey)), 64) .
+ '-----END RSA PRIVATE KEY-----';
+ } else {
+ $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
+ chunk_split(Base64::encode($RSAPrivateKey), 64) .
+ '-----END RSA PRIVATE KEY-----';
+ }
+
+ return $RSAPrivateKey;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $modulus = $n->toBytes(true);
+ $publicExponent = $e->toBytes(true);
+
+ // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>:
+ // RSAPublicKey ::= SEQUENCE {
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER -- e
+ // }
+ $components = array(
+ 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($modulus)), $modulus),
+ 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($publicExponent)), $publicExponent)
+ );
+
+ $RSAPublicKey = pack(
+ 'Ca*a*a*',
+ self::ASN1_SEQUENCE,
+ self::_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
+ $components['modulus'],
+ $components['publicExponent']
+ );
+
+ $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" .
+ chunk_split(Base64::encode($RSAPublicKey), 64) .
+ '-----END RSA PUBLIC KEY-----';
+
+ return $RSAPublicKey;
+ }
+}
--- /dev/null
+<?php
+/**
+ * PKCS#8 Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
+ *
+ * Has the following header:
+ *
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use phpseclib\Crypt\DES;
+use phpseclib\Crypt\Random;
+use phpseclib\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class PKCS8 extends PKCS
+{
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @param \phpseclib\Math\BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
+ {
+ $num_primes = count($primes);
+ $raw = array(
+ 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
+ 'modulus' => $n->toBytes(true),
+ 'publicExponent' => $e->toBytes(true),
+ 'privateExponent' => $d->toBytes(true),
+ 'prime1' => $primes[1]->toBytes(true),
+ 'prime2' => $primes[2]->toBytes(true),
+ 'exponent1' => $exponents[1]->toBytes(true),
+ 'exponent2' => $exponents[2]->toBytes(true),
+ 'coefficient' => $coefficients[2]->toBytes(true)
+ );
+
+ $components = array();
+ foreach ($raw as $name => $value) {
+ $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($value)), $value);
+ }
+
+ $RSAPrivateKey = implode('', $components);
+
+ if ($num_primes > 2) {
+ $OtherPrimeInfos = '';
+ for ($i = 3; $i <= $num_primes; $i++) {
+ // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
+ //
+ // OtherPrimeInfo ::= SEQUENCE {
+ // prime INTEGER, -- ri
+ // exponent INTEGER, -- di
+ // coefficient INTEGER -- ti
+ // }
+ $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
+ $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
+ $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
+ $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
+ }
+ $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
+ }
+
+ $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
+
+ $rsaOID = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00"; // hex version of MA0GCSqGSIb3DQEBAQUA
+ $RSAPrivateKey = pack(
+ 'Ca*a*Ca*a*',
+ self::ASN1_INTEGER,
+ "\01\00",
+ $rsaOID,
+ 4,
+ self::_encodeLength(strlen($RSAPrivateKey)),
+ $RSAPrivateKey
+ );
+ $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
+ if (!empty($password) || is_string($password)) {
+ $salt = Random::string(8);
+ $iterationCount = 2048;
+
+ $crypto = new DES(DES::MODE_CBC);
+ $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount);
+ $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey);
+
+ $parameters = pack(
+ 'Ca*a*Ca*N',
+ self::ASN1_OCTETSTRING,
+ self::_encodeLength(strlen($salt)),
+ $salt,
+ self::ASN1_INTEGER,
+ self::_encodeLength(4),
+ $iterationCount
+ );
+ $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03";
+
+ $encryptionAlgorithm = pack(
+ 'Ca*a*Ca*a*',
+ self::ASN1_OBJECT,
+ self::_encodeLength(strlen($pbeWithMD5AndDES_CBC)),
+ $pbeWithMD5AndDES_CBC,
+ self::ASN1_SEQUENCE,
+ self::_encodeLength(strlen($parameters)),
+ $parameters
+ );
+
+ $RSAPrivateKey = pack(
+ 'Ca*a*Ca*a*',
+ self::ASN1_SEQUENCE,
+ self::_encodeLength(strlen($encryptionAlgorithm)),
+ $encryptionAlgorithm,
+ self::ASN1_OCTETSTRING,
+ self::_encodeLength(strlen($RSAPrivateKey)),
+ $RSAPrivateKey
+ );
+
+ $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, self::_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
+
+ $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
+ chunk_split(Base64::encode($RSAPrivateKey), 64) .
+ '-----END ENCRYPTED PRIVATE KEY-----';
+ } else {
+ $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" .
+ chunk_split(Base64::encode($RSAPrivateKey), 64) .
+ '-----END PRIVATE KEY-----';
+ }
+
+ return $RSAPrivateKey;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $modulus = $n->toBytes(true);
+ $publicExponent = $e->toBytes(true);
+
+ // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>:
+ // RSAPublicKey ::= SEQUENCE {
+ // modulus INTEGER, -- n
+ // publicExponent INTEGER -- e
+ // }
+ $components = array(
+ 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($modulus)), $modulus),
+ 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, self::_encodeLength(strlen($publicExponent)), $publicExponent)
+ );
+
+ $RSAPublicKey = pack(
+ 'Ca*a*a*',
+ self::ASN1_SEQUENCE,
+ self::_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
+ $components['modulus'],
+ $components['publicExponent']
+ );
+
+ // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
+ $rsaOID = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00"; // hex version of MA0GCSqGSIb3DQEBAQUA
+ $RSAPublicKey = chr(0) . $RSAPublicKey;
+ $RSAPublicKey = chr(3) . self::_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;
+
+ $RSAPublicKey = pack(
+ 'Ca*a*',
+ self::ASN1_SEQUENCE,
+ self::_encodeLength(strlen($rsaOID . $RSAPublicKey)),
+ $rsaOID . $RSAPublicKey
+ );
+
+ $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
+ chunk_split(Base64::encode($RSAPublicKey), 64) .
+ '-----END PUBLIC KEY-----';
+
+ return $RSAPublicKey;
+ }
+}
--- /dev/null
+<?php
+/**
+ * PuTTY Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use ParagonIE\ConstantTime\Hex;
+use phpseclib\Crypt\AES;
+use phpseclib\Crypt\Hash;
+use phpseclib\Math\BigInteger;
+
+/**
+ * PuTTY Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class PuTTY
+{
+ /**
+ * Default comment
+ *
+ * @var string
+ * @access private
+ */
+ static $comment = 'phpseclib-generated-key';
+
+ /**
+ * Sets the default comment
+ *
+ * @access public
+ * @param string $comment
+ */
+ static function setComment($comment)
+ {
+ self::$comment = str_replace(array("\r", "\n"), '', $comment);
+ }
+
+ /**
+ * Generate a symmetric key for PuTTY keys
+ *
+ * @access public
+ * @param string $password
+ * @param string $iv
+ * @param int $length
+ * @return string
+ */
+ static function generateSymmetricKey($password, $length)
+ {
+ $symkey = '';
+ $sequence = 0;
+ while (strlen($symkey) < $length) {
+ $temp = pack('Na*', $sequence++, $password);
+ $symkey.= Hex::decode(sha1($temp));
+ }
+ return substr($symkey, 0, $length);
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_string($key)) {
+ return false;
+ }
+
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ if (strpos($key, 'BEGIN SSH2 PUBLIC KEY')) {
+ $data = preg_split('#[\r\n]+#', $key);
+ $data = array_splice($data, 2, -1);
+ $data = implode('', $data);
+
+ $components = OpenSSH::load($data);
+ if ($components === false) {
+ return false;
+ }
+
+ if (!preg_match('#Comment: "(.+)"#', $key, $matches)) {
+ return false;
+ }
+ $components['comment'] = str_replace(array('\\\\', '\"'), array('\\', '"'), $matches[1]);
+
+ return $components;
+ }
+
+ $components = array('isPublicKey' => false);
+ $key = preg_split('#\r\n|\r|\n#', $key);
+ $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0]));
+ if ($type != 'ssh-rsa') {
+ return false;
+ }
+ $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
+ $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
+
+ $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
+ $public = Base64::decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
+ $public = substr($public, 11);
+ extract(unpack('Nlength', self::_string_shift($public, 4)));
+ $components['publicExponent'] = new BigInteger(self::_string_shift($public, $length), -256);
+ extract(unpack('Nlength', self::_string_shift($public, 4)));
+ $components['modulus'] = new BigInteger(self::_string_shift($public, $length), -256);
+
+ $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4]));
+ $private = Base64::decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength))));
+
+ switch ($encryption) {
+ case 'aes256-cbc':
+ $symkey = static::generateSymmetricKey($password, 32);
+ $crypto = new AES(AES::MODE_CBC);
+ }
+
+ if ($encryption != 'none') {
+ $crypto->setKey($symkey);
+ $crypto->setIV(str_repeat("\0", $crypto->getBlockLength() >> 3));
+ $crypto->disablePadding();
+ $private = $crypto->decrypt($private);
+ if ($private === false) {
+ return false;
+ }
+ }
+
+ extract(unpack('Nlength', self::_string_shift($private, 4)));
+ if (strlen($private) < $length) {
+ return false;
+ }
+ $components['privateExponent'] = new BigInteger(self::_string_shift($private, $length), -256);
+ extract(unpack('Nlength', self::_string_shift($private, 4)));
+ if (strlen($private) < $length) {
+ return false;
+ }
+ $components['primes'] = array(1 => new BigInteger(self::_string_shift($private, $length), -256));
+ extract(unpack('Nlength', self::_string_shift($private, 4)));
+ if (strlen($private) < $length) {
+ return false;
+ }
+ $components['primes'][] = new BigInteger(self::_string_shift($private, $length), -256);
+
+ $temp = $components['primes'][1]->subtract($one);
+ $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
+ $temp = $components['primes'][2]->subtract($one);
+ $components['exponents'][] = $components['publicExponent']->modInverse($temp);
+
+ extract(unpack('Nlength', self::_string_shift($private, 4)));
+ if (strlen($private) < $length) {
+ return false;
+ }
+ $components['coefficients'] = array(2 => new BigInteger(self::_string_shift($private, $length), -256));
+
+ return $components;
+ }
+
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ * @access private
+ */
+ static function _string_shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @param \phpseclib\Math\BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ return false;
+ }
+
+ $raw = array(
+ 'modulus' => $n->toBytes(true),
+ 'publicExponent' => $e->toBytes(true),
+ 'privateExponent' => $d->toBytes(true),
+ 'prime1' => $primes[1]->toBytes(true),
+ 'prime2' => $primes[2]->toBytes(true),
+ 'exponent1' => $exponents[1]->toBytes(true),
+ 'exponent2' => $exponents[2]->toBytes(true),
+ 'coefficient' => $coefficients[2]->toBytes(true)
+ );
+
+ $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: ";
+ $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
+ $key.= $encryption;
+ $key.= "\r\nComment: " . self::$comment . "\r\n";
+ $public = pack(
+ 'Na*Na*Na*',
+ strlen('ssh-rsa'),
+ 'ssh-rsa',
+ strlen($raw['publicExponent']),
+ $raw['publicExponent'],
+ strlen($raw['modulus']),
+ $raw['modulus']
+ );
+ $source = pack(
+ 'Na*Na*Na*Na*',
+ strlen('ssh-rsa'),
+ 'ssh-rsa',
+ strlen($encryption),
+ $encryption,
+ strlen(self::$comment),
+ self::$comment,
+ strlen($public),
+ $public
+ );
+ $public = Base64::encode($public);
+ $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
+ $key.= chunk_split($public, 64);
+ $private = pack(
+ 'Na*Na*Na*Na*',
+ strlen($raw['privateExponent']),
+ $raw['privateExponent'],
+ strlen($raw['prime1']),
+ $raw['prime1'],
+ strlen($raw['prime2']),
+ $raw['prime2'],
+ strlen($raw['coefficient']),
+ $raw['coefficient']
+ );
+ if (empty($password) && !is_string($password)) {
+ $source.= pack('Na*', strlen($private), $private);
+ $hashkey = 'putty-private-key-file-mac-key';
+ } else {
+ $private.= Random::string(16 - (strlen($private) & 15));
+ $source.= pack('Na*', strlen($private), $private);
+ $crypto = new AES();
+
+ $crypto->setKey(static::generateSymmetricKey($password, 32));
+ $crypto->setIV(str_repeat("\0", $crypto->getBlockLength() >> 3));
+ $crypto->disablePadding();
+ $private = $crypto->encrypt($private);
+ $hashkey = 'putty-private-key-file-mac-key' . $password;
+ }
+
+ $private = Base64::encode($private);
+ $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
+ $key.= chunk_split($private, 64);
+ $hash = new Hash('sha1');
+ $hash->setKey(sha1($hashkey, true));
+ $key.= 'Private-MAC: ' . Hex::encode($hash->hash($source)) . "\r\n";
+
+ return $key;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $n = $n->toBytes(true);
+ $e = $e->toBytes(true);
+
+ $key = pack(
+ 'Na*Na*Na*',
+ strlen('ssh-rsa'),
+ 'ssh-rsa',
+ strlen($e),
+ $e,
+ strlen($n),
+ $n
+ );
+ $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
+ 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\"'), self::$comment) . "\"\r\n";
+ chunk_split(Base64::encode($key), 64) .
+ '---- END SSH2 PUBLIC KEY ----';
+
+ return $key;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Raw RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * An array containing two \phpseclib\Math\BigInteger objects.
+ *
+ * The exponent can be indexed with any of the following:
+ *
+ * 0, e, exponent, publicExponent
+ *
+ * The modulus can be indexed with any of the following:
+ *
+ * 1, n, modulo, modulus
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use phpseclib\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Raw
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_array($key)) {
+ return false;
+ }
+ if (isset($key['isPublicKey']) && isset($key['modulus'])) {
+ if (isset($key['privateExponent']) || isset($key['publicExponent'])) {
+ if (!isset($key['primes'])) {
+ return $key;
+ }
+ if (isset($key['exponents']) && isset($key['coefficients']) && isset($key['publicExponent']) && isset($key['privateExponent'])) {
+ return $key;
+ }
+ }
+ }
+ $components = array('isPublicKey' => true);
+ switch (true) {
+ case isset($key['e']):
+ $components['publicExponent'] = $key['e'];
+ break;
+ case isset($key['exponent']):
+ $components['publicExponent'] = $key['exponent'];
+ break;
+ case isset($key['publicExponent']):
+ $components['publicExponent'] = $key['publicExponent'];
+ break;
+ case isset($key[0]):
+ $components['publicExponent'] = $key[0];
+ }
+ switch (true) {
+ case isset($key['n']):
+ $components['modulus'] = $key['n'];
+ break;
+ case isset($key['modulo']):
+ $components['modulus'] = $key['modulo'];
+ break;
+ case isset($key['modulus']):
+ $components['modulus'] = $key['modulus'];
+ break;
+ case isset($key[1]):
+ $components['modulus'] = $key[1];
+ }
+ return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return array('e' => clone $e, 'n' => clone $n);
+ }
+}
--- /dev/null
+<?php
+/**
+ * XML Formatted RSA Key Handler
+ *
+ * More info:
+ *
+ * http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
+ * http://en.wikipedia.org/wiki/XML_Signature
+ *
+ * PHP version 5
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt\RSA;
+
+use ParagonIE\ConstantTime\Base64;
+use phpseclib\Math\BigInteger;
+
+/**
+ * XML Formatted RSA Key Handler
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class XML
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @access public
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ static function load($key, $password = '')
+ {
+ if (!is_string($key)) {
+ return false;
+ }
+
+ $components = array(
+ 'isPublicKey' => false,
+ 'primes' => array(),
+ 'exponents' => array(),
+ 'coefficients' => array()
+ );
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ if (!$dom->loadXML('<xml>' . $key . '</xml>')) {
+ return false;
+ }
+ $xpath = new \DOMXPath($dom);
+ $keys = array('modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd');
+ foreach ($keys as $key) {
+ // $dom->getElementsByTagName($key) is case-sensitive
+ $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
+ if (!$temp->length) {
+ continue;
+ }
+ $value = new BigInteger(Base64::decode($temp->item(0)->nodeValue), 256);
+ switch ($key) {
+ case 'modulus':
+ $components['modulus'] = $value;
+ break;
+ case 'exponent':
+ $components['publicExponent'] = $value;
+ break;
+ case 'p':
+ $components['primes'][1] = $value;
+ break;
+ case 'q':
+ $components['primes'][2] = $value;
+ break;
+ case 'dp':
+ $components['exponents'][1] = $value;
+ break;
+ case 'dq':
+ $components['exponents'][2] = $value;
+ break;
+ case 'inverseq':
+ $components['coefficients'][2] = $value;
+ break;
+ case 'd':
+ $components['privateExponent'] = $value;
+ }
+ }
+
+ libxml_use_internal_errors($use_errors);
+
+ return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @param \phpseclib\Math\BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ return false;
+ }
+ return "<RSAKeyValue>\r\n" .
+ ' <Modulus>' . Base64::encode($n->toBytes()) . "</Modulus>\r\n" .
+ ' <Exponent>' . Base64::encode($e->toBytes()) . "</Exponent>\r\n" .
+ ' <P>' . Base64::encode($primes[1]->toBytes()) . "</P>\r\n" .
+ ' <Q>' . Base64::encode($primes[2]->toBytes()) . "</Q>\r\n" .
+ ' <DP>' . Base64::encode($exponents[1]->toBytes()) . "</DP>\r\n" .
+ ' <DQ>' . Base64::encode($exponents[2]->toBytes()) . "</DQ>\r\n" .
+ ' <InverseQ>' . Base64::encode($coefficients[2]->toBytes()) . "</InverseQ>\r\n" .
+ ' <D>' . Base64::encode($d->toBytes()) . "</D>\r\n" .
+ '</RSAKeyValue>';
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @access public
+ * @param \phpseclib\Math\BigInteger $n
+ * @param \phpseclib\Math\BigInteger $e
+ * @return string
+ */
+ static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return "<RSAKeyValue>\r\n" .
+ ' <Modulus>' . Base64::encode($n->toBytes()) . "</Modulus>\r\n" .
+ ' <Exponent>' . Base64::encode($e->toBytes()) . "</Exponent>\r\n" .
+ '</RSAKeyValue>';
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * BadConfigurationException
+ *
+ * PHP version 5
+ *
+ * @category Exception
+ * @package BadConfigurationException
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Exception;
+
+/**
+ * BadConfigurationException
+ *
+ * @package BadConfigurationException
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BadConfigurationException extends \RuntimeException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * FileNotFoundException
+ *
+ * PHP version 5
+ *
+ * @category Exception
+ * @package FileNotFoundException
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Exception;
+
+/**
+ * FileNotFoundException
+ *
+ * @package FileNotFoundException
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class FileNotFoundException extends \RuntimeException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * NoSupportedAlgorithmsException
+ *
+ * PHP version 5
+ *
+ * @category Exception
+ * @package NoSupportedAlgorithmsException
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Exception;
+
+/**
+ * NoSupportedAlgorithmsException
+ *
+ * @package NoSupportedAlgorithmsException
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class NoSupportedAlgorithmsException extends \RuntimeException
+{
+}
--- /dev/null
+<?php
+
+/**
+ * UnsupportedAlgorithmException
+ *
+ * PHP version 5
+ *
+ * @category Exception
+ * @package UnsupportedAlgorithmException
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Exception;
+
+/**
+ * UnsupportedAlgorithmException
+ *
+ * @package UnsupportedAlgorithmException
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnsupportedAlgorithmException extends \RuntimeException
+{
+}