[int]
This commit is contained in:
parent
42e6b13597
commit
49f31834e7
189 changed files with 23308 additions and 5 deletions
2
lib/alveolata/.gitignore
vendored
Normal file
2
lib/alveolata/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.geany
|
||||
|
87
lib/alveolata/accesscontrol/functions.php
Normal file
87
lib/alveolata/accesscontrol/functions.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\accesscontrol;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct_subject
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array {map<string,function<tuple<any,any>,bool>>}
|
||||
*/
|
||||
public $getters;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {record<default:list<list<record<type:string,?parameters:any>>>,concrete:map<string,list<list<record<type:string,?parameters:any>>>>>}
|
||||
*/
|
||||
public $acl;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
array $getters,
|
||||
array $acl
|
||||
)
|
||||
{
|
||||
$this->getters = $getters;
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function make(
|
||||
array $getters,
|
||||
array $acl
|
||||
)
|
||||
{
|
||||
return (
|
||||
new struct_subject(
|
||||
$getters,
|
||||
$acl
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function check(
|
||||
struct_subject $subject,
|
||||
string $action,
|
||||
$state = null
|
||||
) : bool
|
||||
{
|
||||
$acl_section = (
|
||||
$subject->acl['concrete'][$action]
|
||||
?? $subject->acl['default']
|
||||
?? []
|
||||
);
|
||||
return \alveolata\list_\some(
|
||||
$acl_section,
|
||||
function (array $acl_section_sub) use ($subject, $state) : bool {
|
||||
return \alveolata\list_\every(
|
||||
$acl_section_sub,
|
||||
function (array $entry) use ($subject, $state) : bool {
|
||||
if (! array_key_exists($entry['type'], $subject->getters)) {
|
||||
throw (new \Exception('unhandled ACL check type: ' . $entry['type']));
|
||||
}
|
||||
else {
|
||||
$getter = $subject->getters[$entry['type']];
|
||||
return $getter($entry['parameters'] ?? null, $state);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
52
lib/alveolata/accesscontrol/test.spec.php
Normal file
52
lib/alveolata/accesscontrol/test.spec.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/accesscontrol/functions.php');
|
||||
|
||||
|
||||
$data = \alveolata\test\get_data();
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'accesscontrol',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'check',
|
||||
'setup' => function (&$environment) use ($data) {
|
||||
$environment['subject'] = \alveolata\accesscontrol\make(
|
||||
[
|
||||
'password' => function ($parameters, $state) {
|
||||
return ($state['password'] === $parameters['value']);
|
||||
},
|
||||
],
|
||||
$data['check']['parameters']['acl']
|
||||
);
|
||||
},
|
||||
'cases' => array_map(
|
||||
function (array $case_raw) : array {
|
||||
return [
|
||||
'name' => $case_raw['name'],
|
||||
'procedure' => function ($assert, &$environment) use ($case_raw) {
|
||||
// execution
|
||||
$resultActual = \alveolata\accesscontrol\check(
|
||||
$environment['subject'],
|
||||
$case_raw['input']['action'],
|
||||
$case_raw['input']['state'],
|
||||
);
|
||||
// assertions
|
||||
$resultExpected = $case_raw['output'];
|
||||
$assert->equal($resultActual, $resultExpected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['check']['cases']
|
||||
),
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
61
lib/alveolata/accesscontrol/testdata.json
Normal file
61
lib/alveolata/accesscontrol/testdata.json
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"check": {
|
||||
"parameters": {
|
||||
"acl": {
|
||||
"default": [
|
||||
],
|
||||
"concrete": {
|
||||
"bar": [
|
||||
],
|
||||
"baz": [
|
||||
[
|
||||
{"type": "password", "parameters": {"value": "correct"}}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "should fall back to default if the action is unknown",
|
||||
"input": {
|
||||
"action": "qux",
|
||||
"state": {}
|
||||
},
|
||||
"output": false
|
||||
},
|
||||
{
|
||||
"name": "should forbid access if the state is bad and no checker is given",
|
||||
"input": {
|
||||
"action": "bar",
|
||||
"state": {"password": "wrong"}
|
||||
},
|
||||
"output": false
|
||||
},
|
||||
{
|
||||
"name": "should forbid access if the state is good but no checker is given",
|
||||
"input": {
|
||||
"action": "bar",
|
||||
"state": {"password": "correct"}
|
||||
},
|
||||
"output": false
|
||||
},
|
||||
{
|
||||
"name": "should forbid access if the state is bad but does not match the checker",
|
||||
"input": {
|
||||
"action": "baz",
|
||||
"state": {"password": "wrong"}
|
||||
},
|
||||
"output": false
|
||||
},
|
||||
{
|
||||
"name": "should grant access if the state is good and matches the checker",
|
||||
"input": {
|
||||
"action": "baz",
|
||||
"state": {"password": "correct"}
|
||||
},
|
||||
"output": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
79
lib/alveolata/algorithm/functions.php
Normal file
79
lib/alveolata/algorithm/functions.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\algorithm;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param array $order {map<§x,list<§x>>}
|
||||
* @return array {list<§x>}
|
||||
* @throw \Exception if not possible
|
||||
*/
|
||||
function topsort(
|
||||
array $order
|
||||
) : array
|
||||
{
|
||||
if (empty($order)) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
foreach ($order as $member => $dependencies) {
|
||||
if (count($dependencies) <= 0) {
|
||||
$order_ = [];
|
||||
foreach ($order as $member_ => $dependencies_) {
|
||||
if ($member === $member_) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
$order_[$member_] = \alveolata\list_\filter/*<§x,bool>*/(
|
||||
$dependencies_,
|
||||
function ($dependency_) use ($member) : bool {
|
||||
return (! ($member === $dependency_));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return array_merge([$member], topsort($order_));
|
||||
}
|
||||
}
|
||||
throw (new \Exception('not sortable'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calculates the levenshtein distance between two strings
|
||||
*/
|
||||
function levenshtein(string $x, string $y) : int
|
||||
{
|
||||
$u = str_split($x);
|
||||
$v = str_split($y);
|
||||
|
||||
// init
|
||||
$matrix = array_map(
|
||||
fn($i) => array_map(fn($j) => null, \alveolata\list_\sequence(count($u)+1)),
|
||||
\alveolata\list_\sequence(count($v)+1)
|
||||
);
|
||||
foreach (\alveolata\list_\sequence(count($u)+1) as $i) {$matrix[$i][0] = $i;}
|
||||
foreach (\alveolata\list_\sequence(count($v)+1) as $j) {$matrix[0][$j] = $j;}
|
||||
|
||||
// feed
|
||||
foreach (\alveolata\list_\sequence(count($u)) as $i) {
|
||||
foreach (\alveolata\list_\sequence(count($v)) as $j) {
|
||||
$matrix[$i+1][$j+1] = min(
|
||||
min(
|
||||
($matrix[$i+1][$j+0] + 1),
|
||||
($matrix[$i+0][$j+1] + 1)
|
||||
),
|
||||
($matrix[$i+0][$j+0] + (($u[$i] === $v[$j]) ? 0 : 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// return
|
||||
return $matrix[count($u)][count($v)];
|
||||
}
|
||||
|
||||
|
||||
?>
|
74
lib/alveolata/algorithm/test.spec.php
Normal file
74
lib/alveolata/algorithm/test.spec.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/algorithm/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'algorithm',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'topsort',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'test',
|
||||
'procedure' => function ($assert) {
|
||||
$order = [
|
||||
'qux' => ['bar','baz'],
|
||||
'bar' => ['foo'],
|
||||
'foo' => [],
|
||||
'baz' => ['foo'],
|
||||
];
|
||||
$result_actual = \alveolata\algorithm\topsort($order);
|
||||
$results_possible = [
|
||||
['foo','bar','baz','qux'],
|
||||
['foo','baz','bar','qux'],
|
||||
];
|
||||
$found = false;
|
||||
foreach ($results_possible as $result_expected) {
|
||||
if ($result_actual === $result_expected) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$assert->equal($found, true);
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'levenshtein',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'test1',
|
||||
'procedure' => function ($assert) {
|
||||
$result = \alveolata\algorithm\levenshtein('', '');
|
||||
$assert->equal($result, 0);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'test2',
|
||||
'procedure' => function ($assert) {
|
||||
$result = \alveolata\algorithm\levenshtein('tier', 'tor');
|
||||
$assert->equal($result, 2);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'test3',
|
||||
'procedure' => function ($assert) {
|
||||
$result = \alveolata\algorithm\levenshtein('tier', 'vier');
|
||||
$assert->equal($result, 1);
|
||||
}
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
448
lib/alveolata/api/functions.php
Normal file
448
lib/alveolata/api/functions.php
Normal file
|
@ -0,0 +1,448 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\api;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/file/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/type/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/markdown/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/api/types.php');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
* @return struct_subject
|
||||
*/
|
||||
function make(
|
||||
string $name = 'API',
|
||||
array $infoparagraphs = []
|
||||
) : struct_subject
|
||||
{
|
||||
return (new struct_subject($name, [], $infoparagraphs));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject $subject
|
||||
* @param string $name
|
||||
* @param array $options {
|
||||
* record<
|
||||
* ?active:function<(null|string),boolean>,
|
||||
* ?title:(null|string),
|
||||
* ?execution:function<(null|string),any,any,any>,
|
||||
* ?restriction:function<(null|string),any,any>,
|
||||
* ?input_type:function<(null|string),any>,
|
||||
* ?output_type:function<(null|string),any>,
|
||||
* ?description:string,
|
||||
* ?custom:any,
|
||||
* >
|
||||
* }
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function register(
|
||||
struct_subject $subject,
|
||||
string $name,
|
||||
array $options = []
|
||||
) : void
|
||||
{
|
||||
$options = array_merge(
|
||||
[
|
||||
'active' => function ($version) {
|
||||
return true;
|
||||
},
|
||||
'execution' => function ($version, $environment, $input) {
|
||||
throw (new \Exception('not implemented'));
|
||||
},
|
||||
'restriction' => function ($version, $environment) {
|
||||
return true;
|
||||
},
|
||||
'input_type' => function ($version) {
|
||||
return ['kind' => 'any'];
|
||||
},
|
||||
'output_type' => function ($version) {
|
||||
return ['kind' => 'any'];
|
||||
},
|
||||
'title' => null,
|
||||
'description' => null,
|
||||
'custom' => null,
|
||||
],
|
||||
$options
|
||||
);
|
||||
if (array_key_exists($name, $subject->actions)) {
|
||||
throw (new exception_action_already_registered($name));
|
||||
}
|
||||
else {
|
||||
$action = new struct_action(
|
||||
$name,
|
||||
$options['active'],
|
||||
$options['execution'],
|
||||
$options['restriction'],
|
||||
fn($version) => \alveolata\type\make($options['input_type']($version)),
|
||||
fn($version) => \alveolata\type\make($options['output_type']($version)),
|
||||
$options['title'],
|
||||
$options['description'],
|
||||
$options['custom']
|
||||
);
|
||||
$subject->actions[$action->name] = $action;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject $subject
|
||||
* @param string $action_name
|
||||
* @param ?array $options {
|
||||
* record<
|
||||
* ?version:(null|string),
|
||||
* ?input:any,
|
||||
* ?environment:map<(null|string),any>,
|
||||
* ?checklevel_restriction:&enum_checklevel,
|
||||
* ?checklevel_input:&enum_checklevel,
|
||||
* ?checklevel_output:&enum_checklevel,
|
||||
* >
|
||||
* }
|
||||
* @return array {record<custom:any,output:any>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function call_with_custom(
|
||||
struct_subject $subject,
|
||||
string $action_name,
|
||||
array $options = []
|
||||
) : array
|
||||
{
|
||||
$options = array_merge(
|
||||
[
|
||||
'version' => null,
|
||||
'input' => null,
|
||||
'environment' => [],
|
||||
'checklevel_restriction' => enum_checklevel::hard,
|
||||
'checklevel_input' => enum_checklevel::soft,
|
||||
'checklevel_output' => enum_checklevel::soft,
|
||||
],
|
||||
$options
|
||||
);
|
||||
if (! array_key_exists($action_name, $subject->actions)) {
|
||||
throw (new exception_action_missing($action_name));
|
||||
}
|
||||
else {
|
||||
$action = $subject->actions[$action_name];
|
||||
if (! ($action->active)($options['version'])) {
|
||||
throw (new exception_action_missing($action_name));
|
||||
}
|
||||
else {
|
||||
switch ($options['checklevel_restriction']) {
|
||||
case enum_checklevel::none: {
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
case enum_checklevel::soft: {
|
||||
if (! ($action->restriction)($options['version'], $options['environment'])) {
|
||||
\alveolata\log\warning(
|
||||
'api_permission_denied',
|
||||
[
|
||||
'version' => $options['version'],
|
||||
'environment' => $options['environment'],
|
||||
'action_name' => $action->name,
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case enum_checklevel::hard: {
|
||||
if (! ($action->restriction)($options['version'], $options['environment'])) {
|
||||
throw (new exception_denied());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch ($options['checklevel_input']) {
|
||||
case enum_checklevel::none: {
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case enum_checklevel::soft: {
|
||||
$input_type = ($action->input_type)($options['version']);
|
||||
$flaws = \alveolata\type\investigate($input_type, $options['input']);
|
||||
if (! empty($flaws)) {
|
||||
\alveolata\log\warning(
|
||||
'api_input_flawed',
|
||||
[
|
||||
'type_expected' => \alveolata\type\to_string($input_type),
|
||||
// 'type_derived' => \alveolata\type\derive($options['input']),
|
||||
'value' => $options['input'],
|
||||
'flaws' => $flaws,
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case enum_checklevel::hard: {
|
||||
$input_type = ($action->input_type)($options['version']);
|
||||
$flaws = \alveolata\type\investigate($input_type, $options['input']);
|
||||
if (! empty($flaws)) {
|
||||
throw (new exception_invalid_input($input_type, $options['input'], $flaws));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$output = ($action->execution)($options['version'], $options['environment'], $options['input']);
|
||||
switch ($options['checklevel_output']) {
|
||||
case enum_checklevel::none: {
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case enum_checklevel::soft: {
|
||||
$output_type = ($action->output_type)($options['version']);
|
||||
$flaws = \alveolata\type\investigate($output_type, $output);
|
||||
if (! empty($flaws)) {
|
||||
\alveolata\log\warning(
|
||||
'api_output_flawed',
|
||||
[
|
||||
'type_expected' => \alveolata\type\to_string($output_type),
|
||||
// 'type_derived' => \alveolata\type\derive($output_type),
|
||||
'value' => $output,
|
||||
'flaws' => $flaws,
|
||||
]
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case enum_checklevel::hard: {
|
||||
$output_type = ($action->output_type)($options['version']);
|
||||
$flaws = \alveolata\type\investigate($output_type, $output);
|
||||
if (! empty($flaws)) {
|
||||
throw (new exception_invalid_output($output_type, $output, $flaws));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'custom' => $action->custom,
|
||||
'output' => $output,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject $subject
|
||||
* @param string $action_name
|
||||
* @param ?array $options {
|
||||
* record<
|
||||
* ?version:(null|string),
|
||||
* ?input:any,
|
||||
* ?environment:map<string,any>,
|
||||
* ?checklevel_restriction:&enum_checklevel,
|
||||
* ?checklevel_input:&enum_checklevel,
|
||||
* ?checklevel_output:&enum_checklevel,
|
||||
* >
|
||||
* }
|
||||
* @return mixed {any}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function call(
|
||||
struct_subject $subject,
|
||||
string $action_name,
|
||||
array $options = []
|
||||
)
|
||||
{
|
||||
$options = array_merge(
|
||||
[
|
||||
'version' => null,
|
||||
'input' => null,
|
||||
'environment' => [],
|
||||
'checklevel_restriction' => enum_checklevel::hard,
|
||||
'checklevel_input' => enum_checklevel::soft,
|
||||
'checklevel_output' => enum_checklevel::soft,
|
||||
],
|
||||
$options
|
||||
);
|
||||
return call_with_custom(
|
||||
$subject,
|
||||
$action_name,
|
||||
[
|
||||
'version' => $options['version'],
|
||||
'input' => $options['input'],
|
||||
'environment' => $options['environment'],
|
||||
'checklevel_restriction' => $options['checklevel_restriction'],
|
||||
'checklevel_input' => $options['checklevel_input'],
|
||||
'checklevel_output' => $options['checklevel_output'],
|
||||
]
|
||||
)['output'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject $subject
|
||||
* @param array $options {
|
||||
* record<
|
||||
* ?version:(null|string),
|
||||
* >
|
||||
* }
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function doc_markdown(
|
||||
struct_subject $subject,
|
||||
array $options = []
|
||||
) : string
|
||||
{
|
||||
$options = array_merge(
|
||||
[
|
||||
'version' => null,
|
||||
],
|
||||
$options
|
||||
);
|
||||
$markdown = '';
|
||||
$markdown .= \alveolata\markdown\headline(1, $subject->name);
|
||||
{
|
||||
{
|
||||
$markdown .= \alveolata\markdown\headline(2, 'Infos');
|
||||
foreach ($subject->infoparagraphs as $infoparagraph) {
|
||||
$markdown .= \alveolata\markdown\paragraph($infoparagraph);
|
||||
}
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
{
|
||||
$markdown .= \alveolata\markdown\headline(2, 'Methods');
|
||||
foreach ($subject->actions as $name => $action) {
|
||||
if (! ($action->active)($options['version'])) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
$markdown .= \alveolata\markdown\headline(3, $action->name);
|
||||
// description
|
||||
{
|
||||
$markdown .= \alveolata\markdown\headline(4, 'Description');
|
||||
$markdown .= \alveolata\markdown\paragraph($action->description);
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
// input type
|
||||
{
|
||||
$markdown .= \alveolata\markdown\headline(4, 'Input Type');
|
||||
$markdown .= \alveolata\markdown\code(\alveolata\type\to_string(($action->input_type)($options['version']), true));
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
// output type
|
||||
{
|
||||
$markdown .= \alveolata\markdown\headline(4, 'Output Type');
|
||||
$markdown .= \alveolata\markdown\code(\alveolata\type\to_string(($action->output_type)($options['version']), true));
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
$markdown .= \alveolata\markdown\rule();
|
||||
}
|
||||
}
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
$markdown .= \alveolata\markdown\sectionend();
|
||||
}
|
||||
return $markdown;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject $subject
|
||||
* @param array $options {
|
||||
* record<
|
||||
* ?version:(null|string),
|
||||
* ?hue:integer,
|
||||
* >
|
||||
* }
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function doc_html(
|
||||
struct_subject $subject,
|
||||
array $options = []
|
||||
) : string
|
||||
{
|
||||
$options = array_merge(
|
||||
[
|
||||
'version' => null,
|
||||
'hue' => 150,
|
||||
],
|
||||
$options,
|
||||
);
|
||||
$expose_type = (fn($type) => htmlentities(
|
||||
str_replace(
|
||||
"\t",
|
||||
' ',
|
||||
\alveolata\type\to_string($type, true)
|
||||
)
|
||||
));
|
||||
$template_stylesheet = \alveolata\file\read(__DIR__ . '/template-stylesheet.css.tpl');
|
||||
$template_doc = \alveolata\file\read(__DIR__ . '/template-doc.html.tpl');
|
||||
$template_infoparagraph = \alveolata\file\read(__DIR__ . '/template-infoparagraph.html.tpl');
|
||||
$template_tocentry = \alveolata\file\read(__DIR__ . '/template-tocentry.html.tpl');
|
||||
$template_action = \alveolata\file\read(__DIR__ . '/template-action.html.tpl');
|
||||
return \alveolata\string\coin(
|
||||
$template_doc,
|
||||
[
|
||||
'style' => \alveolata\string\coin(
|
||||
$template_stylesheet,
|
||||
[
|
||||
'hue' => strval($options['hue']),
|
||||
]
|
||||
),
|
||||
'name' => $subject->name,
|
||||
'version' => ($options['version'] ?? '-'),
|
||||
'infoparagraphs' => \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
$subject->infoparagraphs,
|
||||
fn($infoparagraph) => \alveolata\string\coin(
|
||||
$template_infoparagraph,
|
||||
[
|
||||
'content' => $infoparagraph,
|
||||
]
|
||||
)
|
||||
),
|
||||
"\n"
|
||||
),
|
||||
'tocentries' => \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
$subject->actions,
|
||||
fn($action) => \alveolata\string\coin(
|
||||
$template_tocentry,
|
||||
[
|
||||
'name' => $action->name,
|
||||
'title' => ($action->title ?? $action->name),
|
||||
'href' => sprintf('#%s', $action->name),
|
||||
]
|
||||
)
|
||||
),
|
||||
"\n"
|
||||
),
|
||||
'actions' => \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
\alveolata\list_\filter(
|
||||
$subject->actions,
|
||||
fn($action) => ($action->active)($options['version'])
|
||||
),
|
||||
fn($action) => \alveolata\string\coin(
|
||||
$template_action,
|
||||
[
|
||||
'name' => $action->name,
|
||||
'anchor' => sprintf('%s', $action->name),
|
||||
'href' => sprintf('#%s', $action->name),
|
||||
'description' => $action->description,
|
||||
'input_type' => $expose_type(($action->input_type)($options['version'])),
|
||||
'output_type' => $expose_type(($action->output_type)($options['version'])),
|
||||
]
|
||||
)
|
||||
),
|
||||
"\n"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
23
lib/alveolata/api/template-action.html.tpl
Normal file
23
lib/alveolata/api/template-action.html.tpl
Normal file
|
@ -0,0 +1,23 @@
|
|||
<li class="api_action">
|
||||
<div class="api_action_head">
|
||||
<a class="api_action_head_left" name="{{anchor}}" href="{{href}}">
|
||||
<h2 class="api_action_name">{{name}}</h2>
|
||||
</a>
|
||||
<a class="api_action_head_right" href="#_top">▲</a>
|
||||
</div>
|
||||
<div class="api_action_stuff">
|
||||
<div class="api_action_snippet api_action_description">
|
||||
<div class="api_action_snippet_head">Description</div>
|
||||
<div class="api_action_snippet_body">{{description}}</div>
|
||||
</div>
|
||||
<div class="api_action_snippet api_action_input">
|
||||
<div class="api_action_snippet_head">Input</div>
|
||||
<pre class="api_action_snippet_body">{{input_type}}</pre>
|
||||
</div>
|
||||
<div class="api_action_snippet api_action_output">
|
||||
<div class="api_action_snippet_head">Output</div>
|
||||
<pre class="api_action_snippet_body">{{output_type}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
32
lib/alveolata/api/template-doc.html.tpl
Normal file
32
lib/alveolata/api/template-doc.html.tpl
Normal file
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<style>
|
||||
{{style}}
|
||||
</style>
|
||||
<title>{{name}} — API Documentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<a name="_top"></a>
|
||||
<div class="api">
|
||||
<h1>{{name}} — API Documentation</h1>
|
||||
<div class="api_version">
|
||||
version: <code>{{version}}</code>
|
||||
</div>
|
||||
<div class="api_infoparagraphs">
|
||||
{{infoparagraphs}}
|
||||
</div>
|
||||
<hr/>
|
||||
<ul class="api_toc">
|
||||
{{tocentries}}
|
||||
</ul>
|
||||
<hr/>
|
||||
<ul class="api_actionlist">
|
||||
{{actions}}
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
1
lib/alveolata/api/template-infoparagraph.html.tpl
Normal file
1
lib/alveolata/api/template-infoparagraph.html.tpl
Normal file
|
@ -0,0 +1 @@
|
|||
<p class="api_infos_paragraph">{{content}}</p>
|
107
lib/alveolata/api/template-stylesheet.css.tpl
Normal file
107
lib/alveolata/api/template-stylesheet.css.tpl
Normal file
|
@ -0,0 +1,107 @@
|
|||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: sans;
|
||||
background-color: hsl({{hue}},0%,0%);
|
||||
color: hsl({{hue}},0%,100%);
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 960px;
|
||||
padding: 8px;
|
||||
margin: auto;
|
||||
background-color: hsl({{hue}},0%,5%);
|
||||
color: hsl({{hue}},0%,75%);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: hsl({{hue}},100%,25%);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: hsl({{hue}},100%,50%);
|
||||
}
|
||||
|
||||
.api {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.api_tocentry {
|
||||
list-style-type: "» ";
|
||||
}
|
||||
|
||||
.api_actionlist {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.api_action {
|
||||
margin: 24px;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
background-color: hsl({{hue}},0%,10%);
|
||||
}
|
||||
|
||||
.api_action_head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.api_action_head_left {
|
||||
flex-basis: 90%;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.api_action_head_right {
|
||||
flex-basis: 10%;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.api_action_stuff {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.api_action_snippet {
|
||||
margin: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: hsl({{hue}},0%,15%);
|
||||
}
|
||||
|
||||
.api_action_snippet_head {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
color: hsl({{hue}},0%,10%);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.api_action_description {
|
||||
flex-shrink: 1;
|
||||
flex-basis: 90%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.api_action_input {
|
||||
flex-shrink: 1;
|
||||
flex-basis: 45%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.api_action_output {
|
||||
flex-shrink: 1;
|
||||
flex-basis: 45%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.api_version {
|
||||
text-align: right;
|
||||
}
|
4
lib/alveolata/api/template-tocentry.html.tpl
Normal file
4
lib/alveolata/api/template-tocentry.html.tpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
<li class="api_tocentry">
|
||||
<a href="{{href}}">{{title}} (<code>{{name}}</code>)</a>
|
||||
</li>
|
||||
|
124
lib/alveolata/api/test.spec.php
Normal file
124
lib/alveolata/api/test.spec.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/api/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'api',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'register',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'throw exception on multiple registration for same name',
|
||||
'procedure' => function ($assert) {
|
||||
$subject = \alveolata\api\make();
|
||||
$assert->crashes(
|
||||
function () use (&$subject) {
|
||||
\alveolata\api\register(
|
||||
$subject,
|
||||
'foo'
|
||||
);
|
||||
\alveolata\api\register(
|
||||
$subject,
|
||||
'foo'
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'call',
|
||||
'setup' => function (&$env) {
|
||||
$subject = \alveolata\api\make();
|
||||
\alveolata\api\register(
|
||||
$subject,
|
||||
'increment',
|
||||
[
|
||||
'execution' => function ($version, $environment, $input) {
|
||||
$output = ($input + 1);
|
||||
return $output;
|
||||
},
|
||||
'restriction' => function ($version, $environment) {
|
||||
return true;
|
||||
},
|
||||
'input_type' => function ($version) {
|
||||
return [
|
||||
'kind' => 'integer',
|
||||
];
|
||||
},
|
||||
'output_type' => function ($version) {
|
||||
return [
|
||||
'kind' => 'integer',
|
||||
];
|
||||
},
|
||||
]
|
||||
);
|
||||
$env['subject'] = $subject;
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'throw exception on malformed input',
|
||||
'procedure' => function ($assert, &$env) {
|
||||
// execution & assertions
|
||||
{
|
||||
$input = '42';
|
||||
$assert->crashes(
|
||||
function () use ($env, $input) {
|
||||
$output_actual = \alveolata\api\call(
|
||||
$env['subject'],
|
||||
'increment',
|
||||
[
|
||||
'input' => $input,
|
||||
'environment' => [],
|
||||
'checklevel_restriction' => \alveolata\api\enum_checklevel::hard,
|
||||
'checklevel_input' => \alveolata\api\enum_checklevel::hard,
|
||||
'checklevel_output' => \alveolata\api\enum_checklevel::hard
|
||||
]
|
||||
);
|
||||
},
|
||||
\alveolata\api\exception_invalid_input::class
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'all_good',
|
||||
'procedure' => function ($assert, &$env) {
|
||||
// execution
|
||||
{
|
||||
$input = 42;
|
||||
$output_actual = \alveolata\api\call(
|
||||
$env['subject'],
|
||||
'increment',
|
||||
[
|
||||
'input' => $input,
|
||||
'environment' => [],
|
||||
'checklevel_restriction' => \alveolata\api\enum_checklevel::hard,
|
||||
'checklevel_input' => \alveolata\api\enum_checklevel::hard,
|
||||
'checklevel_output' => \alveolata\api\enum_checklevel::hard,
|
||||
]
|
||||
);
|
||||
}
|
||||
// assertions
|
||||
{
|
||||
$output_expected = 43;
|
||||
$assert->equal($output_actual, $output_expected);
|
||||
}
|
||||
},
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
318
lib/alveolata/api/types.php
Normal file
318
lib/alveolata/api/types.php
Normal file
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\api;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/type/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_action_already_registered extends \Exception {
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
string $action_name
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
\sprintf('action "%s" already registered', $action_name)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_action_missing extends \Exception {
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
string $action_name
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
\sprintf('no action "%s"', $action_name)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_denied extends \Exception {
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
\sprintf('permission denied')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_flawed_value extends \Exception {
|
||||
|
||||
/**
|
||||
* @var \alveolata\type\interface_type
|
||||
*/
|
||||
private $type;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {list<string>}
|
||||
*/
|
||||
private $flaws;
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $input {any}
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
\alveolata\type\interface_type $type_expected,
|
||||
$value,
|
||||
array $flaws
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
\alveolata\string\coin(
|
||||
'malformed {{name}} | {{details}}',
|
||||
[
|
||||
'name' => $name,
|
||||
'details' => \json_encode(
|
||||
[
|
||||
'type_expected' => \alveolata\type\to_string($type_expected),
|
||||
'value' => $value,
|
||||
'flaws' => $flaws,
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
$this->type_expected = $type_expected;
|
||||
$this->value = $value;
|
||||
$this->flaws = $flaws;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {list<string>}
|
||||
*/
|
||||
public function get_flaws(
|
||||
) : array
|
||||
{
|
||||
return $this->flaws;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_invalid_input extends exception_flawed_value {
|
||||
|
||||
/**
|
||||
* @param mixed $input {any}
|
||||
*/
|
||||
public function __construct(
|
||||
\alveolata\type\interface_type $type_expected,
|
||||
$input,
|
||||
array $flaws
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
'input',
|
||||
$type_expected,
|
||||
$input,
|
||||
$flaws
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class exception_invalid_output extends exception_flawed_value {
|
||||
|
||||
/**
|
||||
* @param mixed $output {any}
|
||||
*/
|
||||
public function __construct(
|
||||
\alveolata\type\interface_type $type_expected,
|
||||
$output,
|
||||
array $flaws
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
'output',
|
||||
$type_expected,
|
||||
$output,
|
||||
$flaws
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class enum_checklevel {
|
||||
public const none = 'none';
|
||||
public const soft = 'soft';
|
||||
public const hard = 'hard';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_action {
|
||||
|
||||
/**
|
||||
* @var string supposed to be unique
|
||||
*/
|
||||
public $name;
|
||||
|
||||
|
||||
/**
|
||||
* @var \Closure {function<string,boolean>}
|
||||
*/
|
||||
public $active;
|
||||
|
||||
|
||||
/**
|
||||
* @var \Closure {function<pair<any,any>,any>}
|
||||
*/
|
||||
public $execution;
|
||||
|
||||
|
||||
/**
|
||||
* @var \Closure {function<any,boolean>}
|
||||
*/
|
||||
public $restriction;
|
||||
|
||||
|
||||
/**
|
||||
* @var \Closure {function<string,&\alveolata\type\interface\type>}
|
||||
*/
|
||||
public $input_type;
|
||||
|
||||
|
||||
/**
|
||||
* @var \Closure {function<string,&\alveolata\type\interface\type>}
|
||||
*/
|
||||
public $output_type;
|
||||
|
||||
|
||||
/**
|
||||
* @var ?string supposed to be human readable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
public $description;
|
||||
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $custom;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
\Closure $active,
|
||||
\Closure $execution,
|
||||
\Closure $restriction,
|
||||
\Closure $input_type,
|
||||
\Closure $output_type,
|
||||
?string $title,
|
||||
?string $description,
|
||||
$custom
|
||||
)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->active = $active;
|
||||
$this->execution = $execution;
|
||||
$this->restriction = $restriction;
|
||||
$this->input_type = $input_type;
|
||||
$this->output_type = $output_type;
|
||||
$this->title = $title;
|
||||
$this->description = $description;
|
||||
$this->custom = $custom;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_subject {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {map<string,struct_action>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $actions;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {list<string>}
|
||||
*/
|
||||
public $infoparagraphs;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
array $actions,
|
||||
array $infoparagraphs = []
|
||||
)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->actions = $actions;
|
||||
$this->infoparagraphs = $infoparagraphs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
47
lib/alveolata/api/wrapper-class.php
Normal file
47
lib/alveolata/api/wrapper-class.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\api;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/api/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_api
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make() : class_api {return (new class_api(make()));}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(array $data_given) {register($this->subject, $data_given);}
|
||||
public function has(string $action_name) : bool {return has($this->subject, $action_name);}
|
||||
public function may(string $action_name, array $options = []) : array {return may($this->subject, $action_name, $options);}
|
||||
public function call(string $action_name, array $options = []) {return call($this->subject, $action_name, $options);}
|
||||
public function doc(array $options = []) : string {return doc($this->subject, $options);}
|
||||
|
||||
}
|
||||
|
||||
?>
|
226
lib/alveolata/args/functions.php
Normal file
226
lib/alveolata/args/functions.php
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\args;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
* @param string $subject
|
||||
* @return array|null
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _regexp_match(
|
||||
string $pattern,
|
||||
string $subject
|
||||
)
|
||||
{
|
||||
$matches = [];
|
||||
$pattern_ = sprintf('/%s/', $pattern);
|
||||
$result = preg_match($pattern_, $subject, $matches);
|
||||
if ($result === 0) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return array_slice($matches, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param list<§x> $list
|
||||
* @param function<§x,§y> $function
|
||||
* @return list<§y>
|
||||
*/
|
||||
function _list_map(
|
||||
array $list,
|
||||
\Closure $function
|
||||
) : array
|
||||
{
|
||||
$list_ = [];
|
||||
for ($index = 0; $index < count($list); $index += 1) {
|
||||
$value = $list[$index];
|
||||
$value_ = $function($value, $index);
|
||||
array_push($list_, $value_);
|
||||
}
|
||||
return $list_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param map<string,§x> $map
|
||||
* @param function<§x,§y> $function
|
||||
* @return map<string,§y>
|
||||
*/
|
||||
function _map_map(
|
||||
array $map,
|
||||
\Closure $function
|
||||
) : array
|
||||
{
|
||||
$map_ = [];
|
||||
foreach ($map as $key => $value) {
|
||||
$key_ = $key;
|
||||
$value_ = $function($value, $key);
|
||||
$map_[$key_] = $value_;
|
||||
}
|
||||
return $map_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param record<positioned:list<record<target:string,?default:any,?processing:function<string,any>>>,named:map<string,record<target:string,?default:any,?processing:function<string,any>>>> $spec_raw
|
||||
* @param list<string> $args_raw
|
||||
* @return map<string,any>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function parse(
|
||||
array $spec_raw,
|
||||
array $args_raw,
|
||||
array $settings_given = []
|
||||
) : array
|
||||
{
|
||||
$settings_default = [
|
||||
'supress_warnings' => false,
|
||||
];
|
||||
$settings = array_merge(
|
||||
$settings_default,
|
||||
$settings_given
|
||||
);
|
||||
// spec refinement
|
||||
{
|
||||
$spec = [
|
||||
'positioned_mandatory' => _list_map(
|
||||
$spec_raw['positioned_mandatory'],
|
||||
function ($entry_raw, $index) {
|
||||
return [
|
||||
'target' => (
|
||||
array_key_exists('target', $entry_raw)
|
||||
? $entry_raw['target']
|
||||
: sprintf('_arg_%d', $index)
|
||||
),
|
||||
'processing' => (
|
||||
array_key_exists('processing', $entry_raw)
|
||||
? $entry_raw['processing']
|
||||
: (function ($value_raw) {return $value_raw;})
|
||||
),
|
||||
];
|
||||
}
|
||||
),
|
||||
'positioned_optional' => _list_map(
|
||||
$spec_raw['positioned_optional'],
|
||||
function ($entry_raw, $index) use (&$spec_raw) {
|
||||
return [
|
||||
'target' => (
|
||||
array_key_exists('target', $entry_raw)
|
||||
? $entry_raw['target']
|
||||
: sprintf('_arg_%d', $index + count($spec_raw['positioned_mandatory']))
|
||||
),
|
||||
'processing' => (
|
||||
array_key_exists('processing', $entry_raw)
|
||||
? $entry_raw['processing']
|
||||
: (function ($value_raw) {return $value_raw;})
|
||||
),
|
||||
'default' => (
|
||||
array_key_exists('default', $entry_raw)
|
||||
? $entry_raw['default']
|
||||
: null
|
||||
),
|
||||
];
|
||||
}
|
||||
),
|
||||
'named' => _map_map(
|
||||
$spec_raw['named'],
|
||||
function ($entry_raw, $key) {
|
||||
return [
|
||||
'target' => (
|
||||
array_key_exists('target', $entry_raw)
|
||||
? $entry_raw['target']
|
||||
: $key
|
||||
),
|
||||
'processing' => (
|
||||
array_key_exists('processing', $entry_raw)
|
||||
? $entry_raw['processing']
|
||||
: (function ($value_raw) {return $value_raw;})
|
||||
),
|
||||
'default' => (
|
||||
array_key_exists('default', $entry_raw)
|
||||
? $entry_raw['default']
|
||||
: null
|
||||
),
|
||||
];
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
$args = [];
|
||||
// default values
|
||||
{
|
||||
foreach ($spec['positioned_optional'] as $entry) {
|
||||
$args[$entry['target']] = $entry['default'];
|
||||
}
|
||||
foreach ($spec['named'] as $entry) {
|
||||
$args[$entry['target']] = $entry['default'];
|
||||
}
|
||||
}
|
||||
// parsing
|
||||
{
|
||||
$position = 0;
|
||||
foreach ($args_raw as $arg_raw) {
|
||||
$result = _regexp_match('--([^=]+)=([^=]+)', $arg_raw);
|
||||
if ($result === null) {
|
||||
if ($position < count($spec['positioned_mandatory'])) {
|
||||
$entry = $spec['positioned_mandatory'][$position];
|
||||
$value_raw = $entry['processing']($arg_raw);
|
||||
$value = $value_raw;
|
||||
$args[$entry['target']] = $value;
|
||||
}
|
||||
else if ($position < count($spec['positioned_mandatory']) + count($spec['positioned_optional'])) {
|
||||
$entry = $spec['positioned_optional'][$position - count($spec['positioned_optional'])];
|
||||
$value_raw = $entry['processing']($arg_raw);
|
||||
$value = $value_raw;
|
||||
$args[$entry['target']] = $value;
|
||||
}
|
||||
else {
|
||||
if (! $settings['supress_warnings']) {
|
||||
\alveolata\log\warning(
|
||||
'unrecognized positional argument',
|
||||
[
|
||||
'position' => $position,
|
||||
'value' => $arg_raw,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
$position += 1;
|
||||
}
|
||||
else {
|
||||
$name = $result[0];
|
||||
$value_raw = $result[1];
|
||||
if (array_key_exists($name, $spec['named'])) {
|
||||
$entry = $spec['named'][$name];
|
||||
$value = $entry['processing']($value_raw);
|
||||
$args[$entry['target']] = $value;
|
||||
}
|
||||
else {
|
||||
if (! $settings['supress_warnings']) {
|
||||
\alveolata\log\warning(
|
||||
'unrecognized named argument',
|
||||
[
|
||||
'name' => $name,
|
||||
'value' => $value_raw,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$left = array_slice($spec['positioned_mandatory'], $position);
|
||||
if (count($left) > 0) {
|
||||
throw (new \Exception(sprintf('%d missing mandatory positioned arguments', count($left))));
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
|
225
lib/alveolata/args/test.spec.php
Normal file
225
lib/alveolata/args/test.spec.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/args/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'args',
|
||||
'setup' => function (&$environment) {
|
||||
$spec_raw = [
|
||||
'positioned_mandatory' => [
|
||||
[
|
||||
'target' => 'hagalaz',
|
||||
],
|
||||
],
|
||||
'positioned_optional' => [
|
||||
[
|
||||
'target' => 'naudhiz',
|
||||
'default' => 'hau_mich_blau',
|
||||
],
|
||||
],
|
||||
'named' => [
|
||||
'fehuz' => [
|
||||
// 'target' => 'fehuz',
|
||||
'default' => false,
|
||||
'processing' => function ($x) {return ($x === 'yes');},
|
||||
],
|
||||
'uruz' => [
|
||||
// 'target' => 'uruz',
|
||||
'default' => 42,
|
||||
'processing' => function ($x) {return intval($x);},
|
||||
],
|
||||
'thurisaz' => [
|
||||
// 'target' => 'thurisaz',
|
||||
'default' => 2.7182,
|
||||
'processing' => function ($x) {return floatval($x);},
|
||||
],
|
||||
'ansuz' => [
|
||||
// 'target' => 'ansuz',
|
||||
'default' => 'math',
|
||||
'processing' => function ($x) {return ($x);},
|
||||
],
|
||||
],
|
||||
];
|
||||
$environment['call'] = function ($args_raw) use (&$spec_raw) {
|
||||
return \alveolata\args\parse(
|
||||
$spec_raw,
|
||||
$args_raw,
|
||||
[
|
||||
'supress_warnings' => false,
|
||||
]
|
||||
);
|
||||
};
|
||||
},
|
||||
'cleanup' => function (&$environment) {
|
||||
},
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'parse',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'positined_mandatory_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$assert->crashes(
|
||||
function () use (&$environment) {
|
||||
$args = $environment['call']([]);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'positined_mandatory_present',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['hagalaz'], 'foo');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'positined_optional_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['naudhiz'], 'hau_mich_blau');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'positined_optional_present',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', 'bar']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['naudhiz'], 'bar');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_boolean_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['fehuz'], false);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_boolean_present_negative',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', '--fehuz=no']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['fehuz'], false);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_boolean_present_positive',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', '--fehuz=yes']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['fehuz'], true);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_integer_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['uruz'], 42);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_integer_present',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', '--uruz=7']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['uruz'], 7);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_float_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['thurisaz'], 2.7182);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_float_present',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', '--thurisaz=3.1415']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['thurisaz'], 3.1415);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_string_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['ansuz'], 'math');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_string_present',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$args = $environment['call'](['foo', '--ansuz=algebra']);
|
||||
|
||||
// assertions
|
||||
$assert->equal($args['ansuz'], 'algebra');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_malformed',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution & assertions
|
||||
$assert->runs(
|
||||
function () use (&$environment) {
|
||||
$args = $environment['call'](['foo', '--raid==ho=odin']);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'named_unspecified',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution & assertions
|
||||
$assert->runs(
|
||||
function () use (&$environment) {
|
||||
$args = $environment['call'](['foo', '--raidho=odin']);
|
||||
}
|
||||
);
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
65
lib/alveolata/auth/abstract/interface-client.php
Normal file
65
lib/alveolata/auth/abstract/interface-client.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
interface interface_client
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function passwordchange(
|
||||
string $password
|
||||
) : bool
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $key
|
||||
* @return boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function passwordreset(
|
||||
string $username,
|
||||
string $password,
|
||||
string $key
|
||||
) : bool
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $passwrd
|
||||
* @return boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function login(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
?>
|
33
lib/alveolata/auth/abstract/interface-server.php
Normal file
33
lib/alveolata/auth/abstract/interface-server.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
interface interface_server
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function login(
|
||||
string $kind,
|
||||
$parameters
|
||||
) : bool
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
?>
|
880
lib/alveolata/auth/implementation-srp/core.php
Normal file
880
lib/alveolata/auth/implementation-srp/core.php
Normal file
|
@ -0,0 +1,880 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
// require_once('vendor/autoload.php');
|
||||
require_once(DIR_ALVEOLATA . '/math/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @template type_number
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_srp_toolset/*<type_number>*/
|
||||
{
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number>,string>
|
||||
*/
|
||||
public $encode;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<string>,type_number>
|
||||
*/
|
||||
public $decode;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<int,string>,type_number>
|
||||
*/
|
||||
public $make;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<>,type_number>
|
||||
*/
|
||||
public $zero;
|
||||
|
||||
|
||||
/**
|
||||
* @todo check if really needed
|
||||
* @var function<tuple<>,type_number>
|
||||
*/
|
||||
public $one;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number>,string>
|
||||
*/
|
||||
public $to_string;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,boolean>
|
||||
*/
|
||||
public $equal;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,type_number>
|
||||
*/
|
||||
public $add;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,type_number>
|
||||
*/
|
||||
public $subtract;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,type_number>
|
||||
*/
|
||||
public $multiply;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,type_number>
|
||||
*/
|
||||
public $xor;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number>,type_number>
|
||||
*/
|
||||
public $mod;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<type_number,type_number,type_number>,type_number>
|
||||
*/
|
||||
public $modpow;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<list<string>,string>
|
||||
*/
|
||||
public $join;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<>
|
||||
*/
|
||||
public $compute_hash;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<string,string>,string>
|
||||
*/
|
||||
public $compute_password_hash;
|
||||
|
||||
|
||||
/**
|
||||
* @var function<tuple<>,type_number>
|
||||
*/
|
||||
public $compute_random;
|
||||
|
||||
|
||||
/**
|
||||
* @param array $tools {
|
||||
* record<
|
||||
* encode:function<tuple<type_number>,string>,
|
||||
* decode:function<tuple<string>,type_number>,
|
||||
* make:function<tuple<int,string>,type_number>,
|
||||
* zero:function<tuple<>,type_number>,
|
||||
* one:function<tuple<>,type_number>,
|
||||
* to_string:function<tuple<type_number>,string>,
|
||||
* equal:function<tuple<type_number,type_number>,boolean>,
|
||||
* add:function<tuple<type_number,type_number>,type_number>,
|
||||
* subtract:function<tuple<type_number,type_number>,type_number>,
|
||||
* multiply:function<tuple<type_number,type_number>,type_number>,
|
||||
* xor:function<tuple<type_number,type_number>,type_number>,
|
||||
* mod:function<tuple<type_number,type_number>,type_number>,
|
||||
* modpow:function<tuple<type_number,type_number,type_number>,type_number>,
|
||||
* join:function<list<string>,string>,
|
||||
* compute_hash:function<>,
|
||||
* compute_password_hash:function<tuple<string,string>,string>,
|
||||
* compute_random:function<tuple<>,type_number>,
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
public function __construct(
|
||||
array $tools
|
||||
)
|
||||
{
|
||||
$this->encode = $tools['encode'];
|
||||
$this->decode = $tools['decode'];
|
||||
$this->make = $tools['make'];
|
||||
$this->zero = $tools['zero'];
|
||||
$this->one = $tools['one'];
|
||||
$this->to_string = $tools['to_string'];
|
||||
$this->equal = $tools['equal'];
|
||||
$this->add = $tools['add'];
|
||||
$this->subtract = $tools['subtract'];
|
||||
$this->multiply = $tools['multiply'];
|
||||
$this->xor = $tools['xor'];
|
||||
$this->mod = $tools['mod'];
|
||||
$this->modpow = $tools['modpow'];
|
||||
$this->join = $tools['join'];
|
||||
$this->compute_hash = $tools['compute_hash'];
|
||||
$this->compute_password_hash = $tools['compute_password_hash'];
|
||||
$this->compute_random = $tools['compute_random'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _srp_toolset_simple(
|
||||
) : struct_srp_toolset/*<int>*/
|
||||
{
|
||||
return (new struct_srp_toolset/*<int>*/(
|
||||
[
|
||||
'encode' => (fn($number) => \sprintf('%X', $number)),
|
||||
'decode' => (fn($string) => \intval($string, 16)),
|
||||
'make' => (fn($base, $digits) => \intval($digits, $base)),
|
||||
'zero' => (fn() => 0),
|
||||
'one' => (fn() => 1),
|
||||
'to_string' => (fn($number) => \sprintf('%X', $number)),
|
||||
'equal' => (fn($x, $y) => ($x === $y)),
|
||||
'add' => (fn($x, $y) => ($x + $y)),
|
||||
'subtract' => (fn($x, $y) => ($x - $y)),
|
||||
'multiply' => (fn($x, $y) => ($x * $y)),
|
||||
'xor' => (fn($x, $y) => ($x ^ $y)),
|
||||
// 'mod' => (fn($x, $y) => ($x % $y)),
|
||||
'mod' => (fn($x, $y) => \alveolata\math\mod($x, $y)),
|
||||
// 'modpow' => (fn($base, $exponent, $modulus) => (($base ** $exponent) % $modulus)),
|
||||
'modpow' => (fn($base, $exponent, $modulus) => \alveolata\math\modpow($base, $exponent, $modulus)),
|
||||
'join' => (fn($parts) => \implode('', $parts)),
|
||||
'compute_hash' => (fn($string) => $string),
|
||||
'compute_password_hash' => (fn($salt, $password) => \sprintf(
|
||||
'%04X',
|
||||
\alveolata\list_\reduce(
|
||||
\alveolata\list_\map(
|
||||
\str_split($password . $salt),
|
||||
(fn($character) => \ord($character))
|
||||
),
|
||||
0,
|
||||
(fn($x, $y) => (($x + $y) & 0xFFFF))
|
||||
)
|
||||
)),
|
||||
'compute_random' => (fn() => \rand(1, 7)),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* requires composer package phpseclib/phpseclib
|
||||
*
|
||||
* @see https://api.phpseclib.com/master/phpseclib3/Math/BigInteger.html
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _srp_toolset_biginteger(
|
||||
) : struct_srp_toolset/*<\phpseclib3\Math\BigInteger>*/
|
||||
{
|
||||
return (new struct_srp_toolset/*<\phpseclib3\Math\BigInteger>*/(
|
||||
[
|
||||
'encode' => (fn($number) => $number->toHex()),
|
||||
'decode' => (fn($string) => (new \phpseclib3\Math\BigInteger($string, 16))),
|
||||
'make' => (fn($base, $digits) => (new \phpseclib3\Math\BigInteger($digits, $base))),
|
||||
'zero' => (fn() => (new \phpseclib3\Math\BigInteger('0', 16))),
|
||||
'one' => (fn() => (new \phpseclib3\Math\BigInteger('1', 16))),
|
||||
'to_string' => (fn($number) => $number->toHex()),
|
||||
'equal' => (fn($x, $y) => $x->equals($y)),
|
||||
'add' => (fn($x, $y) => $x->add($y)),
|
||||
'subtract' => (fn($x, $y) => $x->subtract($y)),
|
||||
'multiply' => (fn($x, $y) => $x->multiply($y)),
|
||||
'xor' => (fn($x, $y) => $x->bitwise_xor($y)),
|
||||
'mod' => (fn($x, $y) => $x->modPow(new \phpseclib3\Math\BigInteger('1', 16), $y)),
|
||||
'modpow' => (fn($base, $exponent, $modulus) => $base->modPow($exponent, $modulus)),
|
||||
'join' => (fn($parts) => \implode('', $parts)),
|
||||
'compute_hash' => (fn($string) => $string),
|
||||
/*
|
||||
'compute_password_hash' => (fn($salt, $password) => \Vinsaj9\Crypto\Scrypt\scrypt(
|
||||
$password,
|
||||
$salt,
|
||||
16384, // TODO: wadd is dadd?
|
||||
8, // TODO: wadd is dadd?
|
||||
1, // TODO: wadd is dadd?
|
||||
64 // TODO: wadd is dadd?
|
||||
)),
|
||||
*/
|
||||
/*
|
||||
'compute_password_hash' => (fn($salt, $password) => \password_hash(
|
||||
$password,
|
||||
\PASSWORD_BCRYPT,
|
||||
[
|
||||
'salt' => $salt,
|
||||
]
|
||||
)),
|
||||
*/
|
||||
'compute_password_hash' => (fn($salt, $password) => \hash(
|
||||
'sha256',
|
||||
($password . $salt)
|
||||
)),
|
||||
'compute_random' => (fn() => \phpseclib3\Math\BigInteger::random(24)),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_srp_subject/*<type_number>*/
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_srp_toolset<type_number>
|
||||
*/
|
||||
public $toolset;
|
||||
|
||||
|
||||
/**
|
||||
* @var type_number
|
||||
*/
|
||||
public $base;
|
||||
|
||||
|
||||
/**
|
||||
* @var type_number
|
||||
*/
|
||||
public $modulus;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
struct_srp_toolset/*<type_number>*/ $toolset,
|
||||
/*type_number */$base,
|
||||
/*type_number */$modulus
|
||||
)
|
||||
{
|
||||
$this->toolset = $toolset;
|
||||
$this->base = $base;
|
||||
$this->modulus = $modulus;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function srp_subject_make/*<type_number>*/(
|
||||
struct_srp_toolset/*<type_number>*/ $toolset,
|
||||
/*type_number */$base,
|
||||
/*type_number */$modulus
|
||||
)
|
||||
{
|
||||
return (
|
||||
new struct_srp_subject(
|
||||
$toolset,
|
||||
$base,
|
||||
$modulus
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return struct_srp_subject<int>
|
||||
*/
|
||||
function srp_subject_test(
|
||||
) : struct_srp_subject/*<int>*/
|
||||
{
|
||||
$toolset = _srp_toolset_simple();
|
||||
// <2>_19 = {2,4,8,16,13,7,14,9,18,17,15,11,3,6,12,5,10,1}
|
||||
return (new struct_srp_subject/*<int>*/(
|
||||
$toolset,
|
||||
($toolset->decode)('2'),
|
||||
($toolset->decode)('1323') // wollte eigentlich 19 nehmen, aber das ist zu klein :/
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sind die Werte, die bytegod immer nimmt
|
||||
* requires composer package phpseclib/phpseclib
|
||||
*
|
||||
* @return struct_srp_subject<\phpseclib3\Math\BigInteger>
|
||||
*/
|
||||
function srp_subject_biginteger_1(
|
||||
) : struct_srp_subject/*<\phpseclib3\Math\BigInteger>*/
|
||||
{
|
||||
$toolset = _srp_toolset_biginteger();
|
||||
return (new struct_srp_subject/*<\phpseclib3\Math\BigInteger>*/(
|
||||
$toolset,
|
||||
($toolset->decode)('2'),
|
||||
($toolset->decode)('AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73')
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class _state
|
||||
{
|
||||
public static $subject_default = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function srp_set_default_subject(
|
||||
\Closure $factory
|
||||
) : void
|
||||
{
|
||||
_state::$subject_default = $factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return struct_srp_subject<any>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function srp_subject_default(
|
||||
) : struct_srp_subject
|
||||
{
|
||||
if (_state::$subject_default === null) {
|
||||
srp_set_default_subject(fn() => srp_subject_biginteger_1());
|
||||
}
|
||||
return (_state::$subject_default)();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $salt
|
||||
* @param string $password
|
||||
* @return array {
|
||||
* record<
|
||||
* x:type_number,
|
||||
* v:type_number
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_verifier/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
string $salt,
|
||||
string $password
|
||||
) : array
|
||||
{
|
||||
$x = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_password_hash)($salt, $password)
|
||||
);
|
||||
// g^x
|
||||
$verifier = ($subject->toolset->modpow)(
|
||||
$subject->base,
|
||||
$x,
|
||||
$subject->modulus
|
||||
);
|
||||
return [
|
||||
'x' => $x,
|
||||
'verifier' => ($verifier),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* compute A
|
||||
*
|
||||
* @return array {
|
||||
* record<
|
||||
* k:type_number,
|
||||
* a_exponent:type_number,
|
||||
* a_value:type_number
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_a/*<type_number>*/(
|
||||
struct_srp_subject $subject
|
||||
) : array
|
||||
{
|
||||
// set asymmetry factor
|
||||
$k = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($subject->modulus),
|
||||
($subject->toolset->to_string)($subject->base),
|
||||
])
|
||||
)
|
||||
);
|
||||
// compute random until it fits requirements by specification
|
||||
while (true) {
|
||||
$a_exponent = ($subject->toolset->compute_random)();
|
||||
// g^a % N
|
||||
$a_value = ($subject->toolset->modpow)($subject->base, $a_exponent, $subject->modulus);
|
||||
// (g^a % N) % N == 0
|
||||
// if (! ($subject->toolset->equal)(($subject->toolset->mod)($a_value, $subject->modulus), ($subject->toolset->zero)())) {
|
||||
if (! ($subject->toolset->equal)($a_value, ($subject->toolset->zero)())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'k' => $k,
|
||||
'a_exponent' => $a_exponent,
|
||||
'a_value' => $a_value,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* compute B
|
||||
*
|
||||
* @param type_number $verifier
|
||||
* @return array {
|
||||
* record<
|
||||
* k:type_number,
|
||||
* b_exponent:type_number,
|
||||
* b_value:type_number
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_b/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*<type_number>*/ $verifier
|
||||
) : array
|
||||
{
|
||||
// asymmetry factor
|
||||
$k = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($subject->modulus),
|
||||
($subject->toolset->to_string)($subject->base),
|
||||
])
|
||||
)
|
||||
);
|
||||
$b_exponent = ($subject->toolset->compute_random)();
|
||||
// k*v
|
||||
$Bl = ($subject->toolset->multiply)($k, $verifier);
|
||||
// g^b
|
||||
$Br = ($subject->toolset->modpow)($subject->base, $b_exponent, $subject->modulus);
|
||||
// k*v + g^b
|
||||
$Bn = ($subject->toolset->add)($Bl, $Br);
|
||||
// (k*v + g^b) % N
|
||||
$b_value = ($subject->toolset->mod)($Bn, $subject->modulus);
|
||||
return [
|
||||
'k' => $k,
|
||||
'b_exponent' => $b_exponent,
|
||||
'b_value' => $b_value,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* k_client:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_k_client/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_exponent,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_value,
|
||||
/*type_number */$x,
|
||||
/*type_number */$k
|
||||
) : array
|
||||
{
|
||||
$u = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($a_value),
|
||||
($subject->toolset->to_string)($b_value),
|
||||
])
|
||||
)
|
||||
);
|
||||
// B - k*g^x
|
||||
$Sl = ($subject->toolset->subtract)(
|
||||
$b_value,
|
||||
($subject->toolset->multiply)(
|
||||
$k,
|
||||
($subject->toolset->modpow)(
|
||||
$subject->base,
|
||||
$x,
|
||||
$subject->modulus
|
||||
)
|
||||
)
|
||||
);
|
||||
// a + u*x
|
||||
$Sr = ($subject->toolset->add)(
|
||||
$a_exponent,
|
||||
($subject->toolset->multiply)(
|
||||
$u,
|
||||
$x
|
||||
)
|
||||
);
|
||||
// (B - k*g^x) ^ (a + u*x)
|
||||
$S = ($subject->toolset->modpow)(
|
||||
$Sl,
|
||||
$Sr,
|
||||
$subject->modulus
|
||||
);
|
||||
$k_client = ($subject->toolset->compute_hash)(($subject->toolset->to_string)($S));
|
||||
return [
|
||||
'k_client' => $k_client,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* k_server:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_k_server/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_exponent,
|
||||
/*type_number */$b_value,
|
||||
/*type_number */$verifier
|
||||
) : array
|
||||
{
|
||||
$u = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($a_value),
|
||||
($subject->toolset->to_string)($b_value),
|
||||
])
|
||||
)
|
||||
);
|
||||
// A*v^u
|
||||
$Sl = ($subject->toolset->multiply)(
|
||||
$a_value,
|
||||
($subject->toolset->modpow)(
|
||||
$verifier,
|
||||
$u,
|
||||
$subject->modulus
|
||||
)
|
||||
);
|
||||
// (A*v^u)^b
|
||||
$S = ($subject->toolset->modpow)(
|
||||
$Sl,
|
||||
$b_exponent,
|
||||
$subject->modulus
|
||||
);
|
||||
$k_server = ($subject->toolset->compute_hash)(($subject->toolset->to_string)($S));
|
||||
return [
|
||||
'k_server' => $k_server,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* m1:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m1_generic/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
string $s,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_value,
|
||||
string $identifier,
|
||||
string $k
|
||||
) : array
|
||||
{
|
||||
$HN = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->to_string)($subject->modulus)
|
||||
)
|
||||
);
|
||||
$Hg = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->to_string)($subject->base)
|
||||
)
|
||||
);
|
||||
$HI = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
$identifier
|
||||
)
|
||||
);
|
||||
// H(H(N) XOR H(g), H(I), s, A, B, k)
|
||||
$m1 = ($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)(($subject->toolset->xor)($HN, $Hg)),
|
||||
($subject->toolset->to_string)($HI),
|
||||
$s,
|
||||
($subject->toolset->to_string)($a_value),
|
||||
($subject->toolset->to_string)($b_value),
|
||||
$k,
|
||||
])
|
||||
) ;
|
||||
return [
|
||||
'm1' => $m1,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* m1_client:string,
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m1_client/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
string $s,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_value,
|
||||
string $identifier,
|
||||
string $k_client
|
||||
) : array
|
||||
{
|
||||
return ['m1_client' => srp_compute_m1_generic(
|
||||
$subject,
|
||||
$s,
|
||||
$a_value,
|
||||
$b_value,
|
||||
$identifier,
|
||||
$k_client
|
||||
)['m1']];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* m1_server:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m1_server/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
string $s,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_value,
|
||||
string $identifier,
|
||||
string $k_server
|
||||
) : array
|
||||
{
|
||||
return ['m1_server' => srp_compute_m1_generic(
|
||||
$subject,
|
||||
$s,
|
||||
$a_value,
|
||||
$b_value,
|
||||
$identifier,
|
||||
$k_server
|
||||
)['m1']];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* M2:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m2_generic/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value,
|
||||
string $m1,
|
||||
string $k
|
||||
) : array
|
||||
{
|
||||
$M2 = ($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($a_value),
|
||||
$m1,
|
||||
$k,
|
||||
])
|
||||
);
|
||||
return [
|
||||
'M2' => $M2,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* m2_client:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m2_client/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value,
|
||||
string $m1_client,
|
||||
string $k_client
|
||||
) : array
|
||||
{
|
||||
return ['m2_client' => srp_compute_m2_generic(
|
||||
$subject,
|
||||
$a_value,
|
||||
$m1_client,
|
||||
$k_client
|
||||
)['M2']];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* record<
|
||||
* m2_server:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_compute_m2_server/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value,
|
||||
string $m1_server,
|
||||
string $k_server
|
||||
) : array
|
||||
{
|
||||
return ['m2_server' => srp_compute_m2_generic(
|
||||
$subject,
|
||||
$a_value,
|
||||
$m1_server,
|
||||
$k_server
|
||||
)['M2']];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
function srp_verify_a/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value
|
||||
) : bool
|
||||
{
|
||||
if (! ($subject->toolset->equal)($a_value, ($subject->toolset->zero)())) {
|
||||
if (! ($subject->toolset->equal)(($subject->toolset->mod)($a_value, $subject->modulus), ($subject->toolset->zero)())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
function srp_verify_b/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$b_value
|
||||
) : bool
|
||||
{
|
||||
if (! ($subject->toolset->equal)($b_value, ($subject->toolset->zero)())) {
|
||||
if (! ($subject->toolset->equal)(($subject->toolset->mod)($b_value, $subject->modulus), ($subject->toolset->zero)())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
function srp_verify_hab/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
/*type_number */$a_value,
|
||||
/*type_number */$b_value
|
||||
) : bool
|
||||
{
|
||||
$u = ($subject->toolset->decode)(
|
||||
($subject->toolset->compute_hash)(
|
||||
($subject->toolset->join)([
|
||||
($subject->toolset->to_string)($a_value),
|
||||
($subject->toolset->to_string)($b_value),
|
||||
])
|
||||
)
|
||||
);
|
||||
if (! ($subject->toolset->equal)($u, ($subject->toolset->zero)())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
function srp_verify_k(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
string $k_client,
|
||||
string $k_server
|
||||
) : bool
|
||||
{
|
||||
if (($k_client != null) && ($k_server != null)) {
|
||||
if ($k_client === $k_server) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
96
lib/alveolata/auth/implementation-srp/functions-client.php
Normal file
96
lib/alveolata/auth/implementation-srp/functions-client.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/core.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_srp_subject<type_number> $subject
|
||||
* @param function<tuple<>,string> $generate_salt
|
||||
* @param function<tuple<string,string,string>,void> $handle
|
||||
*/
|
||||
function srp_client_register/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
\Closure $generate_salt,
|
||||
\Closure $handle
|
||||
) : \Closure
|
||||
{
|
||||
return (
|
||||
function ($username, $password) use (&$subject, &$generate_salt, &$handle) {
|
||||
$salt = $generate_salt();
|
||||
$compute_verifier_result = srp_compute_verifier(
|
||||
$subject,
|
||||
$salt,
|
||||
$password
|
||||
);
|
||||
$verifier = $compute_verifier_result['verifier'];
|
||||
$response1 = $handle(
|
||||
$username,
|
||||
$salt,
|
||||
($subject->toolset->encode)($verifier)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param function<tuple<string,string>,record<passed:boolean,salt:string,b_value:string>> $handle1
|
||||
* @param function<tuple<string>,record<passed:boolean,m2_server:string>> $handle2
|
||||
* @return function<tuple<string,string>,boolean>
|
||||
*/
|
||||
function srp_client_login/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
\Closure $handle1,
|
||||
\Closure $handle2
|
||||
) : \Closure
|
||||
{
|
||||
return (
|
||||
function (string $username, string $password) use (&$subject, &$handle1, &$handle2) {
|
||||
$computeAResult = srp_compute_a($subject);
|
||||
$a_exponent = $computeAResult['a_exponent'];
|
||||
$a_value = $computeAResult['a_value'];
|
||||
$k = $computeAResult['k'];
|
||||
$response1 = $handle1($username, ($subject->toolset->encode)($a_value));
|
||||
$salt = $response1['salt'];
|
||||
$b_value = ($subject->toolset->decode)($response1['b_value']);
|
||||
if (! $response1['passed']) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$verifiedB = srp_verify_b($subject, $b_value);
|
||||
$verifiedHAB = srp_verify_hab($subject, $a_exponent, $b_value);
|
||||
if (! ($verifiedB && $verifiedHAB)) {
|
||||
throw (new \Exception('connection security fault'));
|
||||
}
|
||||
else {
|
||||
$computeVerifierResult = srp_compute_verifier($subject, $salt, $password);
|
||||
$x = $computeVerifierResult['x'];
|
||||
$computeClientKResult = srp_compute_k_client($subject, $a_exponent, $a_value, $b_value, $x, $k);
|
||||
$k_client = $computeClientKResult['k_client'];
|
||||
$computeClientM1Result = srp_compute_m1_client($subject, $salt, $a_value, $b_value, $username, $k_client);
|
||||
$m1_client = $computeClientM1Result['m1_client'];
|
||||
$response2 = $handle2($m1_client);
|
||||
$m2_server = $response2['m2_server'];
|
||||
if (! $response2['passed']) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$computeClientM2Result = srp_compute_m2_client($subject, $a_value, $m1_client, $k_client);
|
||||
$m2_client = $computeClientM2Result['m2_client'];
|
||||
if (! ($m2_server === $m2_client)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
194
lib/alveolata/auth/implementation-srp/functions-server.php
Normal file
194
lib/alveolata/auth/implementation-srp/functions-server.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/core.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure $handler {
|
||||
* function<
|
||||
* tuple<
|
||||
* string,
|
||||
* string,
|
||||
* string
|
||||
* >,
|
||||
* void
|
||||
* >
|
||||
* } procedure that shall store the data (username, salt, verifier) (e.g. by creating a new database entry)
|
||||
* @return \Closure {
|
||||
* function<
|
||||
* tuple<
|
||||
* string,
|
||||
* string,
|
||||
* type_number,
|
||||
* >,
|
||||
* void
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_server_register/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
\Closure $handler
|
||||
) : \Closure
|
||||
{
|
||||
return (
|
||||
function ($username, $salt, $verifier_encoded) use (&$subject, &$handler) {
|
||||
$handler(
|
||||
$username,
|
||||
$salt,
|
||||
/*($subject->toolset->encode)($verifier)*/$verifier_encoded
|
||||
);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure $get_salt_and_verifier {
|
||||
* function<
|
||||
* tuple<
|
||||
* >,
|
||||
* record<
|
||||
* found:boolean,
|
||||
* salt:string,
|
||||
* verifier:string,
|
||||
* >
|
||||
* >
|
||||
* }
|
||||
* @param \Closure $state_save {
|
||||
* function<
|
||||
* tuple<
|
||||
* any,
|
||||
* >,
|
||||
* void
|
||||
* >
|
||||
* }
|
||||
* @return \Closure {
|
||||
* function<
|
||||
* tuple<
|
||||
* string,
|
||||
* string,
|
||||
* >,
|
||||
* record<
|
||||
* passed:boolean,
|
||||
* salt:string,
|
||||
* b_value:string,
|
||||
* >
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_server_login_1/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
\Closure $get_salt_and_verifier,
|
||||
\Closure $state_save
|
||||
) : \Closure
|
||||
{
|
||||
return (
|
||||
function (string $username, string $a_value_encoded) use (&$subject, &$get_salt_and_verifier, &$state_save) {
|
||||
$a_value = ($subject->toolset->decode)($a_value_encoded);
|
||||
$salt_and_verifier = $get_salt_and_verifier($username);
|
||||
$found = $salt_and_verifier['found'];
|
||||
if (! $found) {
|
||||
return [
|
||||
'passed' => false,
|
||||
'salt' => null,
|
||||
'b_value' => null,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$salt = $salt_and_verifier['salt'];
|
||||
$verifier = ($subject->toolset->decode)($salt_and_verifier['verifier']);
|
||||
$verified_a = srp_verify_a($subject, $a_value);
|
||||
if (! $verified_a) {
|
||||
return [
|
||||
'passed' => false,
|
||||
'salt' => null,
|
||||
'b_value' => null,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$computeBResult = srp_compute_b(
|
||||
$subject,
|
||||
$verifier
|
||||
);
|
||||
$b_value = $computeBResult['b_value'];
|
||||
$b_exponent = $computeBResult['b_exponent'];
|
||||
$state_save(
|
||||
[
|
||||
'salt' => $salt,
|
||||
'identifier' => $username,
|
||||
'a_value' => ($subject->toolset->encode)($a_value),
|
||||
'verifier' => ($subject->toolset->encode)($verifier),
|
||||
'b_exponent' => ($subject->toolset->encode)($b_exponent),
|
||||
'b_value' => ($subject->toolset->encode)($b_value),
|
||||
]
|
||||
);
|
||||
return [
|
||||
'passed' => true,
|
||||
'salt' => $salt,
|
||||
'b_value' => ($subject->toolset->encode)($b_value),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure $state_load {
|
||||
* function<
|
||||
* tuple<
|
||||
* >,
|
||||
* state:any
|
||||
* >
|
||||
* }
|
||||
* @return \Closure {
|
||||
* function<
|
||||
* tuple<
|
||||
* type_number
|
||||
* >,
|
||||
* record<
|
||||
* passed:boolean,
|
||||
* m2_server:type_number,
|
||||
* >
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function srp_server_login_2/*<type_number>*/(
|
||||
struct_srp_subject/*<type_number>*/ $subject,
|
||||
\Closure $state_load
|
||||
) : \Closure
|
||||
{
|
||||
return (
|
||||
function (string $m1_client) use (&$subject, &$state_load) {
|
||||
$state = $state_load();
|
||||
$salt = $state['salt'];
|
||||
$a_value = ($subject->toolset->decode)($state['a_value']);
|
||||
$b_exponent = ($subject->toolset->decode)($state['b_exponent']);
|
||||
$b_value = ($subject->toolset->decode)($state['b_value']);
|
||||
$verifier = ($subject->toolset->decode)($state['verifier']);
|
||||
$identifier = $state['identifier'];
|
||||
$compute_k_server_result = srp_compute_k_server($subject, $a_value, $b_exponent, $b_value, $verifier);
|
||||
$k_server = $compute_k_server_result['k_server'];
|
||||
$compute_m1_server_result = srp_compute_m1_server($subject, $salt, $a_value, $b_value, $identifier, $k_server);
|
||||
$m1_server = $compute_m1_server_result['m1_server'];
|
||||
if (! ($m1_client === $m1_server)) {
|
||||
return [
|
||||
'passed' => false,
|
||||
'm2_server' => null,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$compute_m2_server_result = srp_compute_m2_server($subject, $a_value, $m1_server, $k_server);
|
||||
return [
|
||||
'passed' => true,
|
||||
'm2_server' => $compute_m2_server_result['m2_server'],
|
||||
];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
101
lib/alveolata/auth/implementation-srp/wrapper-class-client.php
Normal file
101
lib/alveolata/auth/implementation-srp/wrapper-class-client.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/auth/abstract/interface-client.php');
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/functions-client.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_client_srp implements interface_client
|
||||
{
|
||||
|
||||
/**
|
||||
* @param function<tuple<>,string> $generate_salt
|
||||
* @param function<tuple<string,string,string>,void> $registration_send
|
||||
* @param function<tuple<string,string>,record<passed:boolean,salt:string,b_value:string>> $login_handle1
|
||||
* @param function<tuple<string>,record<passed:boolean,m2_server:string>> $login_handle2
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
struct_srp_subject $subject,
|
||||
\Closure $generate_salt,
|
||||
\Closure $register_handle,
|
||||
\Closure $login_handle1,
|
||||
\Closure $login_handle2
|
||||
)
|
||||
{
|
||||
$this->subject = $subject;
|
||||
$this->generate_salt = $generate_salt;
|
||||
$this->register_handle = $register_handle;
|
||||
$this->login_handle1 = $login_handle1;
|
||||
$this->login_handle2 = $login_handle2;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
{
|
||||
$proc = srp_client_register(
|
||||
$this->subject,
|
||||
$this->generate_salt,
|
||||
$this->register_handle
|
||||
);
|
||||
return $proc($username, $password);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function passwordchange(
|
||||
string $password
|
||||
) : bool
|
||||
{
|
||||
throw (new Exception('not implemented'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function passwordreset(
|
||||
string $username,
|
||||
string $password,
|
||||
string $key
|
||||
) : bool
|
||||
{
|
||||
throw (new Exception('not implemented'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function login(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
{
|
||||
$proc = srp_client_login(
|
||||
$this->subject,
|
||||
$this->login_handle1,
|
||||
$this->login_handle2
|
||||
);
|
||||
return $proc($username, $password);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/auth/abstract/interface-server.php');
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/functions-server.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_server_srp implements interface_server
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
struct_srp_subject $subject,
|
||||
\Closure $registration_handle,
|
||||
\Closure $get_salt_and_verifier,
|
||||
\Closure $state_save,
|
||||
\Closure $state_load
|
||||
)
|
||||
{
|
||||
$this->subject = $subject;
|
||||
$this->registration_handle = $registration_handle;
|
||||
$this->get_salt_and_verifier = $get_salt_and_verifier;
|
||||
$this->state_save = $state_save;
|
||||
$this->state_load = $state_load;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(
|
||||
string $username,
|
||||
string $password
|
||||
) : bool
|
||||
{
|
||||
$proc = srp_server_register(
|
||||
$this->registration_handle
|
||||
);
|
||||
return $proc($username, $password);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function login(
|
||||
string $kind,
|
||||
$parameters
|
||||
) : bool
|
||||
{
|
||||
switch ($kind) {
|
||||
default: {
|
||||
throw (new \Exception('unhandled'));
|
||||
break;
|
||||
}
|
||||
case 'step1': {
|
||||
$proc = srp_server_login_1(
|
||||
$this->subject,
|
||||
$this->state_save,
|
||||
$this->get_salt_and_verifier
|
||||
);
|
||||
return $proc($username, $password);
|
||||
break;
|
||||
}
|
||||
case 'step2': {
|
||||
$proc = srp_server_login_1(
|
||||
$this->subject,
|
||||
$this->state_load
|
||||
);
|
||||
return $proc($username, $password);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
298
lib/alveolata/auth/policy.php
Normal file
298
lib/alveolata/auth/policy.php
Normal file
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\auth;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/session/session.php');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_exception_policyreport extends \Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
$messages
|
||||
)
|
||||
{
|
||||
parent::__construct(
|
||||
' | '.join($messages)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
abstract class class_policy
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param record<session_id:string> $environment
|
||||
* @return list<string>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
abstract public function check(
|
||||
array $environment
|
||||
) : array
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_policy_none extends class_policy
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function check(
|
||||
array $environment
|
||||
) : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_policy_conjunction extends class_policy
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
array $subpolicies
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->subpolicies = $subpolicies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function check(
|
||||
array $environment
|
||||
) : array
|
||||
{
|
||||
return (
|
||||
\alveolata\list_\reduce(
|
||||
\alveolata\list_\map(
|
||||
$this->subpolicies,
|
||||
function ($policy) use ($environment) {
|
||||
return $policy->check($environment);
|
||||
}
|
||||
),
|
||||
[],
|
||||
function ($x, $y) {return \alveolata\list_\concat($x, $y);}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_policy_disjunction extends class_policy
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
array $subpolicies
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->subpolicies = $subpolicies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function check(
|
||||
array $environment
|
||||
) : array
|
||||
{
|
||||
$some = \alveolata\list_\some(
|
||||
$this->subpolicies,
|
||||
function ($policy) use ($environment) {
|
||||
return $policy->check($environment);
|
||||
}
|
||||
);
|
||||
return ($some ? [] : ['all alternatives failed']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_policy_logged_in extends class_policy
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function check(
|
||||
array $environment
|
||||
) : array
|
||||
{
|
||||
if (! array_key_exists('session_id', $environment)) {
|
||||
return ['session id not set'];
|
||||
}
|
||||
else {
|
||||
if (! \alveolata\session\has($environment['session_id'])) {
|
||||
return ['no session present'];
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param record<kind:string,parameters:map<strin,any>>
|
||||
* @return class_policy
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make(
|
||||
array $description
|
||||
) : class_policy
|
||||
{
|
||||
switch ($description['kind']) {
|
||||
default: {
|
||||
throw (
|
||||
new \Exception(
|
||||
\alveolata\string\coin('unhandled policy kind "{{kind}}"', ['kind' => $description['kind']])
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'none': {
|
||||
return (
|
||||
new class_policy_none(
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'logged_in': {
|
||||
return (
|
||||
new class_policy_logged_in(
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'conjunction': {
|
||||
return (
|
||||
new class_policy_conjunction(
|
||||
\helper\list_\map(
|
||||
$description['parameters']['sub'] ?? [],
|
||||
function ($description_) {
|
||||
return make($description_);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'disjunction': {
|
||||
return (
|
||||
new class_policy_disjunction(
|
||||
\helper\list_\map(
|
||||
$description['parameters']['sub'] ?? [],
|
||||
function ($description_) {
|
||||
return make($description_);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param class_policy $policy
|
||||
* @param function<void,type_result> $function
|
||||
* @param record<session_id:string> $environment
|
||||
* @return type_result
|
||||
* @throw \Exception if the policy check fails
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function wrap/*<type_result>*/(
|
||||
class_policy $policy,
|
||||
array $environment,
|
||||
\Closure $function
|
||||
)/* : type_result*/
|
||||
{
|
||||
$messages = $policy->check($environment);
|
||||
if (count($messages) === 0) {
|
||||
$result = $function();
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
throw (new class_exception_policyreport($messages));
|
||||
}
|
||||
}
|
||||
|
||||
|
139
lib/alveolata/auth/test.spec.php
Normal file
139
lib/alveolata/auth/test.spec.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/wrapper-class-client.php');
|
||||
require_once(DIR_ALVEOLATA . '/auth/implementation-srp/wrapper-class-server.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'auth',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'test',
|
||||
'setup' => function (&$environment) {
|
||||
// vars
|
||||
{
|
||||
$environment['users'] = [];
|
||||
$environment['state'] = null;
|
||||
}
|
||||
// constants
|
||||
{
|
||||
$srp_subject_client = \alveolata\auth\srp_subject_test();
|
||||
$srp_subject_server = \alveolata\auth\srp_subject_test();
|
||||
$proc_server_register = \alveolata\auth\srp_server_register(
|
||||
$srp_subject_server,
|
||||
function ($username, $salt, $verifier_encoded) use (&$environment) {
|
||||
$environment['users'][$username] = [
|
||||
'salt' => $salt,
|
||||
'verifier' => $verifier_encoded,
|
||||
];
|
||||
}
|
||||
);
|
||||
$proc_client_register = \alveolata\auth\srp_client_register(
|
||||
$srp_subject_client,
|
||||
fn() => 'salt',
|
||||
fn($username, $salt, $verifier_encoded) => $proc_server_register($username, $salt, $verifier_encoded)
|
||||
);
|
||||
$proc_server_login_1 = \alveolata\auth\srp_server_login_1(
|
||||
$srp_subject_server,
|
||||
function ($username) use (&$environment) {
|
||||
if (! array_key_exists($username, $environment['users'])) {
|
||||
return [
|
||||
// 'found' => true,
|
||||
'found' => false,
|
||||
'salt' => '',
|
||||
'verifier' => '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$user = $environment['users'][$username];
|
||||
return [
|
||||
'found' => true,
|
||||
'salt' => $user['salt'],
|
||||
'verifier' => $user['verifier'],
|
||||
];
|
||||
}
|
||||
},
|
||||
function ($state) use (&$environment) {
|
||||
$environment['state'] = $state;
|
||||
}
|
||||
);
|
||||
$proc_server_login_2 = \alveolata\auth\srp_server_login_2(
|
||||
$srp_subject_server,
|
||||
function () use (&$environment) {
|
||||
return $environment['state'];
|
||||
}
|
||||
);
|
||||
$environment['proc_client_login'] = \alveolata\auth\srp_client_login(
|
||||
$srp_subject_client,
|
||||
(fn($username, $a_value) => $proc_server_login_1($username, $a_value)),
|
||||
(fn($m1_client) => $proc_server_login_2($m1_client))
|
||||
);
|
||||
$environment['user_name'] = 'hans';
|
||||
$environment['user_password'] = '1234';
|
||||
$proc_client_register($environment['user_name'], $environment['user_password']);
|
||||
}
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'user_missing',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
{
|
||||
$success = $environment['proc_client_login'](
|
||||
$environment['user_name'] . '_some_bogus',
|
||||
$environment['user_password']
|
||||
);
|
||||
}
|
||||
// assertions
|
||||
{
|
||||
$assert->is(! $success);
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'user_existing_and_password_wrong',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
{
|
||||
$success = $environment['proc_client_login'](
|
||||
$environment['user_name'],
|
||||
$environment['user_password'] . '_some_bogus'
|
||||
);
|
||||
}
|
||||
// assertions
|
||||
{
|
||||
$assert->is(! $success);
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'user_existing_and_password_correct',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
{
|
||||
$success = $environment['proc_client_login'](
|
||||
$environment['user_name'],
|
||||
$environment['user_password']
|
||||
);
|
||||
}
|
||||
// assertions
|
||||
{
|
||||
$assert->is($success);
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
'cleanup' => function (&$environment) {
|
||||
},
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
64
lib/alveolata/cache/abstract/interface.php
vendored
Normal file
64
lib/alveolata/cache/abstract/interface.php
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
interface interface_cache
|
||||
{
|
||||
|
||||
/**
|
||||
* shell store a value
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set(
|
||||
string $id,
|
||||
$value
|
||||
) : void
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall tell if a value is set
|
||||
*
|
||||
* @param string $id
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function has(
|
||||
string $id
|
||||
) : bool
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall fetch a stored value
|
||||
*
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function fetch(
|
||||
string $id
|
||||
)
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall remove all stored values
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function clear(
|
||||
) : void
|
||||
;
|
||||
|
||||
}
|
||||
|
103
lib/alveolata/cache/functions.php
vendored
Normal file
103
lib/alveolata/cache/functions.php
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-none/wrapper-class.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-memory/wrapper-class.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-apc/wrapper-class.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param Closure $retrieve
|
||||
* @return record<fetched:boolean,value:mixed>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get_with_info(
|
||||
interface_cache $cache,
|
||||
string $id,
|
||||
\Closure $retrieve
|
||||
)
|
||||
{
|
||||
if (! $cache->has($id)) {
|
||||
$value = $retrieve();
|
||||
$cache->set($id, $value);
|
||||
return ['fetched' => false, 'value' => $value];
|
||||
}
|
||||
else {
|
||||
$value = $cache->fetch($id);
|
||||
return ['fetched' => true, 'value' => $value];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param Closure $retrieve
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get(
|
||||
interface_cache $cache,
|
||||
string $id,
|
||||
\Closure $retrieve
|
||||
)
|
||||
{
|
||||
return get_with_info($cache, $id, $retrieve)['value'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param Closure $retrieve
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function clear(
|
||||
interface_cache $cache
|
||||
)
|
||||
{
|
||||
return $cache->clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make(
|
||||
string $kind,
|
||||
array $parameters = []
|
||||
) : interface_cache
|
||||
{
|
||||
switch ($kind) {
|
||||
default: {
|
||||
throw (new \Exception(sprintf('invalid cache kind "%s"', $kind)));
|
||||
break;
|
||||
}
|
||||
case 'none': {
|
||||
return (
|
||||
implementation_none::make(
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'memory': {
|
||||
return (
|
||||
implementation_memory::make(
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'apc': {
|
||||
return (
|
||||
implementation_apc::make(
|
||||
$parameters['section'] ?? UNSET_STRING
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
163
lib/alveolata/cache/implementation-apc/functions.php
vendored
Normal file
163
lib/alveolata/cache/implementation-apc/functions.php
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class _state_apc {
|
||||
|
||||
/**
|
||||
* @var array {list<string>}
|
||||
*/
|
||||
public static $sections = [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_subject_apc {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $section;
|
||||
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $ids;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $section
|
||||
)
|
||||
{
|
||||
$this->section = $section;
|
||||
$this->ids = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_apc $subject
|
||||
* @param string id
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _apc_id(
|
||||
struct_subject_apc $subject,
|
||||
string $id
|
||||
) : string
|
||||
{
|
||||
return (
|
||||
($subject->section === UNSET_STRING)
|
||||
? $id
|
||||
: sprintf('%s_%s', $subject->section, $id)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
* @return struct_subject_apc
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function apc_make(
|
||||
string $section = UNSET_STRING
|
||||
) : struct_subject_apc
|
||||
{
|
||||
if ($section === UNSET_STRING) {
|
||||
$section = sprintf('alveolata_%d', count(_state_apc::$sections));
|
||||
}
|
||||
if (in_array($section, _state_apc::$sections)) {
|
||||
throw (new \Exception(sprintf('APC section "%s" already in use', $section)));
|
||||
}
|
||||
else {
|
||||
array_push(_state_apc::$sections, $section);
|
||||
$subject = (new struct_subject_apc($section));
|
||||
return $subject;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_apc $subject
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function apc_set(
|
||||
struct_subject_apc $subject,
|
||||
string $id,
|
||||
$value
|
||||
) : void
|
||||
{
|
||||
$id_ = _apc_id($subject, $id);
|
||||
array_push($subject->ids, $id_);
|
||||
\apc_store($id_, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_apc $subject
|
||||
* @param string $id
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function apc_has(
|
||||
struct_subject_apc $subject,
|
||||
string $id
|
||||
) : bool
|
||||
{
|
||||
return \apc_exists(_apc_id($subject, $id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_apc $subject
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function apc_fetch(
|
||||
struct_subject_apc $subject,
|
||||
string $id
|
||||
)
|
||||
{
|
||||
return \apc_fetch(_apc_id($subject, $id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_apc $subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function apc_clear(
|
||||
struct_subject_apc $subject
|
||||
) : void
|
||||
{
|
||||
if ($subject->section === UNSET_STRING) {
|
||||
\apc_clear_cache();
|
||||
}
|
||||
else {
|
||||
foreach ($subject->ids as $id_) {
|
||||
\apc_delete($id_);
|
||||
}
|
||||
$subject->ids = [];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
48
lib/alveolata/cache/implementation-apc/wrapper-class.php
vendored
Normal file
48
lib/alveolata/cache/implementation-apc/wrapper-class.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-apc/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/abstract/interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_apc implements interface_cache
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject_apc $subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject_apc $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
* @return implementation_apc
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make(string $section = UNSET_STRING) : implementation_apc {return (new static(apc_make($section)));}
|
||||
|
||||
|
||||
/**
|
||||
* @implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function set(string $id, $value) : void {apc_set($this->subject, $id, $value);}
|
||||
public function has(string $id) : bool {return apc_has($this->subject, $id);}
|
||||
public function fetch(string $id) {return apc_fetch($this->subject, $id);}
|
||||
public function clear() : void {apc_clear($this->subject);}
|
||||
|
||||
}
|
||||
|
98
lib/alveolata/cache/implementation-memory/functions.php
vendored
Normal file
98
lib/alveolata/cache/implementation-memory/functions.php
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_subject_memory {
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $data;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
* @return struct_subject_memory
|
||||
*/
|
||||
function memory_make(
|
||||
) : struct_subject_memory
|
||||
{
|
||||
return (new struct_subject_memory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_memory $subject
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function memory_set(
|
||||
struct_subject_memory $subject,
|
||||
string $id,
|
||||
$value
|
||||
) : void
|
||||
{
|
||||
$subject->data[$id] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_memory $subject
|
||||
* @param string $id
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function memory_has(
|
||||
struct_subject_memory $subject,
|
||||
string $id
|
||||
) : bool
|
||||
{
|
||||
return array_key_exists($id, $subject->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_memory $subject
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function memory_fetch(
|
||||
struct_subject_memory $subject,
|
||||
string $id
|
||||
)
|
||||
{
|
||||
return $subject->data[$id];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_memory $subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function memory_clear(
|
||||
struct_subject_memory $subject
|
||||
) : void
|
||||
{
|
||||
$subject->data = [];
|
||||
}
|
||||
|
48
lib/alveolata/cache/implementation-memory/wrapper-class.php
vendored
Normal file
48
lib/alveolata/cache/implementation-memory/wrapper-class.php
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-memory/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/abstract/interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_memory implements interface_cache
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject_memory
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @var struct_subject_memory $subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject_memory $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @return implementation_memory
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make() : implementation_memory {return (new static(memory_make()));}
|
||||
|
||||
|
||||
/**
|
||||
* @implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function set(string $id, $value) : void {memory_set($this->subject, $id, $value);}
|
||||
public function has(string $id) : bool {return memory_has($this->subject, $id);}
|
||||
public function fetch(string $id) {return memory_fetch($this->subject, $id);}
|
||||
public function clear() : void {memory_clear($this->subject);}
|
||||
|
||||
}
|
||||
|
54
lib/alveolata/cache/implementation-none/functions.php
vendored
Normal file
54
lib/alveolata/cache/implementation-none/functions.php
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function none_set(
|
||||
string $id,
|
||||
$value
|
||||
) : void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function none_has(
|
||||
string $id
|
||||
) : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function none_fetch(
|
||||
string $id
|
||||
)
|
||||
{
|
||||
throw (new \Exception('not available'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function none_clear(
|
||||
) : void
|
||||
{
|
||||
}
|
||||
|
39
lib/alveolata/cache/implementation-none/wrapper-class.php
vendored
Normal file
39
lib/alveolata/cache/implementation-none/wrapper-class.php
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cache;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/implementation-none/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/abstract/interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_none implements interface_cache
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct() {}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make() : implementation_none {return (new static());}
|
||||
|
||||
|
||||
/**
|
||||
* @implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function set(string $id, $value) : void {none_set($id, $value);}
|
||||
public function has(string $id) : bool {return none_has($id);}
|
||||
public function fetch(string $id) {return none_fetch($id);}
|
||||
public function clear() : void {none_clear();}
|
||||
|
||||
}
|
||||
|
205
lib/alveolata/cache/test.spec.php
vendored
Normal file
205
lib/alveolata/cache/test.spec.php
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'cache',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'none',
|
||||
'setup' => function (&$environment) {
|
||||
$cache = \alveolata\cache\make('none', []);
|
||||
\alveolata\cache\clear($cache);
|
||||
$environment['cache'] = $cache;
|
||||
},
|
||||
'cleanup' => function (&$environment) {
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'retrieve at first access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$result = \alveolata\cache\get_with_info(
|
||||
$environment['cache'],
|
||||
'foo',
|
||||
function () {
|
||||
return 'fehuz';
|
||||
}
|
||||
);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result['fetched'], false);
|
||||
$assert->equal($result['value'], 'fehuz');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'retrieve at second access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// consts
|
||||
$id = 'bar';
|
||||
$retrieve = function () {return 'uruz';};
|
||||
|
||||
// execution
|
||||
$result1 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
$result2 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result1['fetched'], false);
|
||||
$assert->equal($result1['value'], 'uruz');
|
||||
$assert->equal($result2['fetched'], false);
|
||||
$assert->equal($result2['value'], 'uruz');
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'memory',
|
||||
'setup' => function (&$environment) {
|
||||
$cache = \alveolata\cache\make('memory', []);
|
||||
\alveolata\cache\clear($cache);
|
||||
$environment['cache'] = $cache;
|
||||
},
|
||||
'cleanup' => function (&$environment) {
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'retrieve at first access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$result = \alveolata\cache\get_with_info(
|
||||
$environment['cache'],
|
||||
'foo',
|
||||
function () {
|
||||
return 'fehuz';
|
||||
}
|
||||
);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result['fetched'], false);
|
||||
$assert->equal($result['value'], 'fehuz');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'fetch at second access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// consts
|
||||
$id = 'bar';
|
||||
$retrieve = function () {return 'uruz';};
|
||||
|
||||
// execution
|
||||
$result1 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
$result2 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result1['fetched'], false);
|
||||
$assert->equal($result1['value'], 'uruz');
|
||||
$assert->equal($result2['fetched'], true);
|
||||
$assert->equal($result2['value'], 'uruz');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'retrieve again after clear',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// consts
|
||||
$id = 'baz';
|
||||
$retrieve = function () {return 'thurisaz';};
|
||||
|
||||
// execution
|
||||
$result1 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
$result2 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
\alveolata\cache\clear($environment['cache']);
|
||||
$result3 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result1['fetched'], false);
|
||||
$assert->equal($result1['value'], 'thurisaz');
|
||||
$assert->equal($result2['fetched'], true);
|
||||
$assert->equal($result2['value'], 'thurisaz');
|
||||
$assert->equal($result3['fetched'], false);
|
||||
$assert->equal($result3['value'], 'thurisaz');
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'apc',
|
||||
'active' => false,
|
||||
'setup' => function (&$environment) {
|
||||
$cache = \alveolata\cache\make('apc', []);
|
||||
\alveolata\cache\clear($cache);
|
||||
$environment['cache'] = $cache;
|
||||
},
|
||||
'cleanup' => function (&$environment) {
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'retrieve at first access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// execution
|
||||
$result = \alveolata\cache\get_with_info(
|
||||
$environment['cache'],
|
||||
'foo',
|
||||
function () {
|
||||
return 'fehuz';
|
||||
}
|
||||
);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result['fetched'], false);
|
||||
$assert->equal($result['value'], 'fehuz');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'fetch at second access',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// consts
|
||||
$id = 'bar';
|
||||
$retrieve = function () {return 'uruz';};
|
||||
|
||||
// execution
|
||||
$result1 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
$result2 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result1['fetched'], false);
|
||||
$assert->equal($result1['value'], 'uruz');
|
||||
$assert->equal($result2['fetched'], true);
|
||||
$assert->equal($result2['value'], 'uruz');
|
||||
}
|
||||
],
|
||||
[
|
||||
'name' => 'retrieve again after clear',
|
||||
'procedure' => function ($assert, $environment) {
|
||||
// consts
|
||||
$id = 'baz';
|
||||
$retrieve = function () {return 'thurisaz';};
|
||||
|
||||
// execution
|
||||
$result1 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
$result2 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
\alveolata\cache\clear($environment['cache']);
|
||||
$result3 = \alveolata\cache\get_with_info($environment['cache'], $id, $retrieve);
|
||||
|
||||
// assertions
|
||||
$assert->equal($result1['fetched'], false);
|
||||
$assert->equal($result1['value'], 'thurisaz');
|
||||
$assert->equal($result2['fetched'], true);
|
||||
$assert->equal($result2['value'], 'thurisaz');
|
||||
$assert->equal($result3['fetched'], false);
|
||||
$assert->equal($result3['value'], 'thurisaz');
|
||||
}
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
36
lib/alveolata/call/functions.php
Normal file
36
lib/alveolata/call/functions.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\call;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function convey(
|
||||
$value,
|
||||
array $functions
|
||||
)
|
||||
{
|
||||
$result = $value;
|
||||
foreach ($functions as $function) {
|
||||
$result = $function($result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
* @see https://www.php.net/manual/en/function.usleep.php
|
||||
*/
|
||||
function pause(
|
||||
float $seconds
|
||||
) : void
|
||||
{
|
||||
$seconds_whole = \intval(\floor($seconds));
|
||||
$seconds_rest = ($seconds - $seconds_whole);
|
||||
\sleep($seconds_whole);
|
||||
\usleep(\intval($seconds_rest * 1000000));
|
||||
}
|
||||
|
||||
?>
|
79
lib/alveolata/cgi/functions.php
Normal file
79
lib/alveolata/cgi/functions.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cgi;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cookie/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/http/types.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get_http_request(
|
||||
): \alveolata\http\struct_request
|
||||
{
|
||||
/*
|
||||
\alveolata\log\debug(
|
||||
'superglobals',
|
||||
[
|
||||
'server' => $_SERVER,
|
||||
'cookie' => \json_encode($_COOKIE),
|
||||
]
|
||||
);
|
||||
*/
|
||||
$cookiedata = (
|
||||
isset($_SERVER['HTTP_COOKIE'])
|
||||
? \alveolata\cookie\decode($_SERVER['HTTP_COOKIE'])
|
||||
: null
|
||||
);
|
||||
$request = new \alveolata\http\struct_request(
|
||||
$_SERVER['SERVER_PROTOCOL'],
|
||||
($_SERVER['REQUEST_METHOD'] ?? 'POST'),
|
||||
$_SERVER['REQUEST_URI'],
|
||||
\array_merge(
|
||||
\getallheaders(),
|
||||
[
|
||||
'Cookie' => ($_SERVER['HTTP_COOKIE'] ?? null),
|
||||
'Content-Type' => ($_SERVER['CONTENT_TYPE'] ?? 'application/json'),
|
||||
],
|
||||
),
|
||||
\file_get_contents('php://input')
|
||||
);
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function setup(
|
||||
): void
|
||||
{
|
||||
while (ob_get_level() >= 1) {
|
||||
ob_end_clean();
|
||||
}
|
||||
ob_start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function put_http_response(
|
||||
\alveolata\http\struct_response $response
|
||||
): void
|
||||
{
|
||||
while (ob_get_level() >= 1) {
|
||||
ob_end_clean();
|
||||
}
|
||||
ob_start();
|
||||
foreach ($response->headers as $key => $value) {
|
||||
\header(\sprintf('%s: %s', $key, $value));
|
||||
}
|
||||
\http_response_code($response->statuscode);
|
||||
\file_put_contents('php://output', $response->body);
|
||||
ob_end_flush();
|
||||
}
|
||||
|
||||
?>
|
8
lib/alveolata/cgi/setup.php
Normal file
8
lib/alveolata/cgi/setup.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
// ini_set('output_buffering', '1');
|
||||
while (ob_get_level() >= 1) {
|
||||
ob_end_clean();
|
||||
}
|
||||
ob_start();
|
||||
|
20
lib/alveolata/composer.json
Normal file
20
lib/alveolata/composer.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "greenscale/alveolata",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://greenscale.de",
|
||||
"license": ["proprietary"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Fraß"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"./definitions.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
103
lib/alveolata/conf/functions.php
Normal file
103
lib/alveolata/conf/functions.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\conf;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/file/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/json/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/cache/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @var \alveolata\cache\interface_cache
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class _state
|
||||
{
|
||||
public static $path = 'conf.json';
|
||||
public static $cache_kind = 'memory';
|
||||
public static $cache_parameters = [];
|
||||
public static $cache_instance = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_cache(
|
||||
string $kind,
|
||||
array $parameters = []
|
||||
) : void
|
||||
{
|
||||
_state::$cache_kind = $kind;
|
||||
_state::$cache_parameters = $parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_path(
|
||||
string $path
|
||||
) : void
|
||||
{
|
||||
_state::$path = $path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param any [$fallback] value to return in case no conf entry was found for the given path
|
||||
* @param boolean [$throw_exception] if true, do not return the fallback in case of a missing entry, but throw an exception
|
||||
* @return mixed
|
||||
* @throw \Exception
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get(
|
||||
string $path,
|
||||
$fallback = null,
|
||||
$throw_exception = false
|
||||
)
|
||||
{
|
||||
// prepare cache
|
||||
if (_state::$cache_instance === null) {
|
||||
_state::$cache_instance = \alveolata\cache\make(_state::$cache_kind, _state::$cache_parameters);
|
||||
}
|
||||
// get raw data
|
||||
$data = \alveolata\cache\get(
|
||||
_state::$cache_instance,
|
||||
'data',
|
||||
function () {
|
||||
$content = \alveolata\file\read(_state::$path);
|
||||
$data = \alveolata\json\decode($content);
|
||||
return $data;
|
||||
}
|
||||
);
|
||||
// get specific piece of information
|
||||
$steps = (($path === '') ? [] : explode('.', $path));
|
||||
$value = $data;
|
||||
foreach ($steps as $step) {
|
||||
if (! array_key_exists($step, $value)) {
|
||||
$report = \alveolata\report\make(
|
||||
'conf entry not set',
|
||||
[
|
||||
'path' => $path,
|
||||
'fallback' => $fallback,
|
||||
]
|
||||
);
|
||||
if ($throw_exception) {
|
||||
throw (\alveolata\report_as_exception($report));
|
||||
}
|
||||
else {
|
||||
\alveolata\log\warning_($report);
|
||||
return $fallback;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$value = $value[$step];
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
?>
|
42
lib/alveolata/cookie/functions.php
Normal file
42
lib/alveolata/cookie/functions.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\cookie;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return map<string,string>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function decode(
|
||||
$string
|
||||
) : array
|
||||
{
|
||||
$parts = explode('; ', $string);
|
||||
$stuff = [];
|
||||
foreach ($parts as $part) {
|
||||
$parts2 = explode('=', $part);
|
||||
$stuff[$parts2[0]] = $parts2[1];
|
||||
}
|
||||
return $stuff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param map<string,string> $stuff
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function encode(
|
||||
array $stuff
|
||||
) : string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($stuff as $key => $value) {
|
||||
$part = sprintf('%s=%s', $key, $value);
|
||||
}
|
||||
$string = implode('; ', $parts);
|
||||
return $string;
|
||||
}
|
||||
|
||||
?>
|
101
lib/alveolata/csv/functions.php
Normal file
101
lib/alveolata/csv/functions.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\csv;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @paraam array $data {list<list<string>>}
|
||||
*/
|
||||
function encode(
|
||||
array $data,
|
||||
?array $settings = null
|
||||
) : string
|
||||
{
|
||||
$settings = array_merge(
|
||||
[
|
||||
'delimiter' => ";",
|
||||
'linebreak' => "\n",
|
||||
'quote' => "\"",
|
||||
'prepend_byte_order_mark' => false,
|
||||
],
|
||||
($settings ?? [])
|
||||
);
|
||||
$csv = \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
$data,
|
||||
function (array $row) use ($settings) : string {
|
||||
return \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
$row,
|
||||
function ($field) use ($settings) : string {
|
||||
return sprintf(
|
||||
'%s%s%s',
|
||||
$settings['quote'],
|
||||
str_replace($settings['quote'], '\\' . $settings['quote'], $field),
|
||||
$settings['quote']
|
||||
);
|
||||
}
|
||||
),
|
||||
$settings['delimiter']
|
||||
);
|
||||
}
|
||||
),
|
||||
$settings['linebreak']
|
||||
);
|
||||
if ($settings['prepend_byte_order_mark']) {
|
||||
$csv = (chr(0xEF) . chr(0xBB) . chr(0xBF) . $csv);
|
||||
}
|
||||
return $csv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function decode(
|
||||
string $csv,
|
||||
array $settings_given = []
|
||||
) : array
|
||||
{
|
||||
$settings_default = [
|
||||
'delimiter' => ";",
|
||||
'linebreak' => "\n",
|
||||
'quote' => "\"",
|
||||
];
|
||||
$settings = array_merge($settings_default, $settings_given);
|
||||
return \alveolata\list_\map(
|
||||
\alveolata\list_\filter(
|
||||
\alveolata\string\split(
|
||||
$csv,
|
||||
$settings['linebreak']
|
||||
),
|
||||
function (string $line) : bool {
|
||||
return (! empty(trim($line)));
|
||||
}
|
||||
),
|
||||
function (string $line) use ($settings) : array {
|
||||
return \alveolata\list_\map(
|
||||
\alveolata\string\split(
|
||||
$line,
|
||||
$settings['delimiter']
|
||||
),
|
||||
function (string $field) use ($settings) {
|
||||
if (
|
||||
\alveolata\string\starts_with($field, $settings['quote'])
|
||||
and
|
||||
\alveolata\string\end_with($field, $settings['quote'])
|
||||
) {
|
||||
return substr($field, 1, strlen($field)-2);
|
||||
}
|
||||
else {
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
59
lib/alveolata/database/abstract/interface.php
Normal file
59
lib/alveolata/database/abstract/interface.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
interface interface_database
|
||||
{
|
||||
|
||||
/**
|
||||
* shall return the terminal symbol used for indicating auto incrementation for a column
|
||||
*
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function terminal_autoincrement(
|
||||
) : string
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall return the definition for a field, which is meant to be an integer typed primary key with auto increment
|
||||
* setting
|
||||
*
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function boilerplate_field_definition_for_integer_primary_key_with_auto_increment(
|
||||
) : string
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall send a query to the database
|
||||
*
|
||||
* @param string $template an SQL query string with placeholders of the form ":name"
|
||||
* @param array $arguments record values to insert for the placeholders
|
||||
* @return array {
|
||||
* record<
|
||||
* rows:list<map<string,any>>,
|
||||
* id:(null|integer),
|
||||
* affected:integer
|
||||
* >
|
||||
* }
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function query(
|
||||
string $template,
|
||||
array $arguments = []
|
||||
) : array
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
?>
|
61
lib/alveolata/database/functions.php
Normal file
61
lib/alveolata/database/functions.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-sqlite/wrapper-class.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-mysql/wrapper-class.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-postgresql/wrapper-class.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make(
|
||||
string $kind,
|
||||
array $parameters
|
||||
) : interface_database
|
||||
{
|
||||
switch ($kind) {
|
||||
default: {
|
||||
throw (new \Exception(sprintf('invalid database kind "%s"', $kind)));
|
||||
break;
|
||||
}
|
||||
case 'sqlite': {
|
||||
return (
|
||||
implementation_sqlite::make(
|
||||
$parameters['path'],
|
||||
$parameters['verbosity'] ?? 0
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'mysql': {
|
||||
return (
|
||||
implementation_mysql::make(
|
||||
$parameters['host'],
|
||||
$parameters['port'],
|
||||
$parameters['schema'],
|
||||
$parameters['username'],
|
||||
$parameters['password']
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'postgresql': {
|
||||
return (
|
||||
implementation_postgresql::make(
|
||||
$parameters['host'],
|
||||
$parameters['port'],
|
||||
$parameters['schema'],
|
||||
$parameters['username'],
|
||||
$parameters['password']
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
188
lib/alveolata/database/implementation-mysql/functions.php
Normal file
188
lib/alveolata/database/implementation-mysql/functions.php
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/sql/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_subject_mysql {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $host;
|
||||
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $port;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $username;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $password;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
)
|
||||
{
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->schema = $schema;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function mysql_make(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
) : struct_subject_mysql
|
||||
{
|
||||
return (
|
||||
new struct_subject_mysql(
|
||||
$host,
|
||||
$port,
|
||||
$schema,
|
||||
$username,
|
||||
$password
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function mysql_terminal_autoincrement(
|
||||
) : string
|
||||
{
|
||||
return 'AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function mysql_boilerplate_field_definition_for_integer_primary_key_with_auto_increment(
|
||||
) : string
|
||||
{
|
||||
return 'INTEGER PRIMARY KEY AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_mysql $subject
|
||||
* @param string $template
|
||||
* @param array $arguments
|
||||
* @return array
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function mysql_query(
|
||||
struct_subject_mysql $subject,
|
||||
string $template,
|
||||
array $arguments
|
||||
) : array
|
||||
{
|
||||
$connection = \mysqli_connect(
|
||||
$subject->host,
|
||||
$subject->username,
|
||||
$subject->password,
|
||||
$subject->schema,
|
||||
$subject->port
|
||||
);
|
||||
\mysqli_options($connection, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
|
||||
\mysqli_set_charset($connection, 'utf8');
|
||||
\mysqli_query($connection, 'set names \'utf8\'');
|
||||
$query = $template;
|
||||
foreach ($arguments as $key => $value) {
|
||||
$pattern = sprintf(':%s', $key);
|
||||
$replacement = \alveolata\sql\format(
|
||||
$value,
|
||||
function ($x) use ($connection) {
|
||||
return \mysqli_real_escape_string($connection, $x);
|
||||
}
|
||||
);
|
||||
$query = str_replace($pattern, $replacement, $query);
|
||||
}
|
||||
$report = \alveolata\report\make(
|
||||
'query',
|
||||
[
|
||||
'query' => $query,
|
||||
]
|
||||
);
|
||||
\alveolata\log\debug_($report);
|
||||
$result = \mysqli_query($connection, $query);
|
||||
if ($result === false) {
|
||||
$report = \alveolata\report\make(
|
||||
'query failed',
|
||||
[
|
||||
'query' => $query,
|
||||
'reason' => \mysqli_error($connection),
|
||||
]
|
||||
);
|
||||
throw (\alveolata\report\as_exception($report));
|
||||
}
|
||||
else if ($result === true) {
|
||||
$output = [
|
||||
'rows' => [],
|
||||
'id' => mysqli_insert_id($connection),
|
||||
'affected' => \mysqli_affected_rows($connection),
|
||||
];
|
||||
return $output;
|
||||
}
|
||||
else {
|
||||
$output = [
|
||||
'rows' => $result->fetch_all(MYSQLI_ASSOC),
|
||||
'id' => 0,
|
||||
'affected' => 0,
|
||||
];
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-mysql/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_mysql implements interface_database
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject_mysql
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject_mysql $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
) : implementation_mysql
|
||||
{
|
||||
$subject = mysql_make($host, $port, $schema, $username, $password);
|
||||
return (new implementation_mysql($subject));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function terminal_autoincrement() : string {return mysql_terminal_autoincrement();}
|
||||
public function boilerplate_field_definition_for_integer_primary_key_with_auto_increment() : string {return mysql_boilerplate_field_definition_for_integer_primary_key_with_auto_increment();}
|
||||
public function query(string $template, array $arguments = []) : array {return mysql_query($this->subject, $template, $arguments);}
|
||||
|
||||
}
|
||||
|
||||
?>
|
203
lib/alveolata/database/implementation-postgresql/functions.php
Normal file
203
lib/alveolata/database/implementation-postgresql/functions.php
Normal file
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/sql/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
* requires PHP module "pgsql" (Debian package name "php-pgsql")
|
||||
*/
|
||||
class struct_subject_postgresql {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $host;
|
||||
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $port;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $username;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $password;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
)
|
||||
{
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->schema = $schema;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function postgresql_make(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
) : struct_subject_postgresql
|
||||
{
|
||||
return (
|
||||
new struct_subject_postgresql(
|
||||
$host,
|
||||
$port,
|
||||
$schema,
|
||||
$username,
|
||||
$password
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function postgresql_terminal_autoincrement(
|
||||
) : string
|
||||
{
|
||||
return 'AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function postgresql_boilerplate_field_definition_for_integer_primary_key_with_auto_increment(
|
||||
) : string
|
||||
{
|
||||
return 'SERIAL PRIMARY KEY';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_postgresql $subject
|
||||
* @param string $template
|
||||
* @param array $arguments
|
||||
* @return array
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function postgresql_query(
|
||||
struct_subject_postgresql $subject,
|
||||
string $template,
|
||||
array $arguments
|
||||
) : array
|
||||
{
|
||||
$connection = \pg_connect(
|
||||
sprintf(
|
||||
'host=%s port=%d user=%s password=%s dbname=%s',
|
||||
$subject->host,
|
||||
$subject->port,
|
||||
$subject->username,
|
||||
$subject->password,
|
||||
$subject->schema
|
||||
)
|
||||
);
|
||||
// \pg_set_client_encoding($connection, \UNICODE);
|
||||
\pg_query($connection, "SET client_encoding TO 'UNICODE'");
|
||||
$template_adjusted = $template;
|
||||
$arguments_adjusted = [];
|
||||
$counter = 0;
|
||||
foreach ($arguments as $key => $value) {
|
||||
$pattern = \sprintf(':%s', $key);
|
||||
$replacement = \sprintf('$%d', $counter+1);
|
||||
$counter += 1;
|
||||
$template_adjusted = str_replace($pattern, $replacement, $template_adjusted);
|
||||
\array_push($arguments_adjusted, $value);
|
||||
}
|
||||
$report = \alveolata\report\make(
|
||||
'postgresl_query',
|
||||
[
|
||||
'template' => $template_adjusted,
|
||||
'arguments' => $arguments,
|
||||
]
|
||||
);
|
||||
\alveolata\log\debug_($report);
|
||||
$result = \pg_query_params($connection, $template_adjusted, $arguments_adjusted);
|
||||
if ($result === false) {
|
||||
$report = \alveolata\report\make(
|
||||
'postgresl_query_failed',
|
||||
[
|
||||
'template' => $template_adjusted,
|
||||
'arguments' => $arguments_adjusted,
|
||||
]
|
||||
);
|
||||
throw (\alveolata\report\as_exception($report));
|
||||
}
|
||||
else {
|
||||
// $status = \pg_result_status($result, \PGSQL_STATUS_STRING);
|
||||
$status = \pg_result_status($result, \PGSQL_STATUS_LONG);
|
||||
switch ($status) {
|
||||
default: {
|
||||
$report = \alveolata\report\make(
|
||||
'postgresl_query_bad_result',
|
||||
[
|
||||
'template' => $template_adjusted,
|
||||
'arguments' => $arguments_adjusted,
|
||||
'status' => $status,
|
||||
]
|
||||
);
|
||||
throw (\alveolata\report\as_exception($report));
|
||||
break;
|
||||
}
|
||||
case \PGSQL_COMMAND_OK:
|
||||
case \PGSQL_TUPLES_OK: {
|
||||
$id_raw = \pg_last_oid($result);
|
||||
return [
|
||||
'rows' => \pg_fetch_all($result, \PGSQL_ASSOC),
|
||||
'id' => (($id_raw === false) ? null : $id_raw),
|
||||
'affected' => \pg_affected_rows($result),
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-postgresql/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_postgresql implements interface_database
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject_postgresql
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject_postgresql $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make(
|
||||
string $host,
|
||||
int $port,
|
||||
string $schema,
|
||||
string $username,
|
||||
string $password
|
||||
) : implementation_postgresql
|
||||
{
|
||||
$subject = postgresql_make($host, $port, $schema, $username, $password);
|
||||
return (new implementation_postgresql($subject));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function terminal_autoincrement() : string {return postgresql_terminal_autoincrement();}
|
||||
public function boilerplate_field_definition_for_integer_primary_key_with_auto_increment() : string {return postgresql_boilerplate_field_definition_for_integer_primary_key_with_auto_increment();}
|
||||
public function query(string $template, array $arguments = []) : array {return postgresql_query($this->subject, $template, $arguments);}
|
||||
|
||||
}
|
||||
|
||||
?>
|
144
lib/alveolata/database/implementation-sqlite/functions.php
Normal file
144
lib/alveolata/database/implementation-sqlite/functions.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/sql/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_subject_sqlite {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $path;
|
||||
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public $verbosity;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $path,
|
||||
int $verbosity
|
||||
)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->verbosity = $verbosity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sqlite_make(
|
||||
string $path,
|
||||
int $verbosity = 0
|
||||
)
|
||||
{
|
||||
return (
|
||||
new struct_subject_sqlite(
|
||||
$path,
|
||||
$verbosity
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sqlite_terminal_autoincrement(
|
||||
) : string
|
||||
{
|
||||
return 'AUTOINCREMENT';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sqlite_boilerplate_field_definition_for_integer_primary_key_with_auto_increment(
|
||||
) : string
|
||||
{
|
||||
return 'INTEGER PRIMARY KEY AUTOINCREMENT';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct_subject_sqlite $subject
|
||||
* @param string $template
|
||||
* @param array $arguments
|
||||
* @return array
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sqlite_query(
|
||||
struct_subject_sqlite $subject,
|
||||
string $template,
|
||||
array $arguments
|
||||
) : array
|
||||
{
|
||||
$sqlite3 = new \SQLite3($subject->path);
|
||||
$query = $template;
|
||||
foreach ($arguments as $key => $value) {
|
||||
$pattern = sprintf(':%s', $key);
|
||||
$replacement = \alveolata\sql\format($value, function ($x) {return \SQLite3::escapeString($x);});
|
||||
$query = str_replace($pattern, $replacement, $query);
|
||||
}
|
||||
$report = \alveolata\report\make(
|
||||
'query',
|
||||
[
|
||||
'query' => \alveolata\string\replace(
|
||||
$query,
|
||||
[
|
||||
"\n" => ' ',
|
||||
"\t" => ' ',
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
\alveolata\log\debug_($report);
|
||||
$result = $sqlite3->query($query);
|
||||
if ($result === false) {
|
||||
$report = \alveolata\report\make(
|
||||
'query failed',
|
||||
[
|
||||
'query' => $query,
|
||||
]
|
||||
);
|
||||
throw (\alveolata\report\as_exception($report));
|
||||
}
|
||||
else {
|
||||
$output = [
|
||||
'rows' => [],
|
||||
'id' => $sqlite3->lastInsertRowId(),
|
||||
'affected' => $sqlite3->changes(),
|
||||
];
|
||||
if ($result->numColumns() > 0) {
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
array_push($output['rows'], $row);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\database;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/database/implementation-sqlite/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_sqlite implements interface_database
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_subject_sqlite
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_subject_sqlite $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make(
|
||||
string $path,
|
||||
int $verbosity = 0
|
||||
) : implementation_sqlite
|
||||
{
|
||||
$subject = sqlite_make($path, $verbosity);
|
||||
return (new implementation_sqlite($subject));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function terminal_autoincrement() : string {return sqlite_terminal_autoincrement();}
|
||||
public function boilerplate_field_definition_for_integer_primary_key_with_auto_increment() : string {return sqlite_boilerplate_field_definition_for_integer_primary_key_with_auto_increment();}
|
||||
public function query(string $template, array $arguments = []) : array {return sqlite_query($this->subject, $template, $arguments);}
|
||||
|
||||
}
|
||||
|
||||
?>
|
14
lib/alveolata/definitions.php
Normal file
14
lib/alveolata/definitions.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
define('DIR_ALVEOLATA', __DIR__);
|
||||
|
||||
define('UNSET_INTEGER', -7777777);
|
||||
// define('UNSET_INTEGER', null);
|
||||
|
||||
define('UNSET_STRING', '_unset_');
|
||||
// define('UNSET_STRING', null);
|
||||
|
||||
define('UNSET_ARRAY', [UNSET_STRING]);
|
||||
// define('UNSET_ARRAY', null);
|
||||
|
||||
?>
|
22
lib/alveolata/email/abstract/interface.php
Normal file
22
lib/alveolata/email/abstract/interface.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
interface interface_email
|
||||
{
|
||||
|
||||
/**
|
||||
* shall send an E-Mail
|
||||
*/
|
||||
function send(
|
||||
struct_credentials $credentials,
|
||||
struct_data $data
|
||||
) : void
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
?>
|
244
lib/alveolata/email/base.php
Normal file
244
lib/alveolata/email/base.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/pod/wrapper-class.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class enum_negotiation_type
|
||||
{
|
||||
public const none = 'none';
|
||||
public const opportunistic = 'opportunistic'; // STARTTLS
|
||||
public const forced = 'forced';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see https://en.m.wikipedia.org/wiki/SMTP_Authentication
|
||||
*/
|
||||
class enum_auth_type
|
||||
{
|
||||
public const none = 'none';
|
||||
public const plain = 'plain';
|
||||
public const login = 'login';
|
||||
public const gssapi = 'gssapi';
|
||||
public const md5 = 'md5';
|
||||
public const digest_md5 = 'digest_md5';
|
||||
public const cram_md5 = 'cram_md5';
|
||||
public const oauth10a = 'oauth10a';
|
||||
public const oauthbearer = 'oauthbearer';
|
||||
public const xoauth2 = 'xoauth2';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct_credentials
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public $host;
|
||||
|
||||
|
||||
/**
|
||||
* @param int
|
||||
*/
|
||||
public $port;
|
||||
|
||||
|
||||
/**
|
||||
* @param string (enum_negotiation_type)
|
||||
*/
|
||||
public $negotiation_type;
|
||||
|
||||
|
||||
/**
|
||||
* @param string (enum_auth_type)
|
||||
*/
|
||||
public $auth_type;
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public $username;
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public $password;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
array $raw
|
||||
)
|
||||
{
|
||||
$fields = [
|
||||
[
|
||||
'name' => 'host',
|
||||
'default' => \alveolata\pod\class_pod::toom(),
|
||||
],
|
||||
[
|
||||
'name' => 'port',
|
||||
'default' => \alveolata\pod\class_pod::full(587),
|
||||
],
|
||||
[
|
||||
'name' => 'negotiation_type',
|
||||
'default' => \alveolata\pod\class_pod::full(enum_negotiation_type::forced),
|
||||
],
|
||||
[
|
||||
'name' => 'auth_type',
|
||||
'default' => \alveolata\pod\class_pod::full(enum_auth_type::login),
|
||||
],
|
||||
[
|
||||
'name' => 'username',
|
||||
'default' => \alveolata\pod\class_pod::toom(),
|
||||
],
|
||||
[
|
||||
'name' => 'password',
|
||||
'default' => \alveolata\pod\class_pod::toom(),
|
||||
],
|
||||
];
|
||||
foreach ($fields as $field) {
|
||||
if (
|
||||
(! $field['default']->has())
|
||||
&&
|
||||
(! array_key_exists($field['name'], $raw))
|
||||
) {
|
||||
throw (new \Exception(sprintf('mandatory parameter "%s" missing', $field['name'])));
|
||||
}
|
||||
else {
|
||||
$this->{$field['name']} = ($raw[$field['name']] ?? $field['default']->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct_data {
|
||||
|
||||
/**
|
||||
* @param array {list<string>}
|
||||
*/
|
||||
public $to;
|
||||
|
||||
|
||||
/**
|
||||
* @param ?string {(null | string)}
|
||||
*/
|
||||
public $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @param ?string {(null | string)}
|
||||
*/
|
||||
public $body_plain;
|
||||
|
||||
|
||||
/**
|
||||
* @param ?string {(null | string)}
|
||||
*/
|
||||
public $body_html;
|
||||
|
||||
|
||||
/**
|
||||
* @param ?string {(null | string)}
|
||||
*/
|
||||
public $from;
|
||||
|
||||
|
||||
/**
|
||||
* @param array {list<string>}
|
||||
*/
|
||||
public $cc;
|
||||
|
||||
|
||||
/**
|
||||
* @param array {list<string>}
|
||||
*/
|
||||
public $bcc;
|
||||
|
||||
|
||||
/**
|
||||
* @param ?string (null | string)
|
||||
*/
|
||||
public $reply_to;
|
||||
|
||||
|
||||
/**
|
||||
* @param array {list<record<kind:string,data:any>>}
|
||||
*/
|
||||
public $attachments;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct(
|
||||
array $raw
|
||||
)
|
||||
{
|
||||
$fields = [
|
||||
[
|
||||
'name' => 'to',
|
||||
'default' => \alveolata\pod\class_pod::toom(),
|
||||
],
|
||||
[
|
||||
'name' => 'subject',
|
||||
'default' => \alveolata\pod\class_pod::toom(),
|
||||
],
|
||||
[
|
||||
'name' => 'body_plain',
|
||||
'default' => \alveolata\pod\class_pod::full(null),
|
||||
],
|
||||
[
|
||||
'name' => 'body_html',
|
||||
'default' => \alveolata\pod\class_pod::full(null),
|
||||
],
|
||||
[
|
||||
'name' => 'from',
|
||||
'default' => \alveolata\pod\class_pod::full(null),
|
||||
],
|
||||
[
|
||||
'name' => 'cc',
|
||||
'default' => \alveolata\pod\class_pod::full([]),
|
||||
],
|
||||
[
|
||||
'name' => 'bcc',
|
||||
'default' => \alveolata\pod\class_pod::full([]),
|
||||
],
|
||||
[
|
||||
'name' => 'reply_to',
|
||||
'default' => \alveolata\pod\class_pod::full(null),
|
||||
],
|
||||
[
|
||||
'name' => 'attachments',
|
||||
'default' => \alveolata\pod\class_pod::full([]),
|
||||
],
|
||||
];
|
||||
foreach ($fields as $field) {
|
||||
if (
|
||||
(! $field['default']->has())
|
||||
&&
|
||||
(! array_key_exists($field['name'], $raw))
|
||||
) {
|
||||
throw (new \Exception(sprintf('mandatory parameter "%s" missing', $field['name'])));
|
||||
}
|
||||
else {
|
||||
$this->{$field['name']} = ($raw[$field['name']] ?? $field['default']->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
75
lib/alveolata/email/functions.php
Normal file
75
lib/alveolata/email/functions.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-legacy/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-pear/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-swift/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-phpmailer/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param array $credentials_raw {
|
||||
* record<
|
||||
* ?negotiation_type:string,
|
||||
* host:string,
|
||||
* ?port:integer,
|
||||
* ?auth_type:string,
|
||||
* username:string,
|
||||
* password:string
|
||||
* >
|
||||
* }
|
||||
* @param array $data {
|
||||
* record<
|
||||
* to:list<string>,
|
||||
* subject:string,
|
||||
* ?body_plain:string,
|
||||
* ?body_html:string,
|
||||
* ?from:string,
|
||||
* ?cc:list<string>,
|
||||
* ?bcc:list<string>,
|
||||
* ?reply_to:string,
|
||||
* ?attachments:list<record<path:string,mime:string,?name:string>>
|
||||
* >
|
||||
* }
|
||||
* @throws Exception if mail can not be sent
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function send(
|
||||
array $credentials_raw,
|
||||
array $data_raw,
|
||||
array $options = []
|
||||
) : void
|
||||
{
|
||||
$options = \array_merge(
|
||||
[
|
||||
'implementation' => 'swift',
|
||||
],
|
||||
$options
|
||||
);
|
||||
|
||||
$credentials = new struct_credentials($credentials_raw);
|
||||
$data = new struct_data($data_raw);
|
||||
|
||||
switch ($options['implementation']) {
|
||||
default:
|
||||
case 'legacy': {
|
||||
implementations\legacy\send($credentials, $data);
|
||||
break;
|
||||
}
|
||||
case 'pear': {
|
||||
implementations\pear\send($credentials, $data);
|
||||
break;
|
||||
}
|
||||
case 'swift': {
|
||||
implementations\swift\send($credentials, $data);
|
||||
break;
|
||||
}
|
||||
case 'phpmailer': {
|
||||
implementations\phpmailer\send($credentials, $data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
96
lib/alveolata/email/implementation-legacy/functions.php
Normal file
96
lib/alveolata/email/implementation-legacy/functions.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\legacy;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/base.php');
|
||||
|
||||
|
||||
/**
|
||||
* @todo use credentials
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
// constants
|
||||
$separator = \md5(\time());
|
||||
|
||||
// helpers
|
||||
$headerEntry = function ($key, $value, $args = []) {
|
||||
return \sprintf(
|
||||
"%s: %s%s\r\n",
|
||||
$key,
|
||||
$value,
|
||||
\implode(
|
||||
'',
|
||||
\array_map(
|
||||
fn($x) => \sprintf('; %s=%s', $x, $args[$x]),
|
||||
\array_keys($args)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
$separatorEntry = function ($final) use ($separator) {
|
||||
return (
|
||||
$final
|
||||
? \sprintf("--%s--\r\n", $separator)
|
||||
: \sprintf("--%s\r\n", $separator)
|
||||
);
|
||||
};
|
||||
$textEntry = function ($content) {
|
||||
return \sprintf("\r\n%s\r\n", $content);
|
||||
};
|
||||
|
||||
// vars
|
||||
$message = '';
|
||||
$additional_headers = '';
|
||||
$additional_headers .= $headerEntry('To', \implode(',', $data->to));
|
||||
$additional_headers .= $headerEntry('Subject', $data->subject);
|
||||
if (! empty($data->from)) {
|
||||
$additional_headers .= $headerEntry('From', $data->from);
|
||||
}
|
||||
if (! empty($data->reply_to)) {
|
||||
$additional_headers .= $headerEntry('Reply-To', $data->reply_to);
|
||||
}
|
||||
if (! empty($data->cc)) {
|
||||
$additional_headers .= $headerEntry('Cc', \implode(',', $data->cc));
|
||||
}
|
||||
if (! empty($data->bcc)) {
|
||||
$additional_headers .= $headerEntry('Bcc', \implode(',', $data->bcc));
|
||||
}
|
||||
if (empty($data->attachments)) {
|
||||
$additional_headers .= $headerEntry('Content-Type', 'text/plain', ['charset' => 'utf-8']);
|
||||
$message .= $textEntry(\wordwrap($data->body_plain));
|
||||
}
|
||||
else {
|
||||
$additional_headers .= $headerEntry('MIME-Version', '1.0');
|
||||
$additional_headers .= $headerEntry('Content-Type', 'multipart/mixed', ['boundary' => $separator]);
|
||||
$message .= $separatorEntry(false);
|
||||
$message .= $headerEntry('Content-Type', 'text/plain', ['charset' => 'utf-8']);
|
||||
$message .= $headerEntry('Content-Transfer-Encoding', '7bit');
|
||||
$message .= $textEntry(\wordwrap($data->body_plain));
|
||||
foreach ($data->attachments as $attachment) {
|
||||
$message .= $separatorEntry(false);
|
||||
$message .= $headerEntry('Content-Transfer-Encoding', 'base64');
|
||||
$message .= $headerEntry('Content-Type', $attachment['mime'] ?? 'application/octet-stream');
|
||||
$message .= $headerEntry('Content-Disposition', 'attachment', ['filename' => ($attachment['name'] ?? \basename($attachment['path']))]);
|
||||
$message .= $textEntry(\chunk_split(\base64_encode(\file_get_contents($attachment['path']))));
|
||||
}
|
||||
$message .= $separatorEntry(true);
|
||||
}
|
||||
|
||||
// exec
|
||||
$successful = \mail(
|
||||
\implode(',', $data->to),
|
||||
$data->subject,
|
||||
$message,
|
||||
$additional_headers
|
||||
);
|
||||
if (! $successful) {
|
||||
throw (new \Exception(\sprintf('mail_could_not_be_sent')));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
26
lib/alveolata/email/implementation-legacy/wrapper-class.php
Normal file
26
lib/alveolata/email/implementation-legacy/wrapper-class.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\legacy;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-legacy/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class implementation implements interface_email
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
public send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
send($credentials, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
99
lib/alveolata/email/implementation-pear/functions.php
Normal file
99
lib/alveolata/email/implementation-pear/functions.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\pear;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/base.php');
|
||||
|
||||
|
||||
/**
|
||||
* untested!
|
||||
*/
|
||||
function send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
require_once('/usr/share/php/Mail.php');
|
||||
require_once('/usr/share/php/Mail/mime.php');
|
||||
|
||||
$auth_type_map = [
|
||||
\alveolata\email\enum_auth_type::none => false,
|
||||
\alveolata\email\enum_auth_type::plain => 'PLAIN',
|
||||
\alveolata\email\enum_auth_type::login => 'LOGIN',
|
||||
\alveolata\email\enum_auth_type::gssapi => 'GSSAPI',
|
||||
\alveolata\email\enum_auth_type::digest_md5 => 'DIGEST-MD5',
|
||||
\alveolata\email\enum_auth_type::cram_md5 => 'CRAM-MD5',
|
||||
];
|
||||
if (! \array_key_exists($credentials->auth_type, $auth_type_map)) {
|
||||
throw (new \Exception(\sprintf('unsupported auth method "%s"', $credentials->auth_type)));
|
||||
}
|
||||
else {
|
||||
$auth = $auth_type_map[$credentials->auth_type];
|
||||
|
||||
/**
|
||||
* @see https://pear.php.net/manual/en/package.mail.mail.factory.php
|
||||
*/
|
||||
$sender = \Mail::factory(
|
||||
'smtp',
|
||||
[
|
||||
'host' => $credentials->host,
|
||||
'port' => \sprintf('%u', $credentials->port),
|
||||
'auth' => $auth,
|
||||
'username' => $credentials->username,
|
||||
'password' => $credentials->password,
|
||||
// 'localhost' => null,
|
||||
// 'timeout' => null,
|
||||
// 'verp' => null,
|
||||
// 'debug' => null,
|
||||
// 'persist' => null,
|
||||
// 'pipelining' => null,
|
||||
// 'socket_options' => null,
|
||||
]
|
||||
);
|
||||
|
||||
// headers & body
|
||||
$headers_raw = [];
|
||||
$headers_raw['To'] = \implode(',', $data->to);
|
||||
$headers_raw['Subject'] = $data->subject;
|
||||
if (! empty($data->from)) {
|
||||
$headers_raw['From'] = $data->from;
|
||||
}
|
||||
if (! empty($data->reply_to)) {
|
||||
$headers_raw['Reply-To'] = $data->reply_to;
|
||||
}
|
||||
if (empty($data->attachments)) {
|
||||
$headers = $headers_raw;
|
||||
$body = $data->body_plain;
|
||||
}
|
||||
else {
|
||||
$mime = new \Mail_mime();
|
||||
if (! empty($data->body_plain)) {
|
||||
$mime->setTXTBody($data->body_plain);
|
||||
}
|
||||
if (! empty($data->body_html)) {
|
||||
$mime->setHTMLBody($data->body_html);
|
||||
}
|
||||
foreach ($data->attachments as $attachment) {
|
||||
$mime->addAttachment(
|
||||
$attachment['path'],
|
||||
($attachment['mime'] ?? 'application/octet-stream'),
|
||||
($attachment['name'] ?? \basename($attachment['path']))
|
||||
);
|
||||
}
|
||||
$headers = $mime->headers($headers_raw);
|
||||
$body = $mime->get();
|
||||
}
|
||||
|
||||
// exec
|
||||
$mail = $sender->send(
|
||||
\implode(',', $data->to),
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
if (\PEAR::isError($mail)) {
|
||||
throw (new \Exception(\sprintf('mail_could_not_be_sent: %s', $mail->getMessage())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
26
lib/alveolata/email/implementation-pear/wrapper-class.php
Normal file
26
lib/alveolata/email/implementation-pear/wrapper-class.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\pear;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-pear/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class implementation implements interface_email
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
public send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
send($credentials, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
73
lib/alveolata/email/implementation-phpmailer/functions.php
Normal file
73
lib/alveolata/email/implementation-phpmailer/functions.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\phpmailer;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/base.php');
|
||||
|
||||
|
||||
/**
|
||||
* requires composer package "phpmailer/phpmailer"
|
||||
*
|
||||
* @todo set auth type
|
||||
* @todo attachment mapping
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
$phpmailer = new \PHPMailer\PHPMailer\PHPMailer(true);
|
||||
|
||||
// auth
|
||||
$phpmailer->Host = $credentials->host;
|
||||
$phpmailer->Port = \sprintf('%u', $credentials->port);
|
||||
$phpmailer->Username = $credentials->username;
|
||||
$phpmailer->Password = $credentials->password;
|
||||
$phpmailer->isSMTP();
|
||||
// $phpmailer->SMTPDebug = \PHPMailer\PHPMailer\SMTP::DEBUG_SERVER;
|
||||
$phpmailer->SMTPAuth = ($credentials->negotiation_type !== \alveolata\email\enum_negotiation_type::none);
|
||||
$phpmailer->SMTPSecure = ([
|
||||
\alveolata\email\enum_negotiation_type::none => null,
|
||||
\alveolata\email\enum_negotiation_type::opportunistic => \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS,
|
||||
\alveolata\email\enum_negotiation_type::forced => \PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_SMTPS,
|
||||
][$credentials->negotiation_type]);
|
||||
|
||||
// data
|
||||
foreach ($data->to as $address) {
|
||||
$phpmailer->addAddress($address);
|
||||
}
|
||||
$phpmailer->Subject = $data->subject;
|
||||
if (! empty($data->from)) {
|
||||
$phpmailer->setFrom($data->from);
|
||||
}
|
||||
if (! empty($data->reply_to)) {
|
||||
$phpmailer->addReplyTo($data->reply_to);
|
||||
}
|
||||
foreach ($data->cc as $address) {
|
||||
$phpmailer->addCC($address);
|
||||
}
|
||||
foreach ($data->bcc as $address) {
|
||||
$phpmailer->addBCC($address);
|
||||
}
|
||||
foreach ($data->attachments as $attachment) {
|
||||
$phpmailer->addAttachment($attachment);
|
||||
}
|
||||
|
||||
// body
|
||||
if (! empty($data->body_html)) {
|
||||
$phpmailer->isHTML(true);
|
||||
$phpmailer->Body = $data->body_html;
|
||||
if (! empty($data->body_plain)) {
|
||||
$phpmailer->AltBody = $data->body_plain;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$phpmailer->Body = $data->body_plain;
|
||||
}
|
||||
|
||||
// send
|
||||
$phpmailer->send();
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\phpmailer;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-phpmailer/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class implementation implements interface_email
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
public send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
send($credentials, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
68
lib/alveolata/email/implementation-swift/functions.php
Normal file
68
lib/alveolata/email/implementation-swift/functions.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\swift;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/base.php');
|
||||
|
||||
|
||||
/**
|
||||
* requires composer package "swiftmailer/swiftmailer"
|
||||
*
|
||||
* @todo set auth type
|
||||
* @todo check for availability at start
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
// Create the Transport
|
||||
$transport = new \Swift_SmtpTransport(
|
||||
$credentials->host,
|
||||
$credentials->port,
|
||||
[
|
||||
\alveolata\email\enum_negotiation_type::none => null,
|
||||
\alveolata\email\enum_negotiation_type::opportunistic => 'tls',
|
||||
\alveolata\email\enum_negotiation_type::forced => 'ssl',
|
||||
][$credentials->negotiation_type]
|
||||
);
|
||||
$transport->setUsername($credentials->username);
|
||||
$transport->setPassword($credentials->password);
|
||||
|
||||
// Create the Mailer using your created Transport
|
||||
$mailer = new \Swift_Mailer($transport);
|
||||
|
||||
// Create a message
|
||||
$message = new \Swift_Message();
|
||||
if (! empty($data->from)) {
|
||||
$message->setFrom([$data->from]);
|
||||
}
|
||||
$message->setTo($data->to);
|
||||
if (! empty($data->cc)) {
|
||||
$message->setCc($data->cc);
|
||||
}
|
||||
if (! empty($data->bcc)) {
|
||||
$message->setBcc($data->bcc);
|
||||
}
|
||||
$message->setSubject($data->subject);
|
||||
|
||||
if (! empty($data->body_html)) {
|
||||
$message->setBody($data->body_html, 'text/html');
|
||||
if (! empty($data->body_plain)) {
|
||||
$message->addPart($data->body_plain, 'text/plain');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message->setBody($data->body_plain, 'text/plain');
|
||||
}
|
||||
|
||||
foreach ($data->attachments as $attachment) {
|
||||
$attachment_ = \Swift_Attachment::fromPath($attachment['path'], $attachment['mime']);
|
||||
$attachment_->setFilename($attachment['name']);
|
||||
$message->attach($attachment_);
|
||||
}
|
||||
|
||||
// Send the message
|
||||
$result = $mailer->send($message);
|
||||
}
|
26
lib/alveolata/email/implementation-swift/wrapper-class.php
Normal file
26
lib/alveolata/email/implementation-swift/wrapper-class.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\email\implementations\swift;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/email/abstract/interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/implementation-swift/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class implementation implements interface_email
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
public send(
|
||||
\alveolata\email\struct_credentials $credentials,
|
||||
\alveolata\email\struct_data $data
|
||||
) : void
|
||||
{
|
||||
send($credentials, $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
81
lib/alveolata/file/functions.php
Normal file
81
lib/alveolata/file/functions.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\file;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function exists(
|
||||
string $path
|
||||
) : bool
|
||||
{
|
||||
return file_exists($path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function read(
|
||||
string $path
|
||||
) : string
|
||||
{
|
||||
if (! exists($path)) {
|
||||
throw (new \Exception(sprintf('file not found: "%s"', $path)));
|
||||
}
|
||||
else {
|
||||
$content = file_get_contents($path);
|
||||
if ($content === false) {
|
||||
throw (new \Exception('could not read file'));
|
||||
}
|
||||
else {
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $content
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function write(
|
||||
string $path,
|
||||
string $content,
|
||||
bool $create_directory_if_missing = false
|
||||
)
|
||||
{
|
||||
if ($create_directory_if_missing) {
|
||||
$directory = implode('/', array_slice(explode('/', $path), 0, -1));
|
||||
if (! file_exists($directory)) {
|
||||
mkdir($directory);
|
||||
}
|
||||
}
|
||||
file_put_contents($path, $content);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function remove(
|
||||
string $path
|
||||
)
|
||||
{
|
||||
if (! exists($path)) {
|
||||
throw (new \Exception(sprintf('file not found: "%s"', $path)));
|
||||
}
|
||||
else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
|
61
lib/alveolata/html/functions.php
Normal file
61
lib/alveolata/html/functions.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\html;
|
||||
|
||||
|
||||
/**
|
||||
* @param null|array $titles {null|list<string>}
|
||||
* @param array $data {list<list<string>>}
|
||||
*/
|
||||
function table(
|
||||
array $titles,
|
||||
array $data
|
||||
) : string
|
||||
{
|
||||
$html = '';
|
||||
$html .= ("<table>\n");
|
||||
if (! is_null($titles)) {
|
||||
$html .= ("<thead>\n");
|
||||
$html .= sprintf(
|
||||
"<tr>%s</tr>\n",
|
||||
implode(
|
||||
'',
|
||||
array_map(
|
||||
function (string $title) : string {
|
||||
return sprintf(
|
||||
"<th>%s</th>",
|
||||
$title
|
||||
);
|
||||
},
|
||||
$titles
|
||||
)
|
||||
)
|
||||
);
|
||||
$html .= ("</thead>\n");
|
||||
}
|
||||
{
|
||||
$html .= ("<tbody>\n");
|
||||
foreach ($data as $line) {
|
||||
$html .= sprintf(
|
||||
"<tr>%s</tr>\n",
|
||||
implode(
|
||||
'',
|
||||
array_map(
|
||||
function (string $field) : string {
|
||||
return sprintf(
|
||||
"<td>%s</td>",
|
||||
$field
|
||||
);
|
||||
},
|
||||
$line
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$html .= ("</tbody>\n");
|
||||
}
|
||||
$html .= ('</table>' . "\n");
|
||||
return $html;
|
||||
}
|
||||
|
||||
?>
|
167
lib/alveolata/html/test.spec.php
Normal file
167
lib/alveolata/html/test.spec.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/html/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'html',
|
||||
'setup' => function (&$environment) {
|
||||
},
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'parse',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'oblique',
|
||||
'procedure' => function ($assert, &$environment) {
|
||||
$html = '<html><head><meta charset="utf-8"/></head><body><span class="abc def">foo <b>bar</b> baz <i>qux</i></span></body></html>';
|
||||
$xmlnode = \alveolata\html\parse($html);
|
||||
$output_actual = $xmlnode->to_raw();
|
||||
$output_expected = [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'html',
|
||||
'attributes' => [],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'head',
|
||||
'attributes' => [],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'meta',
|
||||
'attributes' => [
|
||||
'charset' => 'utf-8'
|
||||
],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => []
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'body',
|
||||
'attributes' => [],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'span',
|
||||
'attributes' => [
|
||||
'class' => 'abc def'
|
||||
],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'text',
|
||||
'data' => [
|
||||
'content' => 'foo'
|
||||
]
|
||||
],
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'b',
|
||||
'attributes' => [],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'text',
|
||||
'data' => [
|
||||
'content' => 'bar'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'kind' => 'text',
|
||||
'data' => [
|
||||
'content' => 'baz'
|
||||
]
|
||||
],
|
||||
[
|
||||
'kind' => 'complex',
|
||||
'data' => [
|
||||
'name' => 'i',
|
||||
'attributes' => [],
|
||||
'subnode' => [
|
||||
'kind' => 'group',
|
||||
'data' => [
|
||||
'members' => [
|
||||
[
|
||||
'kind' => 'text',
|
||||
'data' => [
|
||||
'content' => 'qux'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
$assert->equal($output_actual, $output_expected);
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
309
lib/alveolata/http/functions.php
Normal file
309
lib/alveolata/http/functions.php
Normal file
|
@ -0,0 +1,309 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\http;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/http/types.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function method_to_oas(
|
||||
string $http_method
|
||||
) : string
|
||||
{
|
||||
return \strtolower($http_method);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @return \alveolata\http\struct_request
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function request_decode(
|
||||
string $input
|
||||
) : \alveolata\http\struct_request
|
||||
{
|
||||
$input_ = str_replace("\r\n", "\n", $input);
|
||||
$parts = explode("\n", $input_);
|
||||
$chunks = [];
|
||||
$request_method = null;
|
||||
$request_path = null;
|
||||
$request_protocol = null;
|
||||
$request_headers = [];
|
||||
$request_body = '';
|
||||
foreach ($parts as $part) {
|
||||
if (preg_match('/^([A-Z]+) ([\S]*) (.*)$/', $part, $matches) === 1) {
|
||||
$request_method = strtoupper($matches[1]);
|
||||
$request_path = $matches[2];
|
||||
$request_protocol = $matches[3];
|
||||
}
|
||||
else if (preg_match('/^([^:]+): (.*)$/', $part, $matches) === 1) {
|
||||
$request_headers[$matches[1]] = $matches[2];
|
||||
}
|
||||
else if ($part !== '') {
|
||||
array_push($chunks, $part);
|
||||
}
|
||||
else {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
$request_body .= implode("\n", $chunks);
|
||||
$request = new \alveolata\http\struct_request(
|
||||
$request_protocol,
|
||||
$request_method,
|
||||
$request_path,
|
||||
$request_headers,
|
||||
$request_body
|
||||
);
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\http\struct_response $response
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function response_encode(
|
||||
\alveolata\http\struct_response $response
|
||||
) : string
|
||||
{
|
||||
$code_text_map = [
|
||||
'100' => 'Continue',
|
||||
'101' => 'Switching Protocols',
|
||||
'103' => 'Early Hints',
|
||||
'200' => 'OK',
|
||||
'201' => 'Created',
|
||||
'202' => 'Accepted',
|
||||
'203' => 'Non-Authoritative Information',
|
||||
'204' => 'No Content',
|
||||
'205' => 'Reset Content',
|
||||
'206' => 'Partial Content',
|
||||
'300' => 'Multiple Choices',
|
||||
'301' => 'Moved Permanently',
|
||||
'302' => 'Found',
|
||||
'303' => 'See Other',
|
||||
'304' => 'Not Modified',
|
||||
'307' => 'Temporary Redirect',
|
||||
'308' => 'Permanent Redirect',
|
||||
'400' => 'Bad Request',
|
||||
'401' => 'Unauthorized',
|
||||
'402' => 'Payment Required',
|
||||
'403' => 'Forbidden',
|
||||
'404' => 'Not Found',
|
||||
'405' => 'Method Not Allowed',
|
||||
'406' => 'Not Acceptable',
|
||||
'407' => 'Proxy Authentication Required',
|
||||
'408' => 'Request Timeout',
|
||||
'409' => 'Conflict',
|
||||
'410' => 'Gone',
|
||||
'411' => 'Length Required',
|
||||
'412' => 'Precondition Failed',
|
||||
'413' => 'Payload Too Large',
|
||||
'414' => 'URI Too Long',
|
||||
'415' => 'Unsupported Media Type',
|
||||
'416' => 'Range Not Satisfiable',
|
||||
'417' => 'Expectation Failed',
|
||||
'418' => 'I\'m a teapot',
|
||||
'422' => 'Unprocessable Entity',
|
||||
'424' => 'Failed Dependency',
|
||||
'425' => 'Too Early',
|
||||
'426' => 'Upgrade Required',
|
||||
'428' => 'Precondition Required',
|
||||
'429' => 'Too Many Requests',
|
||||
'431' => 'Request Header Fields Too Large',
|
||||
'451' => 'Unavailable For Legal Reasons',
|
||||
'500' => 'Internal Server Error',
|
||||
'501' => 'Not Implemented',
|
||||
'502' => 'Bad Gateway',
|
||||
'503' => 'Service Unavailable',
|
||||
'504' => 'Gateway Timeout',
|
||||
'505' => 'HTTP Version Not Supported',
|
||||
'506' => 'Variant Also Negotiates',
|
||||
'507' => 'Insufficient Storage',
|
||||
'508' => 'Loop Detected',
|
||||
'510' => 'Not Extended',
|
||||
'511' => 'Network Authentication Required',
|
||||
];
|
||||
$output = implode(
|
||||
"\r\n",
|
||||
array_merge(
|
||||
[
|
||||
sprintf('HTTP/1.1 %d %s', $response->statuscode, $code_text_map[strval($response->statuscode)]),
|
||||
],
|
||||
array_map(
|
||||
function ($key) use (&$response) {
|
||||
$value = $response->headers[$key];
|
||||
return sprintf('%s: %s', $key, $value);
|
||||
},
|
||||
array_keys($response->headers)
|
||||
),
|
||||
[
|
||||
'',
|
||||
$response->body,
|
||||
]
|
||||
)
|
||||
);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* sends an HTTP request and returns the response
|
||||
* requires CURL plugin
|
||||
*
|
||||
* @param array $options {
|
||||
* record<
|
||||
* timeout ?: float,
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
function call(
|
||||
\alveolata\http\struct_request $request,
|
||||
array $options = []
|
||||
): \alveolata\http\struct_response
|
||||
{
|
||||
$options = \array_merge(
|
||||
[
|
||||
'timeout' => 5.0
|
||||
],
|
||||
$options
|
||||
);
|
||||
|
||||
\alveolata\log\debug(
|
||||
'http_call_request',
|
||||
[
|
||||
'request' => [
|
||||
'protocol' => $request->protocol,
|
||||
'method' => $request->method,
|
||||
'target' => $request->target,
|
||||
'headers' => $request->headers,
|
||||
'body' => $request->body,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$request_headers_transformed = [];
|
||||
foreach ($request->headers as $key => $value) {
|
||||
\array_push($request_headers_transformed, \sprintf('%s: %s', $key, $value));
|
||||
}
|
||||
|
||||
$curl_object = \curl_init();
|
||||
|
||||
// set common options
|
||||
\curl_setopt($curl_object, \CURLOPT_CONNECTTIMEOUT, $options['timeout']);
|
||||
\curl_setopt($curl_object, \CURLOPT_TIMEOUT, $options['timeout']);
|
||||
\curl_setopt($curl_object, \CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
// set request options
|
||||
\curl_setopt($curl_object, \CURLOPT_URL, $request->target);
|
||||
\curl_setopt($curl_object, \CURLOPT_CUSTOMREQUEST, $request->method);
|
||||
\curl_setopt($curl_object, \CURLINFO_HEADER_OUT, true);
|
||||
\curl_setopt($curl_object, \CURLOPT_HTTPHEADER, $request_headers_transformed);
|
||||
\curl_setopt($curl_object, \CURLOPT_SSL_VERIFYPEER, /*$options['sslVerifyPeer']*/1);
|
||||
\curl_setopt($curl_object, \CURLOPT_SSL_VERIFYHOST, /*$options['sslVerifyHost']*/2);
|
||||
|
||||
if (
|
||||
\in_array(
|
||||
$request->method,
|
||||
[
|
||||
\alveolata\http\enum_method::post,
|
||||
\alveolata\http\enum_method::put,
|
||||
\alveolata\http\enum_method::patch,
|
||||
]
|
||||
)
|
||||
) {
|
||||
\curl_setopt($curl_object, \CURLOPT_POSTFIELDS, $request->body);
|
||||
}
|
||||
|
||||
// set response options
|
||||
\curl_setopt($curl_object, \CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($curl_object, \CURLOPT_HEADER, 1);
|
||||
|
||||
$response_raw = \curl_exec($curl_object);
|
||||
if ($response_raw === false) {
|
||||
$error_number = \curl_errno($curl_object);
|
||||
$error_message = \curl_error($curl_object);
|
||||
\curl_close($curl_object);
|
||||
throw (new \Exception(
|
||||
\sprintf(
|
||||
'%s | %s',
|
||||
'http_call_failed',
|
||||
\sprintf(
|
||||
\json_encode(
|
||||
[
|
||||
'error_number' => $error_number,
|
||||
'error_message' => $error_message,
|
||||
'method' => $request->method,
|
||||
'target' => $request->target,
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
else {
|
||||
$header_size = \curl_getinfo($curl_object, \CURLINFO_HEADER_SIZE);
|
||||
// statuscode
|
||||
{
|
||||
$statuscode = \intval(\curl_getinfo($curl_object, \CURLINFO_HTTP_CODE));
|
||||
}
|
||||
// headers
|
||||
{
|
||||
$headers_raw = \substr($response_raw, 0, $header_size);
|
||||
$header_parts = \explode("\r\n", $headers_raw);
|
||||
// throw away first part, containing the status code information
|
||||
\array_shift($header_parts);
|
||||
$headers = [];
|
||||
foreach ($header_parts as $part) {
|
||||
if ($part === '') {
|
||||
// do nothing
|
||||
}
|
||||
else if (\preg_match('/^HTTP\/.* [0-9]{3} .*$/', $part) === 1) {
|
||||
// e.g. "HTTP\/1.1 200 OK"
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
$funds = null;
|
||||
$matching = (\preg_match('/([^:]*): (.*)/', $part, $funds) === 1);
|
||||
if (! $matching) {
|
||||
\alveolata\log\warning(
|
||||
'http_call_malformed_header',
|
||||
[
|
||||
'header' => $part,
|
||||
]
|
||||
);
|
||||
}
|
||||
else {
|
||||
$key = $funds[1];
|
||||
$value = $funds[2];
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// body
|
||||
{
|
||||
$body = \substr($response_raw, $header_size);
|
||||
}
|
||||
\curl_close($curl_object);
|
||||
$response = new \alveolata\http\struct_response($statuscode, $headers, $body);
|
||||
|
||||
\alveolata\log\debug(
|
||||
'http_call_response',
|
||||
[
|
||||
'response' => [
|
||||
'statuscode' => $response->statuscode,
|
||||
'headers' => $response->headers,
|
||||
'body' => $response->body,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
59
lib/alveolata/http/test.spec.php
Normal file
59
lib/alveolata/http/test.spec.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/http/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'http',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'request_decode',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'test1',
|
||||
'procedure' => function ($assert, &$environment) {
|
||||
$input = 'OPTIONS /server/index.php/session HTTP/1.1
|
||||
Host: localhost:1919
|
||||
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0
|
||||
Accept: */*
|
||||
Accept-Language: de,en-US;q=0.7,en;q=0.3
|
||||
Accept-Encoding: gzip, deflate
|
||||
Access-Control-Request-Method: POST
|
||||
Access-Control-Request-Headers: content-type
|
||||
Referer: http://localhost:8888/
|
||||
Origin: http://localhost:8888
|
||||
Connection: keep-alive
|
||||
|
||||
foo
|
||||
bar
|
||||
';
|
||||
// execution
|
||||
{
|
||||
$request = \alveolata\http\request_decode($input);
|
||||
}
|
||||
// assertions
|
||||
{
|
||||
$assert->equal($request->protocol, 'HTTP/1.1');
|
||||
$assert->equal($request->method, 'OPTIONS');
|
||||
$assert->equal($request->target, '/server/index.php/session');
|
||||
$assert->equal($request->headers['Host'], 'localhost:1919');
|
||||
$assert->equal($request->headers['User-Agent'], 'Mozilla/5.0 (X11; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0');
|
||||
$assert->equal($request->headers['Accept'], '*/*');
|
||||
$assert->equal($request->body, "foo\nbar");
|
||||
}
|
||||
},
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
115
lib/alveolata/http/types.php
Normal file
115
lib/alveolata/http/types.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\http;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class enum_method
|
||||
{
|
||||
public const options = 'OPTIONS';
|
||||
public const head = 'HEAD';
|
||||
public const get = 'GET';
|
||||
public const post = 'POST';
|
||||
public const put = 'PUT';
|
||||
public const delete = 'DELETE';
|
||||
public const patch = 'PATCH';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct_request
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $protocol;
|
||||
|
||||
|
||||
/**
|
||||
* @var string (enum_method)
|
||||
*/
|
||||
public $method;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $target;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {map<string,string>}
|
||||
*/
|
||||
public $headers;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $body;
|
||||
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
public function __construct(
|
||||
string $protocol,
|
||||
string $method,
|
||||
string $target,
|
||||
array $headers,
|
||||
string $body
|
||||
)
|
||||
{
|
||||
$this->protocol = $protocol;
|
||||
$this->method = $method;
|
||||
$this->target = $target;
|
||||
$this->headers = $headers;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct_response
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $statuscode;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {map<string,string>}
|
||||
*/
|
||||
public $headers;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $body;
|
||||
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
public function __construct(
|
||||
int $statuscode,
|
||||
array $headers,
|
||||
string $body
|
||||
)
|
||||
{
|
||||
$this->statuscode = $statuscode;
|
||||
$this->headers = $headers;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
46
lib/alveolata/json/functions.php
Normal file
46
lib/alveolata/json/functions.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\json;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function encode(
|
||||
$value,
|
||||
bool $formatted = false
|
||||
) : string
|
||||
{
|
||||
$string = json_encode($value, $formatted ? JSON_PRETTY_PRINT : 0);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw (new \Exception('json not encodable: ' . json_last_error_msg()));
|
||||
}
|
||||
else {
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return mixed
|
||||
* @throws \Exception if not decodable
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function decode(
|
||||
string $string
|
||||
)
|
||||
{
|
||||
$value = json_decode($string, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw (new \Exception('json not decodable: ' . json_last_error_msg()));
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
317
lib/alveolata/list/functions.php
Normal file
317
lib/alveolata/list/functions.php
Normal file
|
@ -0,0 +1,317 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\list_;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* returns a list of integers from 0 to length-1
|
||||
*
|
||||
* @param int $length {integer}
|
||||
* @return array {list<integer>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sequence(
|
||||
int $length
|
||||
) : array
|
||||
{
|
||||
return (
|
||||
($length <= 0)
|
||||
? []
|
||||
: array_merge(sequence($length-1), [$length-1])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list1 {list<§x>}
|
||||
* @param array $list2 {list<§x>}
|
||||
* @return array {list<§x>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function concat(
|
||||
array $list1,
|
||||
array $list2
|
||||
) : array
|
||||
{
|
||||
return array_merge($list1, $list2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $predicate {function<§x,boolean>}
|
||||
* @return array {list<§x>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function filter(
|
||||
array $list,
|
||||
\Closure $predicate
|
||||
) : array
|
||||
{
|
||||
$list_ = [];
|
||||
foreach ($list as $value) {
|
||||
if ($predicate($value)) {
|
||||
array_push($list_, $value);
|
||||
}
|
||||
}
|
||||
return $list_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $transformator {function<§x,§y>}
|
||||
* @return array {list<§y>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function map(
|
||||
array $list,
|
||||
\Closure $transformator
|
||||
) : array
|
||||
{
|
||||
$list_ = [];
|
||||
foreach ($list as $value) {
|
||||
$value_ = $transformator($value);
|
||||
array_push($list_, $value_);
|
||||
}
|
||||
return $list_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param mixed $start {§y}
|
||||
* @param \Closure $aggregator {function<§y,§x,§y>}
|
||||
* @return mixed {§y}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function reduce_left(
|
||||
array $list,
|
||||
$start,
|
||||
\Closure $aggregator
|
||||
)
|
||||
{
|
||||
$result = $start;
|
||||
foreach ($list as $current) {
|
||||
$result = $aggregator($result, $current);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param mixed $start {§y}
|
||||
* @param \Closure $aggregator {function<§x,§y,§y>}
|
||||
* @return mixed {§y}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function reduce_right(
|
||||
array $list,
|
||||
$start,
|
||||
\Closure $aggregator
|
||||
)
|
||||
{
|
||||
$result = $start;
|
||||
foreach (array_reverse($list) as $current) {
|
||||
$result = $aggregator($current, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param mixed $start {§y}
|
||||
* @param \Closure $aggregator {function<§y,§x,§y>}
|
||||
* @return mixed {§y}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function reduce(
|
||||
array $list,
|
||||
$start,
|
||||
\Closure $aggregator
|
||||
)
|
||||
{
|
||||
return reduce_left($list, $start, $aggregator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $predicate {function<§x,bool>}
|
||||
* @return bool {boolean}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function some(
|
||||
array $list,
|
||||
\Closure $predicate
|
||||
) : bool
|
||||
{
|
||||
foreach ($list as $current) {
|
||||
if ($predicate($current)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $predicate {function<§x,bool>}
|
||||
* @return bool {boolean}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function every(
|
||||
array $list,
|
||||
\Closure $predicate
|
||||
) : bool
|
||||
{
|
||||
foreach ($list as $current) {
|
||||
if (! $predicate($current)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list1 {list<§x>}
|
||||
* @param array $list2 {list<§y>}
|
||||
* @param bool $cut {boolean} whether to take the least length in case the lists have different lengths
|
||||
* @return array {list<record<first:§x,second:§y>>}
|
||||
* @throw \Exception if lists have different lengths and $cut = false
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function zip(
|
||||
array $list1,
|
||||
array $list2,
|
||||
bool $cut = true
|
||||
) : array
|
||||
{
|
||||
$l1 = count($list1);
|
||||
$l2 = count($list2);
|
||||
if (! ($l1 === $l2)) {
|
||||
if (! $cut) {
|
||||
throw (new \Exception('lists have different lengths'));
|
||||
}
|
||||
else {
|
||||
$length = $l1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$length = min($l1, $l2);
|
||||
}
|
||||
$list3 = [];
|
||||
for ($index = 0; $index < $length; $index += 1) {
|
||||
$pair = [
|
||||
'first' => $list1[$index],
|
||||
'second' => $list2[$index],
|
||||
];
|
||||
array_push($list3, $pair);
|
||||
}
|
||||
return $list3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $order {function<§x,§x,boolean>}
|
||||
* @return array {list<§x>}
|
||||
* @throws \Exception if the sorting fails
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function sort(
|
||||
array $list,
|
||||
\Closure $order = null
|
||||
) : array
|
||||
{
|
||||
if (is_null($order)) {
|
||||
$order = (function ($x, $y) {return ($x <= $y);});
|
||||
}
|
||||
$copy = array_map(function ($x) {return $x;}, $list);
|
||||
$successful = usort(
|
||||
$copy,
|
||||
function ($x, $y) use ($order) : int {
|
||||
return ($order($x, $y) ? (-1) : (+1));
|
||||
}
|
||||
);
|
||||
if (! $successful) {
|
||||
throw (new \Exception('alveolata_list_sort_failed'));
|
||||
}
|
||||
else {
|
||||
return $copy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $list {list<§x>}
|
||||
* @param \Closure $collation {function<§x,§x,boolean>}
|
||||
* @return array {list<list<§x>>}
|
||||
*/
|
||||
function group(
|
||||
array $list,
|
||||
\Closure $collation
|
||||
) : array
|
||||
{
|
||||
$groups = [];
|
||||
foreach ($list as $element) {
|
||||
$found = false;
|
||||
foreach ($groups as &$group) {
|
||||
if ($collation($group[0], $element)) {
|
||||
array_push($group, $element);
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($group);
|
||||
if (! $found) {
|
||||
$group = [$element];
|
||||
array_push($groups, $group);
|
||||
}
|
||||
}
|
||||
return $groups;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $x {list<type_x>}
|
||||
* @param array $y {list<type_y>}
|
||||
* @param ?array $options {
|
||||
* (
|
||||
* null
|
||||
* |
|
||||
* record<
|
||||
* ?combinator:function<tuple<type_x,type_y>,any>,
|
||||
* >
|
||||
* )
|
||||
* }
|
||||
* @return array {list<tuple<type_x,type_y>>}
|
||||
*/
|
||||
function product(
|
||||
array $x,
|
||||
array $y,
|
||||
?array $options = null
|
||||
) : array
|
||||
{
|
||||
$options = \array_merge(
|
||||
[
|
||||
'combinator' => (fn($u, $v) => [$u, $v])
|
||||
],
|
||||
($options ?? [])
|
||||
);
|
||||
$z = [];
|
||||
foreach ($x as $u) {
|
||||
foreach ($y as $v) {
|
||||
\array_push($z, $options['combinator']($u, $v));
|
||||
}
|
||||
}
|
||||
return $z;
|
||||
}
|
||||
|
||||
?>
|
247
lib/alveolata/list/test.spec.json
Normal file
247
lib/alveolata/list/test.spec.json
Normal file
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"active": true,
|
||||
"sections": [
|
||||
{
|
||||
"name": "sequence",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\sequence({{length}})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty",
|
||||
"active": true,
|
||||
"input": {
|
||||
"length": 0
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non-empty",
|
||||
"active": true,
|
||||
"input": {
|
||||
"length": 5
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": [0,1,2,3,4]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "map",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\map({{list}}, function ($x) {return ($x*2);})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "test",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [0,1,2]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": [0,2,4]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "reduce",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\reduce({{list}}, {{start}}, function ($x, $y) {return ($x-$y);})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty list",
|
||||
"active": true,
|
||||
"input": {
|
||||
"start": 7,
|
||||
"list": []
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty list",
|
||||
"active": true,
|
||||
"input": {
|
||||
"start": 7,
|
||||
"list": [0,1,2]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "some",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\some({{list}}, function ($x) {return (($x % 3) === 0);})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty list",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": []
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty list; negative",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [2,4,5,7]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty list; positive",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [2,3,5,7]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "every",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\every({{list}}, function ($x) {return (($x % 3) === 0);})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty list",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": []
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty list; negative",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [0,3,7,9]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty list; positive",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [0,3,6,9]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\sort({{list}})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": []
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty, positive",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [5,2,3]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": [2,3,5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty, negative",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [-13,-7,-11]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": [-13,-11,-7]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"active": true,
|
||||
"execution": {
|
||||
"call": "\\alveolata\\list_\\group({{list}}, function ($x, $y) {return (($x % 3) === ($y % 3));})"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"name": "empty",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": []
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "non empty",
|
||||
"active": true,
|
||||
"input": {
|
||||
"list": [0,1,2,3,4,5]
|
||||
},
|
||||
"output": {
|
||||
"kind": "regular",
|
||||
"value": [[0,3],[1,4],[2,5]]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
147
lib/alveolata/localization/functions.php
Normal file
147
lib/alveolata/localization/functions.php
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\localization;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class _state {
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static $order = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var map<string,map<string,string>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static $data = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static $suppress_messages = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _add(
|
||||
string $language,
|
||||
string $key,
|
||||
string $value
|
||||
) : void
|
||||
{
|
||||
_state::$data;
|
||||
if (! array_key_exists($language, _state::$data)) {
|
||||
_state::$data[$language] = [];
|
||||
}
|
||||
_state::$data[$language][$key] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function feed(
|
||||
string $language,
|
||||
array $data
|
||||
) : void
|
||||
{
|
||||
array_unshift(_state::$order, $language);
|
||||
foreach ($data as $key => $value) {
|
||||
_add($language, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $language
|
||||
* @param string $key
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _fetch(
|
||||
string $language,
|
||||
string $key
|
||||
) : string
|
||||
{
|
||||
if (! array_key_exists($language, _state::$data)) {
|
||||
if (! _state::$suppress_messages) {
|
||||
\alveolata\log\notice(
|
||||
'no localization data for language',
|
||||
[
|
||||
'language' => $language,
|
||||
]
|
||||
);
|
||||
}
|
||||
return UNSET_STRING;
|
||||
}
|
||||
else {
|
||||
if (! array_key_exists($key, _state::$data[$language])) {
|
||||
if (! _state::$suppress_messages) {
|
||||
\alveolata\log\notice(
|
||||
'no translation for key',
|
||||
[
|
||||
'language' => $language,
|
||||
'key' => $key,
|
||||
]
|
||||
);
|
||||
}
|
||||
return UNSET_STRING;
|
||||
}
|
||||
else {
|
||||
return _state::$data[$language][$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get(
|
||||
string $key,
|
||||
string $fallback = UNSET_STRING
|
||||
) : string
|
||||
{
|
||||
if ($fallback === UNSET_STRING) {
|
||||
$fallback = sprintf('{%s}', $key);
|
||||
}
|
||||
$found = false;
|
||||
foreach (_state::$order as $language) {
|
||||
$value = _fetch($language, $key);
|
||||
if (! ($value === UNSET_STRING)) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $found) {
|
||||
if (! _state::$suppress_messages) {
|
||||
\alveolata\log\warning(
|
||||
'using fallback translation',
|
||||
[
|
||||
'key' => $key,
|
||||
'languages_tried' => _state::$order,
|
||||
]
|
||||
);
|
||||
}
|
||||
return $fallback;
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
61
lib/alveolata/localization/test.spec.php
Normal file
61
lib/alveolata/localization/test.spec.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/localization/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'localization',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'get',
|
||||
'setup' => function (&$environment) {
|
||||
\alveolata\localization\_state::$suppress_messages = true;
|
||||
\alveolata\localization\feed(
|
||||
'eng',
|
||||
[
|
||||
'foo' => 'roses are red',
|
||||
'bar' => 'grass is green',
|
||||
]
|
||||
);
|
||||
\alveolata\localization\feed(
|
||||
'deu',
|
||||
[
|
||||
'foo' => 'Rosen sind rot',
|
||||
]
|
||||
);
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'found at first',
|
||||
'procedure' => function ($assert, &$environment) {
|
||||
$value = \alveolata\localization\get('foo');
|
||||
$assert->equal($value, 'Rosen sind rot');
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'found at second',
|
||||
'procedure' => function ($assert, &$environment) {
|
||||
$value = \alveolata\localization\get('bar');
|
||||
$assert->equal($value, 'grass is green');
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'not found',
|
||||
'procedure' => function ($assert, &$environment) {
|
||||
$value = \alveolata\localization\get('baz');
|
||||
$assert->equal($value, '{baz}');
|
||||
},
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
75
lib/alveolata/log/base.php
Normal file
75
lib/alveolata/log/base.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class enum_level
|
||||
{
|
||||
public const error = 0;
|
||||
public const warning = 1;
|
||||
public const notice = 2;
|
||||
public const info = 3;
|
||||
public const debug = 4;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function level_decode(
|
||||
string $level
|
||||
) : int
|
||||
{
|
||||
$map = [
|
||||
'error' => 0,
|
||||
'warning' => 1,
|
||||
'notice' => 2,
|
||||
'info' => 3,
|
||||
'debug' => 4,
|
||||
];
|
||||
if (! \array_key_exists($level, $map)) {
|
||||
throw (new \Exception(\sprintf('unhandled log level "%s"', $level)));
|
||||
}
|
||||
else {
|
||||
// return \alveolata\string\pad_right(\alveolata\string\case_upper($map[$level]), 7, ' ');
|
||||
return $map[$level];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $level
|
||||
* @return string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function level_encode(
|
||||
int $level
|
||||
) : string
|
||||
{
|
||||
$map = [
|
||||
0 => 'error',
|
||||
1 => 'warning',
|
||||
2 => 'notice',
|
||||
3 => 'info',
|
||||
4 => 'debug',
|
||||
];
|
||||
if (! \array_key_exists($level, $map)) {
|
||||
throw (new \Exception(sprintf('unhandled log output level %u', $level)));
|
||||
}
|
||||
else {
|
||||
// return \alveolata\string\pad_right(\alveolata\string\case_upper($map[$level]), 7, ' ');
|
||||
return $map[$level];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
function output_translate_level($level) {
|
||||
return level_encode($level);
|
||||
}
|
||||
|
||||
?>
|
245
lib/alveolata/log/functions.php
Normal file
245
lib/alveolata/log/functions.php
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/base.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-implementation-restricted.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-implementation-console.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-implementation-file.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-implementation-email.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-implementation-libnotify.php');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @var array list of interface_output
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class _state
|
||||
{
|
||||
public static $outputs = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function add_output(
|
||||
interface_output $output
|
||||
) : void
|
||||
{
|
||||
array_push(_state::$outputs, $output);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function _submit(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
foreach (_state::$outputs as $output) {
|
||||
$output->process($level, $report);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\report\type $report
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function error_(
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
_submit(enum_level::error, $report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $incident
|
||||
* @param map<string,any> [$details]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function error(
|
||||
string $incident,
|
||||
array $details = []
|
||||
) : void
|
||||
{
|
||||
error_(\alveolata\report\make($incident, $details));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\report\type $report
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function warning_(
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
_submit(enum_level::warning, $report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $incident
|
||||
* @param map<string,any> [$details]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function warning(
|
||||
string $incident,
|
||||
array $details = []
|
||||
) : void
|
||||
{
|
||||
warning_(\alveolata\report\make($incident, $details));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\report\type $report
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function notice_(
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
_submit(enum_level::notice, $report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $incident
|
||||
* @param map<string,any> [$details]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function notice(
|
||||
string $incident,
|
||||
array $details = []
|
||||
) : void
|
||||
{
|
||||
notice_(\alveolata\report\make($incident, $details));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\report\type $report
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function info_(
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
_submit(enum_level::info, $report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $incident
|
||||
* @param map<string,any> [$details]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function info(
|
||||
string $incident,
|
||||
array $details = []
|
||||
) : void
|
||||
{
|
||||
info_(\alveolata\report\make($incident, $details));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \alveolata\report\type $report
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function debug_(
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
_submit(enum_level::debug, $report);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $incident
|
||||
* @param map<string,any> [$details]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function debug(
|
||||
string $incident,
|
||||
array $details = []
|
||||
) : void
|
||||
{
|
||||
debug_(\alveolata\report\make($incident, $details));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make_output(
|
||||
string $kind,
|
||||
array $parameters
|
||||
) : interface_output
|
||||
{
|
||||
switch ($kind) {
|
||||
case 'console': {
|
||||
return (
|
||||
new implementation_restricted(
|
||||
new implementation_console(),
|
||||
level_decode($parameters['level_threshold'] ?? 'notice')
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'file': {
|
||||
return (
|
||||
new implementation_restricted(
|
||||
new implementation_file(
|
||||
$parameters['path'],
|
||||
[
|
||||
'human_readable' => ($parameters['human_readable'] ?? false),
|
||||
]
|
||||
),
|
||||
level_decode($parameters['level_threshold'] ?? 'notice')
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'email': {
|
||||
return (
|
||||
new implementation_restricted(
|
||||
new implementation_email(
|
||||
$parameters['auth'],
|
||||
$parameters['receivers'],
|
||||
$parameters['sender'],
|
||||
$parameters['tags'],
|
||||
$parameters['implementation']
|
||||
),
|
||||
level_decode($parameters['level_threshold'] ?? 'notice')
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'libnotify': {
|
||||
return (
|
||||
new implementation_restricted(
|
||||
new implementation_libnotify(),
|
||||
level_decode($parameters['level_threshold'] ?? 'notice')
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw (new \Exception(sprintf('invalid logoutput kind "%s"', $kind)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
55
lib/alveolata/log/output-implementation-console.php
Normal file
55
lib/alveolata/log/output-implementation-console.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/json/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_console implements interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $level_threshold
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
$message = \alveolata\string\coin(
|
||||
(
|
||||
empty($report->details)
|
||||
? '<{{datetime}}> [{{level}}] {{incident}}'
|
||||
: '<{{datetime}}> [{{level}}] {{incident}} | {{details}}'
|
||||
),
|
||||
[
|
||||
'datetime' => date('Y-m-d|H:i:s', $report->timestamp),
|
||||
'level' => output_translate_level($level),
|
||||
'incident' => $report->incident,
|
||||
'details' => \alveolata\json\encode($report->details),
|
||||
]
|
||||
);
|
||||
error_log($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
139
lib/alveolata/log/output-implementation-email.php
Normal file
139
lib/alveolata/log/output-implementation-email.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
require_once(DIR_ALVEOLATA . '/email/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_email implements interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @var $auth {
|
||||
* record<
|
||||
* host:string,
|
||||
* port:integer,
|
||||
* authtype:string,
|
||||
* username:string,
|
||||
* password:string
|
||||
* >
|
||||
* }
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {list<string>}
|
||||
*/
|
||||
private $receivers;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sender;
|
||||
|
||||
|
||||
/**
|
||||
* @var array {list<string>}
|
||||
*/
|
||||
private $tags;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $implementation;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
array $auth,
|
||||
array $receivers,
|
||||
string $sender,
|
||||
array $tags,
|
||||
string $implementation
|
||||
)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->receivers = $receivers;
|
||||
$this->sender = $sender;
|
||||
$this->tags = $tags;
|
||||
$this->implementation = $implementation;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
$subject = \alveolata\string\coin(
|
||||
'{{tags}} {{level}}: {{incident}}',
|
||||
[
|
||||
'tags' => \alveolata\string\join(
|
||||
\alveolata\list_\map(
|
||||
$this->tags,
|
||||
function (string $tag): string {
|
||||
return \alveolata\string\coin('[{{tag}}]', ['tag' => $tag]);
|
||||
}
|
||||
),
|
||||
' '
|
||||
),
|
||||
'level' => output_translate_level($level),
|
||||
'incident' => $report->incident,
|
||||
]
|
||||
);
|
||||
$body_plain = \alveolata\string\coin(
|
||||
"<{{timestamp}}>\n{{details}}",
|
||||
[
|
||||
'timestamp' => date('Y-m-d|H:i:s', $report->timestamp),
|
||||
'details' => implode(
|
||||
"\n",
|
||||
\alveolata\list_\map(
|
||||
array_keys($report->details),
|
||||
function ($key) use ($report) {
|
||||
$value = $report->details[$key];
|
||||
return \alveolata\string\coin(
|
||||
'{{key}}: {{value}}',
|
||||
[
|
||||
'key' => $key,
|
||||
'value' => json_encode($value),
|
||||
]
|
||||
);
|
||||
}
|
||||
)
|
||||
),
|
||||
]
|
||||
);
|
||||
\alveolata\email\send(
|
||||
$this->auth,
|
||||
[
|
||||
'to' => $this->receivers,
|
||||
'from' => $this->sender,
|
||||
'subject' => $subject,
|
||||
'body_plain' => $body_plain,
|
||||
],
|
||||
[
|
||||
'implementation' => $this->implementation
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
99
lib/alveolata/log/output-implementation-file.php
Normal file
99
lib/alveolata/log/output-implementation-file.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_file implements interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $human_readable;
|
||||
|
||||
|
||||
/**
|
||||
* @param int $level_threshold
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
string $path,
|
||||
array $options = []
|
||||
)
|
||||
{
|
||||
$options = \array_merge(
|
||||
[
|
||||
'human_readable' => false,
|
||||
],
|
||||
$options
|
||||
);
|
||||
$this->path = $path;
|
||||
$this->human_readable = $options['human_readable'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
$content = (
|
||||
$this->human_readable
|
||||
? \alveolata\string\coin(
|
||||
(
|
||||
empty($report->details)
|
||||
? "<{{datetime}}> [{{level}}] {{incident}}\n"
|
||||
: "<{{datetime}}> [{{level}}] {{incident}} | {{details}}\n"
|
||||
),
|
||||
[
|
||||
'datetime' => date('Y-m-d|H:i:s', $report->timestamp),
|
||||
'level' => output_translate_level($level),
|
||||
'incident' => $report->incident,
|
||||
'details' => \alveolata\json\encode($report->details),
|
||||
]
|
||||
)
|
||||
: (
|
||||
\alveolata\json\encode(
|
||||
[
|
||||
'timestamp' => $report->timestamp,
|
||||
'datetime' => date('c', $report->timestamp),
|
||||
'level_value' => $level,
|
||||
'level_name' => output_translate_level($level),
|
||||
'incident' => $report->incident,
|
||||
'details' => $report->details,
|
||||
]
|
||||
)
|
||||
.
|
||||
"\n"
|
||||
)
|
||||
);
|
||||
file_put_contents(
|
||||
$this->path,
|
||||
$content,
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
63
lib/alveolata/log/output-implementation-libnotify.php
Normal file
63
lib/alveolata/log/output-implementation-libnotify.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_libnotify implements interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @param int $level_threshold
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
$command = \alveolata\string\coin(
|
||||
'notify-send \'[{{level}}] {{incident}}\' \'{{details}}\'',
|
||||
[
|
||||
'level' => output_translate_level($level),
|
||||
'incident' => $report->incident,
|
||||
'details' => implode(
|
||||
"\n",
|
||||
array_map(
|
||||
function ($key) use (&$report) {
|
||||
$value = $report->details[$key];
|
||||
return \alveolata\string\coin(
|
||||
'{{key}}: {{value}}',
|
||||
[
|
||||
'key' => $key,
|
||||
'value' => json_encode($value),
|
||||
]
|
||||
);
|
||||
},
|
||||
array_keys($report->details)
|
||||
)
|
||||
),
|
||||
]
|
||||
);
|
||||
exec($command);
|
||||
}
|
||||
|
||||
}
|
||||
|
65
lib/alveolata/log/output-implementation-restricted.php
Normal file
65
lib/alveolata/log/output-implementation-restricted.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/json/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/output-interface.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class implementation_restricted implements interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @var interface_output
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
protected $core;
|
||||
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
protected $level_threshold;
|
||||
|
||||
|
||||
/**
|
||||
* @param int $level_threshold
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function __construct(
|
||||
interface_output $core,
|
||||
int $level_threshold
|
||||
)
|
||||
{
|
||||
$this->core = $core;
|
||||
$this->level_threshold = $level_threshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @implementation
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
{
|
||||
if ($level <= $this->level_threshold) {
|
||||
$this->core->process($level, $report);
|
||||
}
|
||||
else {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
25
lib/alveolata/log/output-interface.php
Normal file
25
lib/alveolata/log/output-interface.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\log;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
require_once(DIR_ALVEOLATA . '/string/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/report/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
interface interface_output
|
||||
{
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function process(
|
||||
int $level,
|
||||
\alveolata\report\struct_report $report
|
||||
) : void
|
||||
;
|
||||
|
||||
}
|
11
lib/alveolata/main.php
Normal file
11
lib/alveolata/main.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
require_once('definitions.php');
|
||||
require_once('test.php');
|
||||
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
||||
|
||||
|
||||
// \alveolata\log\add_output(\alveolata\log\make_output('console', ['level_threshold' => 4]));
|
||||
// \alveolata\log\add_output(\alveolata\log\make_output('libnotify', ['level_threshold' => 2]));
|
||||
\alveolata\test\run(isset($argv[1]) ? explode('.', $argv[1]) : []);
|
||||
|
61
lib/alveolata/map/functions.php
Normal file
61
lib/alveolata/map/functions.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\map;
|
||||
|
||||
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @param array $map {map<string,§y>}
|
||||
* @param \Closure $transformator {function<§x,string,§y>}
|
||||
* @return array
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function map/*<§x,§y>*/(
|
||||
array $map,
|
||||
\Closure $transformator
|
||||
) : array
|
||||
{
|
||||
$map_ = [];
|
||||
foreach ($map as $key => $value) {
|
||||
$value_ = $transformator($value, $key);
|
||||
$map_[$key] = $value_;
|
||||
}
|
||||
return $map_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $pairs {list<record<key:string,value:§x>>}
|
||||
* @return array {map<string,§x>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function from_pairs/*<§x>*/(
|
||||
array $pairs
|
||||
) : array
|
||||
{
|
||||
$map = [];
|
||||
foreach ($pairs as $pair) {
|
||||
$map[$pair['key']] = $pair['value'];
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $map {map<string,§x>}
|
||||
* @return array {list<record<key:string,value:§x>>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function to_pairs/*<§x>*/(
|
||||
array $map
|
||||
) : array
|
||||
{
|
||||
$pairs = [];
|
||||
foreach ($map as $key => $value) {
|
||||
\array_push($pairs, ['key' => $key, 'value' => $value]);
|
||||
}
|
||||
return $pairs;
|
||||
}
|
||||
|
||||
?>
|
156
lib/alveolata/markdown/functions.php
Normal file
156
lib/alveolata/markdown/functions.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\markdown;
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.markdownguide.org/basic-syntax/#escaping-characters
|
||||
*/
|
||||
function escape(
|
||||
string $string
|
||||
) : string
|
||||
{
|
||||
$replacements = [
|
||||
['pattern' => "/\n([ ]*)-/", 'replacement' => "\n$1\\-"],
|
||||
['pattern' => "/_/", 'replacement' => "\\_"],
|
||||
['pattern' => "/\|/", 'replacement' => "\\\|"],
|
||||
['pattern' => "/\*/", 'replacement' => "\\\*"],
|
||||
['pattern' => "/\[/", 'replacement' => "\\\["],
|
||||
['pattern' => "/\]/", 'replacement' => "\\\]"],
|
||||
];
|
||||
$result = $string;
|
||||
foreach ($replacements as $replacement) {
|
||||
$result = \preg_replace($replacement['pattern'], $replacement['replacement'], $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function bold(
|
||||
string $piece
|
||||
) : string
|
||||
{
|
||||
return sprintf('__%s__', $piece);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function link(
|
||||
string $label,
|
||||
string $url
|
||||
) : string
|
||||
{
|
||||
return sprintf('[%s](%s)', $label, $url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function image(
|
||||
string $label,
|
||||
string $url
|
||||
) : string
|
||||
{
|
||||
return sprintf('', $label, $url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function headline(
|
||||
int $level,
|
||||
string $string
|
||||
) : string
|
||||
{
|
||||
if ($level < 1) {
|
||||
throw (new \Exception(sprintf('invalid level: %d', $level)));
|
||||
}
|
||||
return sprintf("%s %s\n\n", str_repeat('#', $level), $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function sectionend(
|
||||
) : string
|
||||
{
|
||||
return sprintf("\n\n");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function paragraph(
|
||||
string $string
|
||||
) : string
|
||||
{
|
||||
return sprintf("%s\n\n", $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function code(
|
||||
string $string
|
||||
) : string
|
||||
{
|
||||
return sprintf("```\n%s\n```", $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function item(
|
||||
int $level,
|
||||
string $string
|
||||
) : string
|
||||
{
|
||||
if ($level < 1) {
|
||||
throw (new \Exception(sprintf('invalid level: %d', $level)));
|
||||
}
|
||||
return sprintf("%s- %s\n", str_repeat(' ', $level-1), $string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function rule(
|
||||
) : string
|
||||
{
|
||||
return sprintf("---\n\n");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param null|array $titles {null|list<string>}
|
||||
* @param array $data {list<list<string>>}
|
||||
*/
|
||||
function table(
|
||||
array $titles,
|
||||
array $data
|
||||
) : string
|
||||
{
|
||||
$md = '';
|
||||
if (! is_null($titles)) {
|
||||
$md .= sprintf(
|
||||
"| %s |\n",
|
||||
implode(' | ', $titles)
|
||||
);
|
||||
$md .= sprintf(
|
||||
"| %s |\n",
|
||||
implode(' | ', array_map(function (string $title) {return ':--';}, $titles))
|
||||
);
|
||||
}
|
||||
foreach ($data as $line) {
|
||||
$md .= sprintf(
|
||||
"| %s |\n",
|
||||
implode(' | ', $line)
|
||||
);
|
||||
}
|
||||
return $md;
|
||||
}
|
||||
|
||||
?>
|
34
lib/alveolata/markdown/test.spec.php
Normal file
34
lib/alveolata/markdown/test.spec.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/markdown/functions.php');
|
||||
|
||||
|
||||
$data = \alveolata\test\get_data();
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'markdown',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'escape',
|
||||
'cases' => \array_map(
|
||||
fn($case) => [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\markdown\escape($case['input']);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
],
|
||||
$data['escape']['cases']
|
||||
),
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
37
lib/alveolata/markdown/testdata.json
Normal file
37
lib/alveolata/markdown/testdata.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"escape": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "list item 1",
|
||||
"input": "foo-bar",
|
||||
"output": "foo-bar"
|
||||
},
|
||||
{
|
||||
"name": "list item 2",
|
||||
"input": "foo\n- bar",
|
||||
"output": "foo\n\\- bar"
|
||||
},
|
||||
{
|
||||
"name": "list item 3",
|
||||
"input": "foo\n- bar\n - baz",
|
||||
"output": "foo\n\\- bar\n \\- baz"
|
||||
},
|
||||
{
|
||||
"name": "underscore 1",
|
||||
"input": "foo_bar",
|
||||
"output": "foo\\_bar"
|
||||
},
|
||||
{
|
||||
"name": "pipe 1",
|
||||
"input": "foo|bar",
|
||||
"output": "foo\\|bar"
|
||||
},
|
||||
{
|
||||
"name": "oblique",
|
||||
"input": "jean-claude: twink]e, twink]e, little *, how | w{}nder, wha+ you are!",
|
||||
"output": "jean-claude: twink\\]e, twink\\]e, little \\*, how \\| w{}nder, wha+ you are!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
233
lib/alveolata/math/functions.php
Normal file
233
lib/alveolata/math/functions.php
Normal file
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\math;
|
||||
|
||||
|
||||
/**
|
||||
* modulo operator
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function mod(
|
||||
int $number,
|
||||
int $modulus
|
||||
) : int
|
||||
{
|
||||
// return (x - (div(x, y) * y));
|
||||
if ($modulus <= 0) {
|
||||
throw (new \Exception('invalid divisor'));
|
||||
}
|
||||
else {
|
||||
return (
|
||||
($number >= 0)
|
||||
? ($number%$modulus)
|
||||
: (($modulus-((-$number)%$modulus))%$modulus)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* computes the power of a number in a residue class ring via square and multiply
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function modpow(
|
||||
int $base,
|
||||
int $exponent,
|
||||
int $modulus
|
||||
) : int
|
||||
{
|
||||
if ($exponent === 0) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$a = modpow($base, $exponent >> 1, $modulus);
|
||||
$a = mod(($a * $a), $modulus);
|
||||
if (($exponent & 1) != 0) {
|
||||
$a = mod(($a * $base), $modulus);
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeElement
|
||||
* @param \Closure {function<pair<type_element,type_element>,boolean>}
|
||||
* @param array $set1 {list<type_element>}
|
||||
* @param array $set2 {list<type_element>}
|
||||
* @return array {list<type_element>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_sub/*<type_element>*/(
|
||||
\Closure $collation,
|
||||
array $set1,
|
||||
array $set2
|
||||
) : bool
|
||||
{
|
||||
foreach ($set1 as $element1) {
|
||||
$present = false;
|
||||
foreach ($set2 as $element2) {
|
||||
if ($collation($element1, $element2)) {
|
||||
$present = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $present) {
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure function<pair<type_element,type_element>,boolean>
|
||||
* @param array $set1 {list<type_element>}
|
||||
* @param array $set2 {list<type_element>}
|
||||
* @return array {list<type_element>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_union/*<type_element>*/(
|
||||
\Closure $collation,
|
||||
array $set1,
|
||||
array $set2
|
||||
) : array
|
||||
{
|
||||
$set3 = [];
|
||||
foreach ([$set1, $set2] as $source) {
|
||||
foreach ($source as $element) {
|
||||
$add = true;
|
||||
foreach ($set3 as $element_) {
|
||||
if ($collation($element, $element_)) {
|
||||
$add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($add) {
|
||||
array_push($set3, $element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $set3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure function<pair<type_element,type_element>,boolean>
|
||||
* @param array $set1 {list<type_element>}
|
||||
* @param array $set2 {list<type_element>}
|
||||
* @return array {list<type_element>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_intersection/*<type_element>*/(
|
||||
\Closure $collation,
|
||||
array $set1,
|
||||
array $set2
|
||||
) : array
|
||||
{
|
||||
$set3 = [];
|
||||
foreach ($set1 as $element1) {
|
||||
$add = false;
|
||||
foreach ($set2 as $element2) {
|
||||
if ($collation($element1, $element2)) {
|
||||
$add = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($add) {
|
||||
array_push($set3, $element1);
|
||||
}
|
||||
}
|
||||
return $set3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Closure function<pair<type_element,type_element>,boolean>
|
||||
* @param array $set1 {list<type_element>}
|
||||
* @param array $set2 {list<type_element>}
|
||||
* @return array {list<type_element>}
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function set_difference/*<type_element>*/(
|
||||
\Closure $collation,
|
||||
array $set1,
|
||||
array $set2
|
||||
) : array
|
||||
{
|
||||
$set3 = [];
|
||||
foreach ($set1 as $element1) {
|
||||
$add = true;
|
||||
foreach ($set2 as $element2) {
|
||||
if ($collation($element1, $element2)) {
|
||||
$add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($add) {
|
||||
array_push($set3, $element1);
|
||||
}
|
||||
}
|
||||
return $set3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* applies the lexicographic order
|
||||
*
|
||||
* @template type_element
|
||||
* @param order Closure {function<type_element,type_element,boolean>}
|
||||
* @param array $list1 {list<type_element>}
|
||||
* @param array $list2 {list<type_element>}
|
||||
* @return boolean
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function order_lexicographic(
|
||||
\Closure $order,
|
||||
array $list1,
|
||||
array $list2
|
||||
) : bool
|
||||
{
|
||||
if (empty($list1)) {
|
||||
if (empty($list2)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (empty($list2)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$le = $order($list1[0], $list2[0]);
|
||||
$ge = $order($list2[0], $list1[0]);
|
||||
if (! $le) {
|
||||
if (! $ge) {
|
||||
// impossible: badly defined order
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (! $ge) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return order_lexicographic(
|
||||
$order,
|
||||
array_slice($list1, 1),
|
||||
array_slice($list2, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
139
lib/alveolata/math/test.spec.php
Normal file
139
lib/alveolata/math/test.spec.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/math/functions.php');
|
||||
|
||||
|
||||
$data = \alveolata\test\get_data();
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'math',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'mod',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\mod(
|
||||
$case['input']['divident'],
|
||||
$case['input']['divisor']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['mod']['cases']
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => 'modpow',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\modpow(
|
||||
$case['input']['base'],
|
||||
$case['input']['exponent'],
|
||||
$case['input']['modulus']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['modpow']['cases']
|
||||
),
|
||||
],
|
||||
[
|
||||
'name' => 'set_union',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\set_union(
|
||||
function ($x, $y) {return ($x === $y);},
|
||||
$case['input']['set1'],
|
||||
$case['input']['set2']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['set_union']['cases']
|
||||
)
|
||||
],
|
||||
[
|
||||
'name' => 'set_intersection',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\set_intersection(
|
||||
function ($x, $y) {return ($x === $y);},
|
||||
$case['input']['set1'],
|
||||
$case['input']['set2']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['set_intersection']['cases']
|
||||
)
|
||||
],
|
||||
[
|
||||
'name' => 'set_difference',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\set_difference(
|
||||
function ($x, $y) {return ($x === $y);},
|
||||
$case['input']['set1'],
|
||||
$case['input']['set2']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['set_difference']['cases']
|
||||
)
|
||||
],
|
||||
[
|
||||
'name' => 'order_lexicographic',
|
||||
'cases' => array_map(
|
||||
function (array $case) : array {
|
||||
return [
|
||||
'name' => $case['name'],
|
||||
'procedure' => function ($assert) use ($case) {
|
||||
$result_actual = \alveolata\math\order_lexicographic(
|
||||
(function ($x, $y) {return ($x <= $y);}),
|
||||
$case['input']['list1'],
|
||||
$case['input']['list2']
|
||||
);
|
||||
$result_expected = $case['output'];
|
||||
$assert->equal($result_actual, $result_expected);
|
||||
},
|
||||
];
|
||||
},
|
||||
$data['order_lexicographic']['cases']
|
||||
)
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
188
lib/alveolata/math/testdata.json
Normal file
188
lib/alveolata/math/testdata.json
Normal file
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"mod": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "positive divident, regular",
|
||||
"input": {
|
||||
"divident": 13,
|
||||
"divisor": 5
|
||||
},
|
||||
"output": 3
|
||||
},
|
||||
{
|
||||
"name": "positive divident, lower edge case",
|
||||
"input": {
|
||||
"divident": 0,
|
||||
"divisor": 5
|
||||
},
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"name": "positive divident, upper edge case",
|
||||
"input": {
|
||||
"divident": 5,
|
||||
"divisor": 5
|
||||
},
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"name": "negative divident",
|
||||
"input": {
|
||||
"divident": -13,
|
||||
"divisor": 5
|
||||
},
|
||||
"output": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"modpow": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "test",
|
||||
"input": {
|
||||
"base": 23,
|
||||
"exponent": 5,
|
||||
"modulus": 31
|
||||
},
|
||||
"output": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
"set_union": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "test1",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [0,1]
|
||||
},
|
||||
"output": [0,1]
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [1,2]
|
||||
},
|
||||
"output": [0,1,2]
|
||||
},
|
||||
{
|
||||
"name": "test3",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [2,3]
|
||||
},
|
||||
"output": [0,1,2,3]
|
||||
}
|
||||
]
|
||||
},
|
||||
"set_intersection": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "test1",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [0,1]
|
||||
},
|
||||
"output": [0,1]
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [1,2]
|
||||
},
|
||||
"output": [1]
|
||||
},
|
||||
{
|
||||
"name": "test3",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [2,3]
|
||||
},
|
||||
"output": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"set_difference": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "test1",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [0,1]
|
||||
},
|
||||
"output": []
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [1,2]
|
||||
},
|
||||
"output": [0]
|
||||
},
|
||||
{
|
||||
"name": "test3",
|
||||
"input": {
|
||||
"set1": [0,1],
|
||||
"set2": [2,3]
|
||||
},
|
||||
"output": [0,1]
|
||||
}
|
||||
]
|
||||
},
|
||||
"order_lexicographic": {
|
||||
"cases": [
|
||||
{
|
||||
"name": "test1",
|
||||
"input": {
|
||||
"list1": [],
|
||||
"list2": []
|
||||
},
|
||||
"output": true
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"input": {
|
||||
"list1": [2],
|
||||
"list2": []
|
||||
},
|
||||
"output": false
|
||||
},
|
||||
{
|
||||
"name": "test3",
|
||||
"input": {
|
||||
"list1": [],
|
||||
"list2": [2]
|
||||
},
|
||||
"output": true
|
||||
},
|
||||
{
|
||||
"name": "test4",
|
||||
"input": {
|
||||
"list1": [2],
|
||||
"list2": [2]
|
||||
},
|
||||
"output": true
|
||||
},
|
||||
{
|
||||
"name": "test5",
|
||||
"input": {
|
||||
"list1": [2],
|
||||
"list2": [3]
|
||||
},
|
||||
"output": true
|
||||
},
|
||||
{
|
||||
"name": "test6",
|
||||
"input": {
|
||||
"list1": [2],
|
||||
"list2": [1]
|
||||
},
|
||||
"output": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
67
lib/alveolata/module/implementation-system.php
Normal file
67
lib/alveolata/module/implementation-system.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\module;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/algorithm/functions.php');
|
||||
require_once(DIR_ALVEOLATA . '/module/interface.php');
|
||||
|
||||
|
||||
class implementation_system implements interface_module
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array {map<string,record<module:interface_module,dependencies:list<string>>>}
|
||||
*/
|
||||
private $members;
|
||||
|
||||
|
||||
/**
|
||||
* @var null|array {union<null,list<string>>}
|
||||
*/
|
||||
private $order;
|
||||
|
||||
|
||||
/**
|
||||
* @param array $members {map<string,record<module:interface_module,dependencies:list<string>>>}
|
||||
*/
|
||||
public function __construct(
|
||||
array $members
|
||||
)
|
||||
{
|
||||
$this->members = $members;
|
||||
$dependencies = [];
|
||||
foreach ($this->members as $name => $stuff) {
|
||||
$dependencies[$name] = $stuff['dependencies'];
|
||||
}
|
||||
$this->order = \alveolata\algorithm\topsort($dependencies);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [implementation]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function setup(
|
||||
) : void
|
||||
{
|
||||
foreach ($this->order as $name) {
|
||||
$this->members[$name]['module']->setup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [implementation]
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function teardown(
|
||||
) : void
|
||||
{
|
||||
foreach (array_reverse($this->order) as $name) {
|
||||
$this->members[$name]['module']->teardown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
31
lib/alveolata/module/interface.php
Normal file
31
lib/alveolata/module/interface.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\module;
|
||||
|
||||
/**
|
||||
*/
|
||||
interface interface_module
|
||||
{
|
||||
|
||||
/**
|
||||
* shall construct/build/establish the module
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function setup(
|
||||
) : void
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* shall close/remove/terminate the module
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function teardown(
|
||||
) : void
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
?>
|
73
lib/alveolata/observer/functions.php
Normal file
73
lib/alveolata/observer/functions.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\observer;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class struct {
|
||||
|
||||
/**
|
||||
* @var array {map<string,any>}
|
||||
*/
|
||||
public $listeners;
|
||||
|
||||
|
||||
/**
|
||||
* @array {map<string,any>}
|
||||
*/
|
||||
public function __construct(
|
||||
array $listeners
|
||||
)
|
||||
{
|
||||
$this->listeners = $listeners;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return struct
|
||||
*/
|
||||
function make(
|
||||
) : struct
|
||||
{
|
||||
return (
|
||||
new struct([])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct $subject
|
||||
* @param \Closure $procedure {function<any,void>}
|
||||
*/
|
||||
function register(
|
||||
struct $subject,
|
||||
\Closure $procedure
|
||||
) : string
|
||||
{
|
||||
$count = count($subject->listeners);
|
||||
$id = sprintf('listener_%d', $count);
|
||||
$subject->listeners[$id] = $procedure;
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param struct $subject
|
||||
* @param mixed $data {any}
|
||||
*/
|
||||
function notify(
|
||||
struct $subject,
|
||||
$data
|
||||
)/* : void*/
|
||||
{
|
||||
$result = [];
|
||||
foreach ($subject->listeners as $id => $procedure) {
|
||||
$result[$id] = $procedure($data);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
?>
|
48
lib/alveolata/observer/test.spec.php
Normal file
48
lib/alveolata/observer/test.spec.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/observer/functions.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'observer',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'register_notify',
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'test',
|
||||
'procedure' => function ($assert) {
|
||||
$subject = \alveolata\observer\make();
|
||||
$value = [
|
||||
'own' => null,
|
||||
'other' => null,
|
||||
];
|
||||
$id = \alveolata\observer\register(
|
||||
$subject,
|
||||
function ($data) use (&$value) : void {
|
||||
$value = [
|
||||
'own' => 7,
|
||||
'other' => $data,
|
||||
];
|
||||
}
|
||||
);
|
||||
\alveolata\observer\notify(
|
||||
$subject,
|
||||
11
|
||||
);
|
||||
$assert->equal($value, ['own' => 7, 'other' => 11]);
|
||||
}
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
42
lib/alveolata/observer/wrapper-class.php
Normal file
42
lib/alveolata/observer/wrapper-class.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\observer;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/observer/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
class class_observer
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function make() : class_observer {return (new class_observer(make()));}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function register(\Closure $procedure) {return register($this->subject, $procedure);}
|
||||
public function notify($data) {notify($this->subject, $data);}
|
||||
|
||||
}
|
||||
|
||||
?>
|
124
lib/alveolata/pod/functions.php
Normal file
124
lib/alveolata/pod/functions.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\pod;
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class struct_pod
|
||||
{
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $full;
|
||||
|
||||
|
||||
/**
|
||||
* @var null|TypeValue
|
||||
*/
|
||||
public $value;
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $full
|
||||
* @param null|TypeValue $value
|
||||
*/
|
||||
public function __construct(
|
||||
bool $full,
|
||||
$value
|
||||
)
|
||||
{
|
||||
$this->full = $full;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @return struct_pod<TypeValue>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make_toom(
|
||||
) : struct_pod
|
||||
{
|
||||
return (new struct_pod(false, null));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @param TypeValue $value
|
||||
* @return struct_pod<TypeValue>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function make_full(
|
||||
$value
|
||||
) : struct_pod
|
||||
{
|
||||
return (new struct_pod(true, $value));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @param struct_pod<TypeValue>
|
||||
* @return bool
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function has(
|
||||
struct_pod $pod
|
||||
): bool
|
||||
{
|
||||
return $pod->full;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @param struct_pod<TypeValue>
|
||||
* @return TypeValue
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function get(
|
||||
struct_pod $pod
|
||||
)
|
||||
{
|
||||
if (! $pod->full) {
|
||||
throw (new \Exception('empty'));
|
||||
}
|
||||
else {
|
||||
return $pod->value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* creates a pod on base of the input pod; i.e. if the input pod is empty, the output put is too; if the input pod is
|
||||
* full, the function is applied to its value to make up a new full pod with the result value
|
||||
*
|
||||
* @template TypeValueFrom
|
||||
* @template TypeValueTo
|
||||
* @param struct_pod<TypeValueFrom>
|
||||
* @param \Closure {function<TypeValueFrom,TypeValueTo>}
|
||||
* @return struct_pod<TypeValueTo>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
function brook(
|
||||
struct_pod $pod,
|
||||
\Closure $function
|
||||
) : struct_pod
|
||||
{
|
||||
if (! is_something($pod)) {
|
||||
return make_empty();
|
||||
}
|
||||
else {
|
||||
return make_full($function($pod->value));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
50
lib/alveolata/pod/test.spec.php
Normal file
50
lib/alveolata/pod/test.spec.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/pod/wrapper-class.php');
|
||||
|
||||
|
||||
\alveolata\test\add(
|
||||
[
|
||||
'name' => 'alveolata',
|
||||
'sections' => [
|
||||
[
|
||||
'name' => 'pod',
|
||||
'sections' => [
|
||||
[
|
||||
'active' => true,
|
||||
'name' => 'has_get',
|
||||
'setup' => function (&$env) {
|
||||
$env['safe_division'] = function (int $divident, int $divisor) : pod {
|
||||
if ($divisor === 0) {
|
||||
return pod::toom();
|
||||
}
|
||||
else {
|
||||
return pod::full(intval(floor($divident/$divisor)));
|
||||
}
|
||||
};
|
||||
},
|
||||
'cases' => [
|
||||
[
|
||||
'name' => 'fail',
|
||||
'procedure' => function ($assert, &$env) {
|
||||
$result_actual = $env['safe_division'](42, 0);
|
||||
$assert->equal($result_actual->has(), false);
|
||||
},
|
||||
],
|
||||
[
|
||||
'name' => 'success',
|
||||
'procedure' => function ($assert, &$env) {
|
||||
$result_actual = $env['safe_division'](42, 7);
|
||||
$assert->equal($result_actual->has(), true);
|
||||
$assert->equal($result_actual->get(), 6);
|
||||
},
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
61
lib/alveolata/pod/wrapper-class.php
Normal file
61
lib/alveolata/pod/wrapper-class.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\pod;
|
||||
|
||||
require_once(DIR_ALVEOLATA . '/pod/functions.php');
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
class class_pod
|
||||
{
|
||||
|
||||
/**
|
||||
* @var struct_pod<TypeValue>
|
||||
*/
|
||||
private $subject;
|
||||
|
||||
|
||||
/**
|
||||
* [constructor]
|
||||
*
|
||||
* @param struct_pod<TypeValue> $subject
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
private function __construct(struct_pod $subject) {$this->subject = $subject;}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @return struct_pod<TypeValue>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function toom() : class_pod {return (new class_pod(make_toom()));}
|
||||
|
||||
|
||||
/**
|
||||
* @template TypeValue
|
||||
* @param TypeValue $value
|
||||
* @return struct_pod<TypeValue>
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public static function full($value) : class_pod {return (new class_pod(make_full($value)));}
|
||||
|
||||
|
||||
/**
|
||||
* implementations
|
||||
*
|
||||
* @author Christian Fraß <frass@greenscale.de>
|
||||
*/
|
||||
public function has() : bool {return has($this->subject);}
|
||||
public function get() {return get($this->subject);}
|
||||
public function brook(\Closure $function) : class_pod {return (new class_pod(brook($this->subject, $function)));}
|
||||
// public function __isset() : bool {return $this->has();}
|
||||
|
||||
}
|
||||
|
||||
class_alias('\alveolata\pod\class_pod', 'pod');
|
||||
|
||||
?>
|
304
lib/alveolata/random/functions.php
Normal file
304
lib/alveolata/random/functions.php
Normal file
|
@ -0,0 +1,304 @@
|
|||
<?php
|
||||
|
||||
namespace alveolata\random;
|
||||
|
||||
|
||||
/**
|
||||
* @return float in interval [0,1[
|
||||
*/
|
||||
function generate_unit(
|
||||
) : float
|
||||
{
|
||||
$max = \getrandmax();
|
||||
return (rand(0, $max) / $max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generate an integer value
|
||||
*
|
||||
* @param float $probability the probability with which the result is "true"
|
||||
* @return bool
|
||||
*/
|
||||
function generate_boolean(
|
||||
$probability = 0.5
|
||||
) : bool
|
||||
{
|
||||
return (generate_unit() < $probability);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generate an integer value
|
||||
*
|
||||
* @param int $min the lowest possible generated value
|
||||
* @param int $max the highest possible generated value
|
||||
* @return int
|
||||
*/
|
||||
function generate_integer(
|
||||
int $min,
|
||||
int $max
|
||||
) : int
|
||||
{
|
||||
$t = generate_unit();
|
||||
return \intval(\floor($min + ($t * ($max - $min + 1))));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generate a string value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function generate_string(
|
||||
int $length = 16
|
||||
) : string
|
||||
{
|
||||
$string = '';
|
||||
for ($index = 0; $index < $length; $index += 1) {
|
||||
$string .= sprintf("%X", generate_integer(0, 15));
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* choose an element from a list with equal probabilities for all elements
|
||||
*
|
||||
* @template type_element
|
||||
* @param array $list {list<§type_element>}
|
||||
* @return type_element
|
||||
*/
|
||||
function choose_uniformly(
|
||||
array $list
|
||||
)
|
||||
{
|
||||
$index = generate_integer(0, count($list)-1);
|
||||
return $list[$index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* chooses a value randomly from a list of values with weights (a higher weight means a higher probability to be chosen)
|
||||
*
|
||||
* @template type_element
|
||||
* @param array $list {list<record<element:§type_element,weight:float>>}
|
||||
* @return type_element
|
||||
*/
|
||||
function choose_weighted(
|
||||
array $sets
|
||||
)
|
||||
{
|
||||
$sum = array_reduce(
|
||||
$sets,
|
||||
function (float $sum, array $entry) : float {return ($sum + $entry['weight']);},
|
||||
0
|
||||
);
|
||||
if ($sum === 0) {
|
||||
throw (new \Exception('weights sum up to zero; are all zero or are negative weights included?'));
|
||||
}
|
||||
else {
|
||||
$position = generate_unit();
|
||||
return array_reduce(
|
||||
$sets,
|
||||
function ($current, $set) use ($sum, $position) {
|
||||
$next = ['index' => null, 'value' => null];
|
||||
$next['index'] = ($current['index'] + ($set['weight'] / $sum));
|
||||
$next['value'] = (
|
||||
(($current['index'] <= $position) && ($position < $next['index']))
|
||||
? $set['value']
|
||||
: $current['value']
|
||||
);
|
||||
return $next;
|
||||
},
|
||||
['index' => 0, 'value' => null]
|
||||
)['value'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_vowel(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'i', 'weight' => 1],
|
||||
['value' => 'e', 'weight' => 2],
|
||||
['value' => 'a', 'weight' => 3],
|
||||
['value' => 'o', 'weight' => 2],
|
||||
['value' => 'u', 'weight' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_semivowel(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'y', 'weight' => 2],
|
||||
['value' => 'w', 'weight' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_lateral_approximant(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'l', 'weight' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_nasal(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'n', 'weight' => 4],
|
||||
['value' => 'm', 'weight' => 2],
|
||||
['value' => 'q', 'weight' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_plosive(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'b', 'weight' => 1],
|
||||
['value' => 'p', 'weight' => 2],
|
||||
['value' => 'd', 'weight' => 1],
|
||||
['value' => 't', 'weight' => 2],
|
||||
['value' => 'g', 'weight' => 1],
|
||||
['value' => 'k', 'weight' => 2],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_fricative(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => 'r', 'weight' => 2],
|
||||
['value' => 'h', 'weight' => 1],
|
||||
['value' => 'x', 'weight' => 1],
|
||||
['value' => 'j', 'weight' => 1],
|
||||
['value' => 'c', 'weight' => 1],
|
||||
['value' => 'v', 'weight' => 2],
|
||||
['value' => 'f', 'weight' => 2],
|
||||
['value' => 's', 'weight' => 2],
|
||||
['value' => 'z', 'weight' => 2],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_consonant(
|
||||
) : string
|
||||
{
|
||||
return choose_weighted([
|
||||
['value' => generate_lateral_approximant(), 'weight' => 1],
|
||||
['value' => generate_nasal(), 'weight' => 1],
|
||||
['value' => generate_plosive(), 'weight' => 1],
|
||||
['value' => generate_fricative(), 'weight' => 1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_syllable(
|
||||
) : string
|
||||
{
|
||||
/*
|
||||
+---+---+---+---+---+---+---+
|
||||
| | L | N | P | F | S | V |
|
||||
+---+---+---+---+---+---+---+
|
||||
| L | | o | x | x | o | x |
|
||||
+---+---+---+---+---+---+---+
|
||||
| N | | | o | x | | x |
|
||||
+---+---+---+---+---+---+---+
|
||||
| P | x | o | o | x | x | x |
|
||||
+---+---+---+---+---+---+---+
|
||||
| F | x | o | | o | x | x |
|
||||
+---+---+---+---+---+---+---+
|
||||
| S | x | x | x | x | | x |
|
||||
+---+---+---+---+---+---+---+
|
||||
| V | x | x | x | x | x | o |
|
||||
+---+---+---+---+---+---+---+
|
||||
|
||||
{L,N,P,F}
|
||||
{LP,LF,NF,NP,PL,PF,FL}
|
||||
{LPF,NPL,NPF,PFL}
|
||||
|
||||
pflantze
|
||||
glas
|
||||
crayb-tic
|
||||
|
||||
|
||||
*/
|
||||
$length1 = choose_weighted([
|
||||
['value' => 1, 'weight' => 3],
|
||||
['value' => 2, 'weight' => 4],
|
||||
['value' => 3, 'weight' => 1],
|
||||
]);
|
||||
|
||||
return sprintf(
|
||||
'%s%s%s',
|
||||
generate_consonant(),
|
||||
generate_vowel(),
|
||||
(generate_boolean(0.125) ? generate_semivowel() : '')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_word(
|
||||
) : string
|
||||
{
|
||||
$length = generate_integer(1, 4);
|
||||
$syllables = [];
|
||||
for ($index = 0; $index < $length; $index += 1) {
|
||||
$syllables[] = generate_syllable();
|
||||
}
|
||||
return implode('', $syllables);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function generate_text(
|
||||
) : string
|
||||
{
|
||||
$length = generate_integer(8, 32);
|
||||
$words = [];
|
||||
for ($index = 0; $index < $length; $index += 1) {
|
||||
$words[] = generate_word();
|
||||
}
|
||||
return implode(' ', $words);
|
||||
}
|
||||
|
||||
?>
|
8
lib/alveolata/random/test.php
Normal file
8
lib/alveolata/random/test.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
require_once(__DIR__ . '/functions.php');
|
||||
|
||||
|
||||
print(sprintf("%s\n", \alveolata\random\generate_text()));
|
||||
|
||||
?>
|
12
lib/alveolata/readme.md
Normal file
12
lib/alveolata/readme.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
## Requirements
|
||||
|
||||
### PHP Modules
|
||||
|
||||
- `SQLite3`
|
||||
- `mysqli`
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
- `php main.php [<test-section-path>]` (e.g. `php main.php cache.memory`)
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue