[mod] alles mögliche verbessert [mod] SQLite-Anbindung
This commit is contained in:
parent
3497430fa4
commit
42e6b13597
18 changed files with 1151 additions and 389 deletions
|
@ -3,6 +3,4 @@
|
||||||
## Zu erledigen
|
## Zu erledigen
|
||||||
|
|
||||||
- sanitizing überprüfen
|
- sanitizing überprüfen
|
||||||
- Persistenz mit SQLite
|
|
||||||
- Datum für Dokumente ergänzen
|
- Datum für Dokumente ergänzen
|
||||||
- für jedes bestehende Dokument soll es Funktionen zum Aufbereiten und Herunderladen geben in den Formaten pdf/odt (pandoc nutzen) und ogg
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ proof-of-concept für Partei-Arbeits-Dokumenten-Verwaltung, welche hörbare Vers
|
||||||
### Voraussetzungen
|
### Voraussetzungen
|
||||||
|
|
||||||
- [PHP](https://www.php.net/)-Interpreter für Kommandozeile (Debian-Paket-Name: `php-cli`)
|
- [PHP](https://www.php.net/)-Interpreter für Kommandozeile (Debian-Paket-Name: `php-cli`)
|
||||||
|
- SQLite3-Modul für PHP (Debian-Paket-Name: `php-sqlite3`)
|
||||||
- [FFmpeg](https://ffmpeg.org/) (Debian-Paket-Name: `ffmpeg`)
|
- [FFmpeg](https://ffmpeg.org/) (Debian-Paket-Name: `ffmpeg`)
|
||||||
- Browser
|
- Browser
|
||||||
|
|
||||||
|
|
43
source/entities/doc.php
Normal file
43
source/entities/doc.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\entities\doc;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class entity
|
||||||
|
{
|
||||||
|
public string $title;
|
||||||
|
|
||||||
|
public array $authors;
|
||||||
|
|
||||||
|
public string $content;
|
||||||
|
|
||||||
|
public ?string $reasoning;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $title,
|
||||||
|
array $authors,
|
||||||
|
string $content,
|
||||||
|
?string $reasoning
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
$this->authors = $authors;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->reasoning = $reasoning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function empty_() : entity
|
||||||
|
{
|
||||||
|
return (new entity(
|
||||||
|
'',
|
||||||
|
[],
|
||||||
|
'',
|
||||||
|
null,
|
||||||
|
));
|
||||||
|
}
|
39
source/helpers/list.php
Normal file
39
source/helpers/list.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\helpers\list_;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function sequence(int $length) : array
|
||||||
|
{
|
||||||
|
return (($length <= 0) ? [] : \array_merge(sequence($length-1), [$length-1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function map(array $list, \Closure $function) : array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($list as $element)
|
||||||
|
{
|
||||||
|
\array_push($result, ($function)($element));
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function to_map(array $pairs) : array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($pairs as $pair)
|
||||||
|
{
|
||||||
|
$result[$pair['key']] = $pair['value'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
112
source/helpers/sqlite.php
Normal file
112
source/helpers/sqlite.php
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\helpers\sqlite;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function placeholder(
|
||||||
|
string $name
|
||||||
|
) : string
|
||||||
|
{
|
||||||
|
return (':' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function map_type(string $type) : string
|
||||||
|
{
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case 'bool':
|
||||||
|
{
|
||||||
|
return 'INTEGER';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'integer':
|
||||||
|
{
|
||||||
|
return 'INTEGER';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'string':
|
||||||
|
{
|
||||||
|
return 'TEXT';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw (new \Exception('unhandled type: ' . $type));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function format_value($value) : string
|
||||||
|
{
|
||||||
|
if ($value === null)
|
||||||
|
{
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$type = \gettype($value);
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case 'bool':
|
||||||
|
{
|
||||||
|
return ($value ? '1' : '0');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'int':
|
||||||
|
{
|
||||||
|
return \sprintf('%u', $value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'string':
|
||||||
|
{
|
||||||
|
return \sprintf("'%s'", $value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw (new \Exception('unhandled type: ' . $type));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function query(string $path, string $template, array $arguments) : array
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
\error_log(
|
||||||
|
\json_encode(
|
||||||
|
[
|
||||||
|
'template' => $template,
|
||||||
|
'arguments' => $arguments,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
$connection = new \SQLite3($path);
|
||||||
|
$statement = $connection->prepare($template);
|
||||||
|
foreach ($arguments as $key => $value)
|
||||||
|
{
|
||||||
|
$statement->bindValue(placeholder($key), $value);
|
||||||
|
}
|
||||||
|
$result = $statement->execute();
|
||||||
|
$last_insert_id = $connection->lastInsertRowID();
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'last_insert_id' => $last_insert_id,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
25
source/helpers/storage-interface.php
Normal file
25
source/helpers/storage-interface.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\helpers\storage;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template Key
|
||||||
|
* @template Value
|
||||||
|
*/
|
||||||
|
interface interface_
|
||||||
|
{
|
||||||
|
public function setup() : void;
|
||||||
|
|
||||||
|
public function list_() : array;
|
||||||
|
|
||||||
|
public function read(/*Key */$id)/* : Value*/;
|
||||||
|
|
||||||
|
public function create(/*Value */$value)/* : Key*/;
|
||||||
|
|
||||||
|
public function update(/*Key */$id, /*Value */$value) : void;
|
||||||
|
|
||||||
|
public function delete(/*Key */$id) : void;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -2,32 +2,31 @@
|
||||||
|
|
||||||
namespace rosavox\helpers\storage;
|
namespace rosavox\helpers\storage;
|
||||||
|
|
||||||
/**
|
require_once('helpers/storage-interface.php');
|
||||||
*/
|
|
||||||
interface interface_
|
|
||||||
{
|
|
||||||
public function list_() : array;
|
|
||||||
|
|
||||||
public function read(int $id);
|
|
||||||
|
|
||||||
public function create($value) : int;
|
|
||||||
|
|
||||||
public function update(int $id, $value) : void;
|
|
||||||
|
|
||||||
public function delete(int $id) : void;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
class class_jsonfile implements interface_
|
class class_jsonfile implements interface_/*<int,any>*/
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
private string $path;
|
private string $path;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
private \Closure $id_encode;
|
private \Closure $id_encode;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
private \Closure $id_decode;
|
private \Closure $id_decode;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $path,
|
string $path,
|
||||||
\Closure $id_encode,
|
\Closure $id_encode,
|
||||||
|
@ -39,6 +38,9 @@ class class_jsonfile implements interface_
|
||||||
$this->id_decode = $id_decode;
|
$this->id_decode = $id_decode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
private function get() : array
|
private function get() : array
|
||||||
{
|
{
|
||||||
$content = (\file_exists($this->path) ? \file_get_contents($this->path) : null);
|
$content = (\file_exists($this->path) ? \file_get_contents($this->path) : null);
|
||||||
|
@ -51,12 +53,26 @@ class class_jsonfile implements interface_
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
private function put(array $data) : void
|
private function put(array $data) : void
|
||||||
{
|
{
|
||||||
$content = \json_encode($data, \JSON_PRETTY_PRINT);
|
$content = \json_encode($data, \JSON_PRETTY_PRINT);
|
||||||
\file_put_contents($this->path, $content);
|
\file_put_contents($this->path, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function setup() : void
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
public function list_() : array
|
public function list_() : array
|
||||||
{
|
{
|
||||||
$data = $this->get();
|
$data = $this->get();
|
||||||
|
@ -69,7 +85,10 @@ class class_jsonfile implements interface_
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function read(int $id)
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function read($id)
|
||||||
{
|
{
|
||||||
$data = $this->get();
|
$data = $this->get();
|
||||||
$id_encoded = ($this->id_encode)($id);
|
$id_encoded = ($this->id_encode)($id);
|
||||||
|
@ -83,7 +102,7 @@ class class_jsonfile implements interface_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create($value) : int
|
public function create($value)
|
||||||
{
|
{
|
||||||
$data = $this->get();
|
$data = $this->get();
|
||||||
$id = ($data['last_id'] + 1);
|
$id = ($data['last_id'] + 1);
|
||||||
|
@ -94,7 +113,10 @@ class class_jsonfile implements interface_
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(int $id, $value) : void
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function update($id, $value) : void
|
||||||
{
|
{
|
||||||
$data = $this->get();
|
$data = $this->get();
|
||||||
$id_encoded = ($this->id_encode)($id);
|
$id_encoded = ($this->id_encode)($id);
|
||||||
|
@ -109,7 +131,10 @@ class class_jsonfile implements interface_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(int $id) : void
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function delete($id) : void
|
||||||
{
|
{
|
||||||
$data = $this->get();
|
$data = $this->get();
|
||||||
$id_encoded = ($this->id_encode)($id);
|
$id_encoded = ($this->id_encode)($id);
|
||||||
|
@ -123,6 +148,7 @@ class class_jsonfile implements interface_
|
||||||
$this->put($data);
|
$this->put($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
311
source/helpers/storage-sqlitetable.php
Normal file
311
source/helpers/storage-sqlitetable.php
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\helpers\storage;
|
||||||
|
|
||||||
|
require_once('helpers/list.php');
|
||||||
|
require_once('helpers/sqlite.php');
|
||||||
|
require_once('helpers/storage-interface.php');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class class_sqlitetable implements interface_/*<int,list<any>>*/
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static function field_name(
|
||||||
|
array $field
|
||||||
|
) : string
|
||||||
|
{
|
||||||
|
return \rosavox\helpers\string_\coin(
|
||||||
|
'field_{{name}}',
|
||||||
|
[
|
||||||
|
'name' => $field['name'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private string $path;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private string $name;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $fields {
|
||||||
|
* list<
|
||||||
|
* record<
|
||||||
|
* name:string,
|
||||||
|
* type:string,
|
||||||
|
* nullable:boolean,
|
||||||
|
* >
|
||||||
|
* >
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
private array $fields;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $path,
|
||||||
|
string $name,
|
||||||
|
array $fields
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->path = $path;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->fields = $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function setup() : void
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'CREATE TABLE IF NOT EXISTS {{name}}(id INTEGER PRIMARY KEY AUTOINCREMENT, {{fields}});',
|
||||||
|
[
|
||||||
|
'name' => $this->name,
|
||||||
|
'fields' => \implode(
|
||||||
|
', ',
|
||||||
|
\array_map(
|
||||||
|
fn ($field) => \rosavox\helpers\string_\coin(
|
||||||
|
'{{name}} {{type}}{{macro_nullable}}',
|
||||||
|
[
|
||||||
|
'name' => self::field_name($field),
|
||||||
|
'type' => \rosavox\helpers\sqlite\map_type($field['type']),
|
||||||
|
'macro_nullable' => ($field['nullable'] ? '' : ' NOT NULL'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$this->fields
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function list_() : array
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'SELECT id,{{fields}} FROM {{name}}',
|
||||||
|
[
|
||||||
|
'fields' => \implode(
|
||||||
|
', ',
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
$this->fields,
|
||||||
|
fn ($field) => self::field_name($field)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'name' => $this->name,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$entries = [];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$row = $result['result']->fetchArray(\SQLITE3_NUM);
|
||||||
|
if ($row !== false)
|
||||||
|
{
|
||||||
|
\array_push(
|
||||||
|
$entries,
|
||||||
|
[
|
||||||
|
'id' => $row[0],
|
||||||
|
'value' => \array_slice($row, 1),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function read($id)
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'SELECT {{fields}} FROM {{name}} WHERE (id = {{id}});',
|
||||||
|
[
|
||||||
|
'fields' => \implode(
|
||||||
|
', ',
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
$this->fields,
|
||||||
|
fn ($field) => self::field_name($field)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'name' => $this->name,
|
||||||
|
'id' => \rosavox\helpers\sqlite\placeholder('id'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
|
'id' => $id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$rows = [];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$row = $result['result']->fetchArray(\SQLITE3_NUM);
|
||||||
|
if ($row !== false)
|
||||||
|
{
|
||||||
|
\array_push($rows, $row);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count = \count($rows);
|
||||||
|
if ($count === 0)
|
||||||
|
{
|
||||||
|
throw (new \Exception('not found'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($count > 1)
|
||||||
|
{
|
||||||
|
throw (new \Exception('ambiguous'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $rows[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function create($row)
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'INSERT INTO {{name}}({{schema}}) VALUES ({{values}});',
|
||||||
|
[
|
||||||
|
'name' => $this->name,
|
||||||
|
'schema' => \implode(
|
||||||
|
', ',
|
||||||
|
\array_map(
|
||||||
|
fn ($field) => self::field_name($field),
|
||||||
|
$this->fields
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'values' => \implode(
|
||||||
|
', ',
|
||||||
|
\array_map(
|
||||||
|
fn ($field) => \rosavox\helpers\sqlite\placeholder(
|
||||||
|
self::field_name($field)
|
||||||
|
),
|
||||||
|
$this->fields
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
\rosavox\helpers\list_\to_map(
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
\rosavox\helpers\list_\sequence(
|
||||||
|
\count($this->fields)
|
||||||
|
),
|
||||||
|
fn ($index) => [
|
||||||
|
'key' => self::field_name($this->fields[$index]),
|
||||||
|
'value' => $row[$index],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return $result['last_insert_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function update($id, $row) : void
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'UPDATE {{name}} SET {{sets}} WHERE (id = :id);',
|
||||||
|
[
|
||||||
|
'name' => $this->name,
|
||||||
|
'sets' => \implode(
|
||||||
|
', ',
|
||||||
|
\array_map(
|
||||||
|
fn ($field) => \rosavox\helpers\string_\coin(
|
||||||
|
'{{name}} = {{placeholder}}',
|
||||||
|
[
|
||||||
|
'name' => self::field_name($field),
|
||||||
|
'placeholder' => \rosavox\helpers\sqlite\placeholder(self::field_name($field)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$this->fields
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
\array_merge(
|
||||||
|
[
|
||||||
|
'id' => $id,
|
||||||
|
],
|
||||||
|
\rosavox\helpers\list_\to_map(
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
\rosavox\helpers\list_\sequence(
|
||||||
|
\count($this->fields)
|
||||||
|
),
|
||||||
|
fn ($index) => [
|
||||||
|
'key' => self::field_name($this->fields[$index]),
|
||||||
|
'value' => $row[$index],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public function delete($id) : void
|
||||||
|
{
|
||||||
|
$result = \rosavox\helpers\sqlite\query(
|
||||||
|
$this->path,
|
||||||
|
\rosavox\helpers\string_\coin(
|
||||||
|
'DELETE * FROM {{name}} WHERE (id = {{id}});',
|
||||||
|
[
|
||||||
|
'name' => $this->name,
|
||||||
|
'id' => \rosavox\helpers\sqlite\placeholder('id'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
|
'id' => $id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -1,208 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
require_once('main.php');
|
||||||
require_once('helpers/string.php');
|
|
||||||
require_once('helpers/misc.php');
|
|
||||||
require_once('services/doc.php');
|
|
||||||
|
|
||||||
|
|
||||||
\rosavox\services\doc\init();
|
|
||||||
|
|
||||||
$mode = ($_GET['mode'] ?? 'list');
|
|
||||||
$id_encoded = (! empty($_GET['id']) ? $_GET['id'] : null);
|
|
||||||
$id = (($id_encoded === null) ? null : \intval($id_encoded));
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function make_link(string $mode, array $args) : string
|
|
||||||
{
|
|
||||||
$a = \array_merge($args, ['mode' => $mode]);
|
|
||||||
return (
|
|
||||||
'?'
|
|
||||||
.
|
|
||||||
\implode(
|
|
||||||
'&',
|
|
||||||
\array_map(
|
|
||||||
fn ($key) => \rosavox\helpers\string_\coin(
|
|
||||||
'{{key}}={{value}}',
|
|
||||||
[
|
|
||||||
'key' => $key,
|
|
||||||
'value' => $a[$key],
|
|
||||||
]
|
|
||||||
),
|
|
||||||
\array_keys($a)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function nav(string $mode, array $args) : void
|
|
||||||
{
|
|
||||||
\rosavox\helpers\misc\navigate(make_link($mode, $args));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function render_list() : string
|
|
||||||
{
|
|
||||||
return \rosavox\helpers\misc\render(
|
|
||||||
'docs-list',
|
|
||||||
[
|
|
||||||
'label_make' => \rosavox\helpers\misc\translate('action.make'),
|
|
||||||
'link_make' => make_link('make', []),
|
|
||||||
'entries' => \implode(
|
|
||||||
"\n",
|
|
||||||
\array_map(
|
|
||||||
fn ($entry) => \rosavox\helpers\misc\render(
|
|
||||||
'docs-list-entry',
|
|
||||||
[
|
|
||||||
'label_read' => \rosavox\helpers\misc\translate('action.read'),
|
|
||||||
'value_text' => $entry['value']['title'],
|
|
||||||
'value_link_open' => make_link('edit', ['id' => \sprintf('%u', $entry['id'])]),
|
|
||||||
'value_link_read' => \rosavox\services\doc\readable_path($entry['id']),
|
|
||||||
'area_download_readable' => (
|
|
||||||
(fn ($name, $path) => (
|
|
||||||
(! \file_exists($path))
|
|
||||||
?
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'state-waiting',
|
|
||||||
[
|
|
||||||
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
:
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'download',
|
|
||||||
[
|
|
||||||
'text' => \rosavox\helpers\misc\translate('item.readable.short'),
|
|
||||||
'tooltip' => \rosavox\helpers\string_\coin(
|
|
||||||
'{{item}} {{action}}',
|
|
||||||
[
|
|
||||||
'action' => \rosavox\helpers\misc\translate('action.download'),
|
|
||||||
'item' => \rosavox\helpers\misc\translate('item.readable.long'),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
'link' => $path,
|
|
||||||
'name' => \rosavox\helpers\string_\coin(
|
|
||||||
'{{name}}.md',
|
|
||||||
[
|
|
||||||
'name' => $name,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
'type' => 'text/markdown',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)) (
|
|
||||||
\rosavox\services\doc\name($entry['id']),
|
|
||||||
\rosavox\services\doc\readable_path($entry['id'])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'area_download_audible' => (
|
|
||||||
(fn ($name, $path) => (
|
|
||||||
(! \file_exists($path))
|
|
||||||
?
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'state-waiting',
|
|
||||||
[
|
|
||||||
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
:
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'download',
|
|
||||||
[
|
|
||||||
'text' => \rosavox\helpers\misc\translate('item.audible.short'),
|
|
||||||
'tooltip' => \rosavox\helpers\string_\coin(
|
|
||||||
'{{item}} {{action}}',
|
|
||||||
[
|
|
||||||
'action' => \rosavox\helpers\misc\translate('action.download'),
|
|
||||||
'item' => \rosavox\helpers\misc\translate('item.audible.long'),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
'link' => $path,
|
|
||||||
'name' => \rosavox\helpers\string_\coin(
|
|
||||||
'{{name}}.oga',
|
|
||||||
[
|
|
||||||
'name' => $name,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
'type' => 'audio/ogg',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)) (
|
|
||||||
\rosavox\services\doc\name($entry['id']),
|
|
||||||
\rosavox\services\doc\audible_path($entry['id'])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'area_hear' => (
|
|
||||||
(fn ($name, $path) => (
|
|
||||||
(! \file_exists($path))
|
|
||||||
?
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'state-waiting',
|
|
||||||
[
|
|
||||||
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
:
|
|
||||||
\rosavox\helpers\misc\render(
|
|
||||||
'player',
|
|
||||||
[
|
|
||||||
'source_path' => $path,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)) (
|
|
||||||
\rosavox\services\doc\name($entry['id']),
|
|
||||||
\rosavox\services\doc\audible_path($entry['id'])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
\rosavox\services\doc\list_()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function render_edit(?int $id) : string
|
|
||||||
{
|
|
||||||
$doc = (
|
|
||||||
($id === null)
|
|
||||||
?
|
|
||||||
\rosavox\services\doc\empty_()
|
|
||||||
:
|
|
||||||
\rosavox\services\doc\read($id)
|
|
||||||
);
|
|
||||||
return \rosavox\helpers\misc\render(
|
|
||||||
'docs-edit',
|
|
||||||
[
|
|
||||||
'label_action_back' => \rosavox\helpers\misc\translate('action.back'),
|
|
||||||
'label_action_save' => \rosavox\helpers\misc\translate('action.save'),
|
|
||||||
'label_action_delete' => \rosavox\helpers\misc\translate('action.delete'),
|
|
||||||
'label_doc_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
|
||||||
'label_doc_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
|
||||||
'label_doc_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
|
||||||
'label_doc_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
|
||||||
'value_action_back' => make_link('list', []),
|
|
||||||
'value_action_save' => make_link('save', (($id === null) ? [] : ['id' => \strval($id)])),
|
|
||||||
'value_action_delete' => make_link('delete', (($id === null) ? [] : ['id' => \strval($id)])),
|
|
||||||
'value_doc_title' => $doc['title'],
|
|
||||||
'value_doc_authors' => \implode(', ', $doc['authors']),
|
|
||||||
'value_doc_content' => $doc['content'],
|
|
||||||
'value_doc_reasoning' => ($doc['reasoning'] ?? ''),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -213,63 +10,6 @@ function render_edit(?int $id) : string
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>rosavox</h1>
|
<h1>rosavox</h1>
|
||||||
<?php
|
<?php \rosavox\main($_GET, $_POST); ?>
|
||||||
switch ($mode)
|
|
||||||
{
|
|
||||||
case 'delete':
|
|
||||||
{
|
|
||||||
if ($id === null)
|
|
||||||
{
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
\rosavox\services\doc\delete($id);
|
|
||||||
}
|
|
||||||
nav('list', []);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'save':
|
|
||||||
{
|
|
||||||
$doc = [
|
|
||||||
'title' => $_POST['title'],
|
|
||||||
'authors' => \explode(',', $_POST['authors']),
|
|
||||||
'content' => $_POST['content'],
|
|
||||||
'reasoning' => (empty($_POST['reasoning']) ? null : $_GET['reasoning']),
|
|
||||||
];
|
|
||||||
if ($id === null)
|
|
||||||
{
|
|
||||||
$id = \rosavox\services\doc\create($doc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
\rosavox\services\doc\update($id, $doc);
|
|
||||||
}
|
|
||||||
// nav('edit', ['id' => \rosavox\services\doc\id_encode($id)]);
|
|
||||||
nav('list', []);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'list':
|
|
||||||
{
|
|
||||||
echo(render_list());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'make':
|
|
||||||
{
|
|
||||||
echo(render_edit(null));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit':
|
|
||||||
{
|
|
||||||
echo(render_edit($id));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw (new \Exception(\sprintf('invalid mode: %s', $mode)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
90
source/main.php
Normal file
90
source/main.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox;
|
||||||
|
|
||||||
|
require_once('entities/doc.php');
|
||||||
|
require_once('services/doc.php');
|
||||||
|
require_once('nav.php');
|
||||||
|
require_once('renderings.php');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function main(array $get, array $post) : void
|
||||||
|
{
|
||||||
|
// init
|
||||||
|
\rosavox\services\doc\init();
|
||||||
|
|
||||||
|
// args
|
||||||
|
$mode = (
|
||||||
|
$get['mode']
|
||||||
|
??
|
||||||
|
'list'
|
||||||
|
);
|
||||||
|
$id = (
|
||||||
|
(! empty($get['id']))
|
||||||
|
?
|
||||||
|
\intval($get['id'])
|
||||||
|
:
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
// exec
|
||||||
|
switch ($mode)
|
||||||
|
{
|
||||||
|
case 'delete':
|
||||||
|
{
|
||||||
|
if ($id === null)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
\rosavox\services\doc\remove($id);
|
||||||
|
}
|
||||||
|
nav('list', []);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'save':
|
||||||
|
{
|
||||||
|
$doc = new \rosavox\entities\doc\entity(
|
||||||
|
$post['title'],
|
||||||
|
\explode(',', $post['authors']),
|
||||||
|
$post['content'],
|
||||||
|
(empty($post['reasoning']) ? null : $post['reasoning'])
|
||||||
|
);
|
||||||
|
if ($id === null)
|
||||||
|
{
|
||||||
|
$id = \rosavox\services\doc\add($doc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
\rosavox\services\doc\change($id, $doc);
|
||||||
|
}
|
||||||
|
// \rosavox\nav('edit', ['id' => \strval($id)]);
|
||||||
|
\rosavox\nav('list', []);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'list':
|
||||||
|
{
|
||||||
|
echo(\rosavox\renderings\doc_list());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'make':
|
||||||
|
{
|
||||||
|
echo(\rosavox\renderings\doc_edit(null));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'edit':
|
||||||
|
{
|
||||||
|
echo(\rosavox\renderings\doc_edit($id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw (new \Exception(\sprintf('invalid mode: %s', $mode)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
42
source/nav.php
Normal file
42
source/nav.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox;
|
||||||
|
|
||||||
|
require_once('helpers/list.php');
|
||||||
|
require_once('helpers/string.php');
|
||||||
|
require_once('helpers/misc.php');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function make_link(string $mode, array $args) : string
|
||||||
|
{
|
||||||
|
$a = \array_merge($args, ['mode' => $mode]);
|
||||||
|
return (
|
||||||
|
'?'
|
||||||
|
.
|
||||||
|
\implode(
|
||||||
|
'&',
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
\array_keys($a),
|
||||||
|
fn ($key) => \rosavox\helpers\string_\coin(
|
||||||
|
'{{key}}={{value}}',
|
||||||
|
[
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $a[$key],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function nav(string $mode, array $args) : void
|
||||||
|
{
|
||||||
|
\rosavox\helpers\misc\navigate(make_link($mode, $args));
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
170
source/renderings.php
Normal file
170
source/renderings.php
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\renderings;
|
||||||
|
|
||||||
|
require_once('helpers/list.php');
|
||||||
|
require_once('helpers/string.php');
|
||||||
|
require_once('helpers/misc.php');
|
||||||
|
require_once('entities/doc.php');
|
||||||
|
require_once('services/doc.php');
|
||||||
|
require_once('nav.php');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function doc_list() : string
|
||||||
|
{
|
||||||
|
return \rosavox\helpers\misc\render(
|
||||||
|
'docs-list',
|
||||||
|
[
|
||||||
|
'label_make' => \rosavox\helpers\misc\translate('action.make'),
|
||||||
|
'link_make' => \rosavox\make_link('make', []),
|
||||||
|
'entries' => \implode(
|
||||||
|
"\n",
|
||||||
|
\rosavox\helpers\list_\map(
|
||||||
|
\rosavox\services\doc\dump(),
|
||||||
|
fn ($entry) => \rosavox\helpers\misc\render(
|
||||||
|
'docs-list-entry',
|
||||||
|
[
|
||||||
|
'label_read' => \rosavox\helpers\misc\translate('action.read'),
|
||||||
|
'value_text' => $entry['value']->title,
|
||||||
|
'value_link_open' => \rosavox\make_link('edit', ['id' => \sprintf('%u', $entry['id'])]),
|
||||||
|
'value_link_read' => \rosavox\services\doc\readable_path($entry['id']),
|
||||||
|
'area_download_readable' => (
|
||||||
|
(fn ($name, $path) => (
|
||||||
|
(! \file_exists($path))
|
||||||
|
?
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'state-waiting',
|
||||||
|
[
|
||||||
|
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
:
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'download',
|
||||||
|
[
|
||||||
|
'text' => \rosavox\helpers\misc\translate('item.readable.short'),
|
||||||
|
'tooltip' => \rosavox\helpers\string_\coin(
|
||||||
|
'{{item}} {{action}}',
|
||||||
|
[
|
||||||
|
'action' => \rosavox\helpers\misc\translate('action.download'),
|
||||||
|
'item' => \rosavox\helpers\misc\translate('item.readable.long'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'link' => $path,
|
||||||
|
'name' => \rosavox\helpers\string_\coin(
|
||||||
|
'{{name}}.md',
|
||||||
|
[
|
||||||
|
'name' => $name,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'type' => 'text/markdown',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)) (
|
||||||
|
\rosavox\services\doc\name($entry['id']),
|
||||||
|
\rosavox\services\doc\readable_path($entry['id'])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'area_download_audible' => (
|
||||||
|
(fn ($name, $path) => (
|
||||||
|
(! \file_exists($path))
|
||||||
|
?
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'state-waiting',
|
||||||
|
[
|
||||||
|
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
:
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'download',
|
||||||
|
[
|
||||||
|
'text' => \rosavox\helpers\misc\translate('item.audible.short'),
|
||||||
|
'tooltip' => \rosavox\helpers\string_\coin(
|
||||||
|
'{{item}} {{action}}',
|
||||||
|
[
|
||||||
|
'action' => \rosavox\helpers\misc\translate('action.download'),
|
||||||
|
'item' => \rosavox\helpers\misc\translate('item.audible.long'),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'link' => $path,
|
||||||
|
'name' => \rosavox\helpers\string_\coin(
|
||||||
|
'{{name}}.oga',
|
||||||
|
[
|
||||||
|
'name' => $name,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'type' => 'audio/ogg',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)) (
|
||||||
|
\rosavox\services\doc\name($entry['id']),
|
||||||
|
\rosavox\services\doc\audible_path($entry['id'])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'area_hear' => (
|
||||||
|
(fn ($name, $path) => (
|
||||||
|
(! \file_exists($path))
|
||||||
|
?
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'state-waiting',
|
||||||
|
[
|
||||||
|
'info' => \rosavox\helpers\misc\translate('state.generating'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
:
|
||||||
|
\rosavox\helpers\misc\render(
|
||||||
|
'player',
|
||||||
|
[
|
||||||
|
'source_path' => $path,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)) (
|
||||||
|
\rosavox\services\doc\name($entry['id']),
|
||||||
|
\rosavox\services\doc\audible_path($entry['id'])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function doc_edit(?int $id) : string
|
||||||
|
{
|
||||||
|
$doc = (
|
||||||
|
($id === null)
|
||||||
|
?
|
||||||
|
\rosavox\entities\doc\empty_()
|
||||||
|
:
|
||||||
|
\rosavox\services\doc\get($id)
|
||||||
|
);
|
||||||
|
return \rosavox\helpers\misc\render(
|
||||||
|
'docs-edit',
|
||||||
|
[
|
||||||
|
'label_action_back' => \rosavox\helpers\misc\translate('action.back'),
|
||||||
|
'label_action_save' => \rosavox\helpers\misc\translate('action.save'),
|
||||||
|
'label_action_delete' => \rosavox\helpers\misc\translate('action.delete'),
|
||||||
|
'label_doc_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
||||||
|
'label_doc_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
||||||
|
'label_doc_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
||||||
|
'label_doc_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
||||||
|
'value_action_back' => \rosavox\make_link('list', []),
|
||||||
|
'value_action_save' => \rosavox\make_link('save', (($id === null) ? [] : ['id' => \strval($id)])),
|
||||||
|
'value_action_delete' => \rosavox\make_link('delete', (($id === null) ? [] : ['id' => \strval($id)])),
|
||||||
|
'value_doc_title' => $doc->title,
|
||||||
|
'value_doc_authors' => \implode(', ', $doc->authors),
|
||||||
|
'value_doc_content' => $doc->content,
|
||||||
|
'value_doc_reasoning' => ($doc->reasoning ?? ''),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
164
source/repositories/doc.php
Normal file
164
source/repositories/doc.php
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace rosavox\repositories\doc;
|
||||||
|
|
||||||
|
require_once('helpers/storage-interface.php');
|
||||||
|
require_once('helpers/storage-jsonfile.php');
|
||||||
|
require_once('helpers/storage-sqlitetable.php');
|
||||||
|
|
||||||
|
require_once('entities/doc.php');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class repo implements \rosavox\helpers\storage\interface_/*<int,\rosavox\entities\doc\entity>*/
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private \rosavox\helpers\storage\interface_ $core;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
$this->core = new \rosavox\helpers\storage\class_jsonfile(
|
||||||
|
'docs.json',
|
||||||
|
fn ($id) => \sprintf('%u', $id),
|
||||||
|
fn ($id_encoded) => \intval($id_encoded)
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
$this->core = new \rosavox\helpers\storage\class_sqlitetable(
|
||||||
|
'data.sqlite',
|
||||||
|
'docs',
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'name' => 'title',
|
||||||
|
'type' => 'string',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'authors',
|
||||||
|
'type' => 'string',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'content',
|
||||||
|
'type' => 'string',
|
||||||
|
'nullable' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'reasoning',
|
||||||
|
'type' => 'string',
|
||||||
|
'nullable' => true,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private static ?repo $instance = null;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public static function get_instance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null)
|
||||||
|
{
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private static function encode(\rosavox\entities\doc\entity $doc) : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$doc->title,
|
||||||
|
\implode(',', $doc->authors),
|
||||||
|
$doc->content,
|
||||||
|
$doc->reasoning,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
private static function decode(array $row) : \rosavox\entities\doc\entity
|
||||||
|
{
|
||||||
|
return (new \rosavox\entities\doc\entity(
|
||||||
|
$row[0],
|
||||||
|
\explode(',', $row[1]),
|
||||||
|
$row[2],
|
||||||
|
$row[3],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function setup() : void
|
||||||
|
{
|
||||||
|
$this->core->setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function list_() : array
|
||||||
|
{
|
||||||
|
return \rosavox\helpers\list_\map(
|
||||||
|
$this->core->list_(),
|
||||||
|
fn ($entry) => [
|
||||||
|
'id' => $entry['id'],
|
||||||
|
'value' => self::decode($entry['value']),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function read($id)
|
||||||
|
{
|
||||||
|
return self::decode($this->core->read($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function create($doc)
|
||||||
|
{
|
||||||
|
return $this->core->create(self::encode($doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function update($id, $doc) : void
|
||||||
|
{
|
||||||
|
$this->core->update($id, self::encode($doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [implementation]
|
||||||
|
*/
|
||||||
|
public function delete($id) : void
|
||||||
|
{
|
||||||
|
$this->core->delete($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,16 +3,10 @@
|
||||||
namespace rosavox\services\doc;
|
namespace rosavox\services\doc;
|
||||||
|
|
||||||
require_once('helpers/string.php');
|
require_once('helpers/string.php');
|
||||||
require_once('helpers/storage.php');
|
|
||||||
require_once('helpers/misc.php');
|
require_once('helpers/misc.php');
|
||||||
|
|
||||||
|
require_once('entities/doc.php');
|
||||||
/**
|
require_once('repositories/doc.php');
|
||||||
*/
|
|
||||||
class state
|
|
||||||
{
|
|
||||||
public static ?\rosavox\helpers\storage\interface_ $storage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +35,7 @@ function audible_path(int $id) : string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function generate_readable(int $id, $doc) : void
|
function generate_readable(int $id, \rosavox\entities\doc\entity $doc) : void
|
||||||
{
|
{
|
||||||
$markdown = \rosavox\helpers\string_\coin(
|
$markdown = \rosavox\helpers\string_\coin(
|
||||||
"# {{value_title}}
|
"# {{value_title}}
|
||||||
|
@ -58,7 +52,7 @@ function generate_readable(int $id, $doc) : void
|
||||||
{{macro_reasoning}}",
|
{{macro_reasoning}}",
|
||||||
[
|
[
|
||||||
'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
||||||
'value_title' => $doc['title'],
|
'value_title' => $doc->title,
|
||||||
'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
||||||
'value_authors' => \implode(
|
'value_authors' => \implode(
|
||||||
"\n",
|
"\n",
|
||||||
|
@ -69,13 +63,13 @@ function generate_readable(int $id, $doc) : void
|
||||||
'author' => $author,
|
'author' => $author,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
$doc['authors']
|
$doc->authors
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
||||||
'value_content' => $doc['content'],
|
'value_content' => $doc->content,
|
||||||
'macro_reasoning' => (
|
'macro_reasoning' => (
|
||||||
($doc['reasoning'] === null)
|
($doc->reasoning === null)
|
||||||
?
|
?
|
||||||
''
|
''
|
||||||
:
|
:
|
||||||
|
@ -86,7 +80,7 @@ function generate_readable(int $id, $doc) : void
|
||||||
{{value_reasoning}}",
|
{{value_reasoning}}",
|
||||||
[
|
[
|
||||||
'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
||||||
'value_reasoning' => $doc['reasoning'],
|
'value_reasoning' => $doc->reasoning,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -101,9 +95,7 @@ function generate_readable(int $id, $doc) : void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function remove_readable(
|
function remove_readable(int $id) : void
|
||||||
int $id
|
|
||||||
) : void
|
|
||||||
{
|
{
|
||||||
$path = readable_path($id);
|
$path = readable_path($id);
|
||||||
if (! \file_exists($path))
|
if (! \file_exists($path))
|
||||||
|
@ -119,10 +111,7 @@ function remove_readable(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function generate_audible(
|
function generate_audible(int $id, \rosavox\entities\doc\entity $doc) : void
|
||||||
int $id,
|
|
||||||
$doc
|
|
||||||
) : void
|
|
||||||
{
|
{
|
||||||
$pause = " .\n";
|
$pause = " .\n";
|
||||||
$text = \rosavox\helpers\string_\coin(
|
$text = \rosavox\helpers\string_\coin(
|
||||||
|
@ -130,13 +119,13 @@ function generate_audible(
|
||||||
[
|
[
|
||||||
'pause' => $pause,
|
'pause' => $pause,
|
||||||
'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'),
|
||||||
'value_title' => $doc['title'],
|
'value_title' => $doc->title,
|
||||||
'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'),
|
||||||
'value_authors' => \implode($pause, $doc['authors']),
|
'value_authors' => \implode($pause, $doc->authors),
|
||||||
'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'),
|
||||||
'value_content' => $doc['content'],
|
'value_content' => $doc->content,
|
||||||
'macro_reasoning' => (
|
'macro_reasoning' => (
|
||||||
($doc['reasoning'] === null)
|
($doc->reasoning === null)
|
||||||
?
|
?
|
||||||
''
|
''
|
||||||
:
|
:
|
||||||
|
@ -145,7 +134,7 @@ function generate_audible(
|
||||||
[
|
[
|
||||||
'pause' => $pause,
|
'pause' => $pause,
|
||||||
'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'),
|
||||||
'value_reasoning' => $doc['reasoning'],
|
'value_reasoning' => $doc->reasoning,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -163,9 +152,7 @@ function generate_audible(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function remove_audible(
|
function remove_audible(int $id) : void
|
||||||
int $id
|
|
||||||
) : void
|
|
||||||
{
|
{
|
||||||
$path = audible_path($id);
|
$path = audible_path($id);
|
||||||
if (! \file_exists($path))
|
if (! \file_exists($path))
|
||||||
|
@ -179,41 +166,28 @@ function remove_audible(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function empty_() : array
|
function dump() : array
|
||||||
{
|
{
|
||||||
return [
|
return \rosavox\repositories\doc\repo::get_instance()->list_();
|
||||||
'title' => '',
|
|
||||||
'authors' => [],
|
|
||||||
'content' => '',
|
|
||||||
'reasoning' => null,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function list_() : array
|
function get(int $id) : \rosavox\entities\doc\entity
|
||||||
{
|
{
|
||||||
return state::$storage->list_();
|
return \rosavox\repositories\doc\repo::get_instance()->read($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function read(int $id) : array
|
function add(\rosavox\entities\doc\entity $doc) : int
|
||||||
{
|
{
|
||||||
return state::$storage->read($id);
|
$id = \rosavox\repositories\doc\repo::get_instance()->create($doc);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo async generating
|
|
||||||
*/
|
|
||||||
function create(array $doc) : int
|
|
||||||
{
|
|
||||||
$id = state::$storage->create($doc);
|
|
||||||
generate_readable($id, $doc);
|
generate_readable($id, $doc);
|
||||||
generate_audible($id, $doc);
|
generate_audible($id, $doc);
|
||||||
return $id;
|
return $id;
|
||||||
|
@ -222,9 +196,40 @@ function create(array $doc) : int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function update(int $id, array $doc) : void
|
function add_examples() : void
|
||||||
{
|
{
|
||||||
state::$storage->update($id, $doc);
|
$entities = [
|
||||||
|
new \rosavox\entities\doc\entity(
|
||||||
|
'Freibier bei Parteitagen',
|
||||||
|
[
|
||||||
|
'Björn Biernot',
|
||||||
|
'Doreen Dauerdurst',
|
||||||
|
],
|
||||||
|
'Der Landesverband möge beschließen, dass zu Beginn eines jeden Parteitags für jeden Deligierten mindestens zwei Flaschen Bier auf den zugehörigen Platz zu stellen sind.',
|
||||||
|
'Wir haben Durst!',
|
||||||
|
),
|
||||||
|
new \rosavox\entities\doc\entity(
|
||||||
|
'Götterdämmerung',
|
||||||
|
[
|
||||||
|
'Fenriswolf',
|
||||||
|
],
|
||||||
|
'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen. Diese Welt wird enden.',
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
foreach ($entities as $entity)
|
||||||
|
{
|
||||||
|
add($entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function change(int $id, \rosavox\entities\doc\entity $doc) : void
|
||||||
|
{
|
||||||
|
\rosavox\repositories\doc\repo::get_instance()->update($id, $doc);
|
||||||
remove_readable($id);
|
remove_readable($id);
|
||||||
remove_audible($id);
|
remove_audible($id);
|
||||||
generate_readable($id, $doc);
|
generate_readable($id, $doc);
|
||||||
|
@ -234,52 +239,21 @@ function update(int $id, array $doc) : void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function delete(int $id) : void
|
function remove(int $id) : void
|
||||||
{
|
{
|
||||||
state::$storage->delete($id);
|
\rosavox\repositories\doc\repo::get_instance()->delete($id);
|
||||||
remove_readable($id);
|
remove_readable($id);
|
||||||
remove_audible($id);
|
remove_audible($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function add_examples() : void
|
|
||||||
{
|
|
||||||
create(
|
|
||||||
[
|
|
||||||
'title' => 'Freibier bei Parteitagen',
|
|
||||||
'authors' => [
|
|
||||||
'Björn Biernot',
|
|
||||||
'Doreen Dauerdurst',
|
|
||||||
],
|
|
||||||
'content' => 'Der Landesverband möge beschließen, dass zu Beginn eines jeden Parteitags für jeden Deligierten mindestens zwei Flaschen Bier auf den zugehörigen Platz zu stellen sind.',
|
|
||||||
'reasoning' => 'Wir haben Durst!',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
create(
|
|
||||||
[
|
|
||||||
'title' => 'Götterdämmerung',
|
|
||||||
'authors' => [
|
|
||||||
'Fenriswolf',
|
|
||||||
],
|
|
||||||
'content' => 'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen. Diese Welt wird enden.',
|
|
||||||
'reasoning' => null,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
function init() : void
|
function init() : void
|
||||||
{
|
{
|
||||||
state::$storage = new \rosavox\helpers\storage\class_jsonfile(
|
\rosavox\repositories\doc\repo::get_instance()->setup();
|
||||||
'docs.json',
|
|
||||||
fn ($id) => \sprintf('%u', $id),
|
if (empty(dump()))
|
||||||
fn ($id_encoded) => \intval($id_encoded)
|
|
||||||
);
|
|
||||||
if (empty(state::$storage->list_()))
|
|
||||||
{
|
{
|
||||||
add_examples();
|
add_examples();
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,16 +82,6 @@ button
|
||||||
font-size: 1.0em;
|
font-size: 1.0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs-list-entry
|
|
||||||
{
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.docs-list-entry > *
|
|
||||||
{
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-waiting
|
.state-waiting
|
||||||
{
|
{
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
|
@ -101,3 +91,37 @@ button
|
||||||
{
|
{
|
||||||
content: "\21A7 ";
|
content: "\21A7 ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docs-list > ul
|
||||||
|
{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-list-entry
|
||||||
|
{
|
||||||
|
padding-top: 24px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-list-entry:not(:first-child)
|
||||||
|
{
|
||||||
|
border-top: 2px solid hsl(var(--hue), 0%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-list-entry-main
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-list-entry-actions
|
||||||
|
{
|
||||||
|
margin-top: 8px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-list-entry-actions > *
|
||||||
|
{
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div id="docs-edit">
|
<div class="docs-edit">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>
|
<label>
|
||||||
<span>{{label_doc_title}}</span>
|
<span>{{label_doc_title}}</span>
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<li class="docs-list-entry">
|
<li class="docs-list-entry">
|
||||||
<a href="{{value_link_open}}">{{value_text}}</a>
|
<div class="docs-list-entry-main">
|
||||||
|
|
<a href="{{value_link_open}}">{{value_text}}</a>
|
||||||
{{area_download_readable}}
|
</div>
|
||||||
|
|
<div class="docs-list-entry-actions">
|
||||||
{{area_download_audible}}
|
{{area_download_readable}}
|
||||||
|
|
|
|
||||||
{{area_hear}}
|
{{area_download_audible}}
|
||||||
|
|
|
||||||
|
{{area_hear}}
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div id="docs-list">
|
<div class="docs-list">
|
||||||
<ul>
|
<ul>
|
||||||
{{entries}}
|
{{entries}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Add table
Reference in a new issue