This commit is contained in:
roydfalk 2025-05-23 07:33:29 +00:00
parent 42e6b13597
commit 49f31834e7
189 changed files with 23308 additions and 5 deletions

2
lib/alveolata/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.geany

View 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);
}
}
);
}
);
}
?>

View 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']
),
],
]
]
]
]
);
?>

View 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
}
]
}
}

View 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)];
}
?>

View 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);
}
]
]
],
]
],
]
]
);
?>

View 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"
),
]
);
}
?>

View 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>

View 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>

View file

@ -0,0 +1 @@
<p class="api_infos_paragraph">{{content}}</p>

View 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;
}

View file

@ -0,0 +1,4 @@
<li class="api_tocentry">
<a href="{{href}}">{{title}} (<code>{{name}}</code>)</a>
</li>

View 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
View 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;
}
}
?>

View 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);}
}
?>

View 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;
}

View 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']);
}
);
}
],
]
],
]
]
]
]
);

View 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
;
}
?>

View 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
;
}
?>

View 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;
}
}
?>

View 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;
}
}
}
}
}
);
}
?>

View 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'],
];
}
}
);
}

View 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);
}
}
?>

View file

@ -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;
}
}
}
}
?>

View 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));
}
}

View 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) {
},
],
]
]
]
]
);

View 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
View 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;
}
}
}

View 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 = [];
}
}
?>

View 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);}
}

View 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 = [];
}

View 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);}
}

View 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
{
}

View 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
View 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');
}
],
]
],
],
]
]
]
);

View 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));
}
?>

View 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();
}
?>

View file

@ -0,0 +1,8 @@
<?php
// ini_set('output_buffering', '1');
while (ob_get_level() >= 1) {
ob_end_clean();
}
ob_start();

View 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"
]
}
}

View 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;
}
?>

View 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;
}
?>

View 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;
}
}
);
}
);
}
?>

View 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
;
}
?>

View 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;
}
}
}
?>

View 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;
}
}
?>

View file

@ -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);}
}
?>

View 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;
}
}
}
}
?>

View file

@ -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);}
}
?>

View 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;
}
}
?>

View file

@ -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);}
}
?>

View 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);
?>

View 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
;
}
?>

View 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());
}
}
}
}
?>

View 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;
}
}
}
?>

View 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')));
}
}
?>

View 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);
}
}
?>

View 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())));
}
}
}
?>

View 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);
}
}
?>

View 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();
}
?>

View file

@ -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);
}
}
?>

View 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);
}

View 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);
}
}
?>

View 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);
}
}

View 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;
}
?>

View 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);
},
],
],
],
],
]
]
]
);
?>

View 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;
}
}
?>

View 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");
}
},
],
]
],
]
]
]
]
);
?>

View 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;
}
}
?>

View 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;
}
}

View 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;
}
?>

View 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]]
}
}
]
}
]
}

View 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;
}
}
?>

View 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}');
},
],
]
]
]
]
]
]
);
?>

View 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);
}
?>

View 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;
}
}
}
?>

View 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);
}
}
?>

View 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
]
);
}
}
?>

View 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
);
}
}
?>

View 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);
}
}

View 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
}
}
}
?>

View 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
View 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]) : []);

View 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;
}
?>

View 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('![%s](%s)', $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;
}
?>

View 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']
),
],
]
]
]
]
);
?>

View 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!"
}
]
}
}

View 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)
);
}
}
}
}
}
?>

View 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']
)
],
],
]
]
]
);
?>

View 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
}
]
}
}

View 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();
}
}
}
?>

View 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
;
}
?>

View 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;
}
?>

View 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]);
}
],
]
]
]
],
]
]
);
?>

View 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);}
}
?>

View 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));
}
}
?>

View 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);
},
],
]
],
]
]
]
]
);
?>

View 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');
?>

View 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);
}
?>

View 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
View 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