org -> unit change, some new functionality

This commit is contained in:
Dennis Eichhorn 2023-01-26 21:54:13 +01:00
parent 492f27084f
commit 9e360ca988
29 changed files with 1927 additions and 126 deletions

View File

@ -13,47 +13,164 @@
"virtualPath": "/Modules/Admin",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Global",
"virtualPath": "/Modules/Admin/Templates",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Helper",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Lists",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Letters",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Emails",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Data",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "collection",
"create_directory": true,
"name": "Reports",
"virtualPath": "/Modules/Admin/Templates/Global",
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Pdf Exporter",
"virtualPath": "/Modules/Admin/Templates",
"path": "/Modules/Admin/Templates/Pdf Exporter",
"name": "Assets",
"virtualPath": "/Modules/Admin/Templates/Global/Helper",
"path": "/Modules/Admin/Templates/Global/Helper/Assets",
"files": [
"/Modules/Admin/Admin/Install/Media/PdfExporter"
"/Modules/Admin/Admin/Install/Media/Assets"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Excel Exporter",
"virtualPath": "/Modules/Admin/Templates",
"path": "/Modules/Admin/Templates/Excel Exporter",
"name": "Pdf Default Template",
"virtualPath": "/Modules/Admin/Templates/Global/Helper",
"path": "/Modules/Admin/Templates/Global/Helper/Pdf Default Template",
"files": [
"/Modules/Admin/Admin/Install/Media/ExcelExporter"
"/Modules/Admin/Admin/Install/Media/PdfDefaultTemplate"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Csv Exporter",
"virtualPath": "/Modules/Admin/Templates",
"path": "/Modules/Admin/Templates/Csv Exporter",
"name": "Word Default Template",
"virtualPath": "/Modules/Admin/Templates/Global/Helper",
"path": "/Modules/Admin/Templates/Global/Helper/Word Default Template",
"files": [
"/Modules/Admin/Admin/Install/Media/CsvExporter"
"/Modules/Admin/Admin/Install/Media/WordDefaultTemplate"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Word Exporter",
"virtualPath": "/Modules/Admin/Templates",
"path": "/Modules/Admin/Templates/Word Exporter",
"name": "Word Plain Template",
"virtualPath": "/Modules/Admin/Templates/Global/Helper",
"path": "/Modules/Admin/Templates/Global/Helper/Word Plain Template",
"files": [
"/Modules/Admin/Admin/Install/Media/WordExporter"
"/Modules/Admin/Admin/Install/Media/WordPlainTemplate"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Excel Default Template",
"virtualPath": "/Modules/Admin/Templates/Global/Helper",
"path": "/Modules/Admin/Templates/Global/Helper/Excel Default Template",
"files": [
"/Modules/Admin/Admin/Install/Media/ExcelDefaultTemplate"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Pdf List Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Lists",
"path": "/Modules/Admin/Templates/Global/Lists/Pdf List Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/PdfListExporter"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Word List Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Lists",
"path": "/Modules/Admin/Templates/Global/Lists/Word List Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/WordListExporter"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Excel List Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Lists",
"path": "/Modules/Admin/Templates/Global/Lists/Excel List Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/ExcelListExporter"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Csv List Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Lists",
"path": "/Modules/Admin/Templates/Global/Lists/Csv List Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/CsvListExporter"
],
"user": 1
},
{
"type": "upload",
"create_collection": true,
"name": "Word List Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Letters",
"path": "/Modules/Admin/Templates/Global/Letters/Word Letter Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/WordLetterExporter"
],
"user": 1
},
@ -61,11 +178,71 @@
"type": "upload",
"create_collection": true,
"name": "Email Exporter",
"virtualPath": "/Modules/Admin/Templates",
"path": "/Modules/Admin/Templates/Email Exporter",
"virtualPath": "/Modules/Admin/Templates/Global/Emails",
"path": "/Modules/Admin/Templates/Global/Emails/Email Exporter",
"files": [
"/Modules/Admin/Admin/Install/Media/EmailExporter"
],
"user": 1
},
{
"type": "reference",
"name": "Assets",
"from": "/Modules/Admin/Templates/Global/Helper/Pdf Default Template",
"to": "/Modules/Admin/Templates/Global/Helper/Assets",
"user": 1
},
{
"type": "reference",
"name": "Assets",
"from": "/Modules/Admin/Templates/Global/Helper/Word Default Template",
"to": "/Modules/Admin/Templates/Global/Helper/Assets",
"user": 1
},
{
"type": "reference",
"name": "Assets",
"from": "/Modules/Admin/Templates/Global/Helper/Word Plain Template",
"to": "/Modules/Admin/Templates/Global/Helper/Assets",
"user": 1
},
{
"type": "reference",
"name": "Assets",
"from": "/Modules/Admin/Templates/Global/Helper/Excel Default Template",
"to": "/Modules/Admin/Templates/Global/Helper/Assets",
"user": 1
},
{
"type": "reference",
"name": "Helper",
"from": "/Modules/Admin/Templates/Global/Lists/Pdf List Exporter",
"to": "/Modules/Admin/Templates/Global/Helper/Pdf Default Template",
"user": 1
},
{
"type": "reference",
"name": "Helper",
"from": "/Modules/Admin/Templates/Global/Lists/Word List Exporter",
"to": "/Modules/Admin/Templates/Global/Helper/Word Default Template",
"user": 1
},
{
"type": "reference",
"name": "Helper",
"from": "/Modules/Admin/Templates/Global/Lists/Excel List Exporter",
"to": "/Modules/Admin/Templates/Global/Helper/Excel Default Template",
"user": 1
},
{
"type": "reference",
"name": "Helper",
"from": "/Modules/Admin/Templates/Global/Letters/Word Letter Exporter",
"to": "/Modules/Admin/Templates/Global/Helper/Word Default Template",
"user": 1
}
]

View File

@ -43,26 +43,26 @@ class Media
{
$media = \Modules\Media\Admin\Installer::installExternal($app, ['path' => __DIR__ . '/Media.install.json']);
$defaultPdfExport = (int) \reset($media['upload'][0]);
$defaultExcelExport = (int) \reset($media['upload'][1]);
$defaultCsvExport = (int) \reset($media['upload'][2]);
$defaultWordExport = (int) \reset($media['upload'][3]);
$defaultEmailExport = (int) \reset($media['upload'][4]);
SettingMapper::create()->execute(
new Setting(
0,
SettingsEnum::DEFAULT_LIST_EXPORTS,
(string) $media['collection'][4]['id'],
'\\d+',
unit: 1,
module: 'Admin'
)
);
SettingMapper::create()->execute(
new Setting(0, SettingsEnum::DEFAULT_PDF_EXPORT_TEMPLATE, (string) $defaultPdfExport, '\\d+', 1, 'Admin')
);
SettingMapper::create()->execute(
new Setting(0, SettingsEnum::DEFAULT_EXCEL_EXPORT_TEMPLATE, (string) $defaultExcelExport, '\\d+', 1, 'Admin')
);
SettingMapper::create()->execute(
new Setting(0, SettingsEnum::DEFAULT_CSV_EXPORT_TEMPLATE, (string) $defaultCsvExport, '\\d+', 1, 'Admin')
);
SettingMapper::create()->execute(
new Setting(0, SettingsEnum::DEFAULT_WORD_EXPORT_TEMPLATE, (string) $defaultWordExport, '\\d+', 1, 'Admin')
);
SettingMapper::create()->execute(
new Setting(0, SettingsEnum::DEFAULT_EMAIL_EXPORT_TEMPLATE, (string) $defaultEmailExport, '\\d+', 1, 'Admin')
new Setting(
0,
SettingsEnum::DEFAULT_LETTERS,
(string) $media['collection'][5]['id'],
'\\d+',
unit: 1,
module: 'Admin'
)
);
}
}

View File

@ -12,4 +12,12 @@
*/
declare(strict_types=1);
$exporter = null;
$data = $this->getData('data') ?? [];
$out = \fopen('php://output', 'w');
foreach ($data as $row) {
fputcsv($out, $row);
}
\fclose($out);

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooter;
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
class DefaultExcel extends \PhpOffice\PhpSpreadsheet\Spreadsheet
{
public function __construct()
{
parent::__construct();
$this->getActiveSheet()
->getPageSetup()
->setPaperSize(PageSetup::PAPERSIZE_A4);
$this->getActiveSheet()
->getHeaderFooter()
->setOddHeader("&L&B&20Jingga\n&B&10Business solutions made simple.");
$this->getActiveSheet()
->getHeaderFooter()
->setOddFooter('&RPage &P/&N');
/*
Tested with LibreOffice and not working (requires &G above in the text).
Either it is broken or LibreOffice cannot show the image.
$drawing = new HeaderFooterDrawing();
$drawing->setName('PhpSpreadsheet logo');
$drawing->setPath(__DIR__ . '/../Web/Backend/img/logo.png');
$drawing->setHeight(50);
$this->getActiveSheet()
->getHeaderFooter()
->addImage($drawing, HeaderFooter::IMAGE_HEADER_LEFT);
*/
}
}

View File

@ -1,15 +0,0 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
$exporter = null;

View File

@ -0,0 +1,42 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
use PhpOffice\PhpSpreadsheet\IOFactory;
$media = $this->getData('media');
$data = $this->getData('data') ?? [];
include $media->getSourceByName('template.php')->getAbsolutePath();
$excel = new DefaultExcel();
foreach ($data as $i => $row) {
foreach ($row as $j => $cell) {
$excel->getActiveSheet()->setCellValueByColumnAndRow($j + 1, $i + 1, $cell);
}
}
$file = \tempnam(\sys_get_temp_dir(), 'oms_');
$writer = IOFactory::createWriter($excel, 'Xlsx');
$writer->save($file);
echo \file_get_contents($file);
\unlink($file);

View File

@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
require_once __DIR__ . '/../Resources/tcpdf/tcpdf.php';
class DefaultPdf extends TCPDF
{
public string $fontName = '';
public int $fontSize = 8;
public int $sideMargin = 15;
//Page header
public function Header() {
if ($this->header_xobjid === false) {
$this->header_xobjid = $this->startTemplate($this->w, 0);
// Set Logo
$image_file = __DIR__ . '/../Web/Backend/img/logo.png';
$this->Image($image_file, 15, 15, 15, 15, 'PNG', '', 'T', false, 300, '', false, false, 0, false, false, false);
// Set Title
$this->SetFont('helvetica', 'B', 20);
$this->setX(15 + 15 + 3);
$this->Cell(0, 14, $this->header_title, 0, false, 'L', 0, '', 0, false, 'T', 'M');
$this->SetFont('helvetica', '', 10);
$this->setX(15 + 15 + 3);
$this->Cell(0, 26, $this->header_string, 0, false, 'L', 0, '', 0, false, 'T', 'M');
$this->endTemplate();
}
$x = 0;
$dx = 0;
if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
// adjust margins for booklet mode
$dx = ($this->original_lMargin - $this->original_rMargin);
}
if ($this->rtl) {
$x = $this->w + $dx;
} else {
$x = 0 + $dx;
}
$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
if ($this->header_xobj_autoreset) {
// reset header xobject template at each page
$this->header_xobjid = false;
}
}
// Page footer
public function Footer() {
$this->SetY(-25);
$this->SetFont('helvetica', 'I', 7);
$this->Cell($this->getPageWidth() - 22, 0, 'Page '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, false, 'R', 0, '', 0, false, 'T', 'M');
$this->Ln();
$this->Ln();
$this->SetFillColor(245, 245, 245);
$this->SetX(0);
$this->Cell($this->getPageWidth(), 25, '', 0, 0, 'L', true, '', 0, false, 'T', 'T');
$this->SetFont('helvetica', '', 7);
$this->SetXY(15 + 10, -15, true);
$this->MultiCell(30, 0, "Jingga e.K.\nGartenstr. 26\n61206 Woellstadt", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 15 + 20, -15, true);
$this->MultiCell(40, 0, "Geschäftsführer: Dennis Eichhorn\nFinanzamt: HRB ???\nUSt Id: DE ??????????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 45 + 15 + 30, -15, true);
$this->MultiCell(35, 0, "Volksbank Mittelhessen\nBIC: ??????????\nIBAN: ???????????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 45 + 35 + 15 + 40, -15, true);
$this->MultiCell(35, 0, "www.jingga.app\ninfo@jingga.app\n+49 0152 ???????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
}
public function __construct()
{
parent::__construct('P', 'mm', 'A4', true, 'UTF-8', false);
$this->SetCreator("Jingga");
// set default header data
$this->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Jingga', 'Business solutions made simple.');
// set header and footer fonts
$this->SetHeaderFont([PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN]);
$this->SetFooterFont([PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA]);
// set default monospaced font
$this->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
// set margins
$this->SetMargins(15, 30, 15);
// set auto page breaks
$this->SetAutoPageBreak(true, 25);
// set image scale factor
$this->SetImageScale(PDF_IMAGE_SCALE_RATIO);
// add a page
$this->AddPage();
}
}
/*
[
'company' => '',
'slogan' => '',
'company_full' => '',
'address' => '',
'ciry' => '',
'manager' => '',
'tax_office' => '',
'tax_id' => '',
'tax_vat' => '',
'bank_name' => '',
'bank_bic' => '',
'bank_iban' => '',
'website' => '',
'email' => '',
'phone' => '',
'creator' => '',
'date' => '',
]
*/

View File

@ -1,15 +0,0 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
$exporter = null;

View File

@ -0,0 +1,49 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
$media = $this->getData('media');
$data = $this->getData('data') ?? [];
include $media->getSourceByName('template.php')->getAbsolutePath();
$excel = new DefaultPdf();
$topPos = $pdf->getY();
$tbl = '<table border="1" cellpadding="0" cellspacing="0">';
foreach ($data as $i => $row) {
if ($i === 0) {
$tbl = '<thead><tr>';
foreach ($row as $j => $cell) {
$tbl .= '<td>' . $cell . '</td>';
}
$tbl .= '</tr></thead>';
} else {
$tbl .= '<tr>';
foreach ($row as $j => $cell) {
$tbl .= '<td>' . $cell . '</td>';
}
$tbl .= '</tr>';
}
}
$tbl .= '</table>';
$pdf->Output('list.pdf', 'I');

View File

@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
class DefaultWord extends \PhpOffice\PhpWord\PhpWord {
public function __construct()
{
parent::__construct();
$generalTableStyle = ['cellMargin' => 100, 'bgColor' => 'f5f5f5'];
$this->addTableStyle('FooterTableStyle', $generalTableStyle);
}
public function createFirstPage()
{
$section = $this->addSection([
'marginLeft' => 1000,
'marginRight' => 1000,
'marginTop' => 2000,
'marginBottom' => 2000,
// 'headerHeight' => 50,
// 'footerHeight' => 50,
]);
// first page header
$firstHeader = $section->addHeader();
$firstHeader->firstPage();
$table = $firstHeader->addTable();
$table->addRow();
// first column
$table->addCell(1300)->addImage(__DIR__ . '/../Web/Backend/img/logo.png', ['width' => 50, 'height' => 50]);
//second column
$cell = $table->addCell(8700, ['valign' => 'bottom']);
$textrun = $cell->addTextRun();
$textrun->addText('Jingga', ['name' => 'helvetica', 'bold' => true, 'size' => 20]);
$textrun = $cell->addTextRun();
$textrun->addText('Business solutions made simple.', ['name' => 'helvetica', 'size' => 10]);
// first page footer
$firstFooter = $section->addFooter();
$firstFooter->firstPage();
$firstFooter->addPreserveText('Page {PAGE}/{NUMPAGES}', ['name' => 'helvetica', 'italic' => true], ['alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END]);
$firstFooter->addTextRun();
$table = $firstFooter->addTable('FooterTableStyle');
$table->addRow();
// columns
$cell = $table->addCell(500);
$cell = $table->addCell(2000);
$textrun = $cell->addTextRun();
$textrun->addText('Jingga e.K.', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('Gartenstr. 26', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('61206 Woellstadt', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2700);
$textrun = $cell->addTextRun();
$textrun->addText('Geschäftsführer: Dennis Eichhorn', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('Finanzamt: HRB ???', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('USt Id: DE ??????????', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2700);
$textrun = $cell->addTextRun();
$textrun->addText('Volksbank Mittelhessen', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('BIC: ??????????', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('IBAN: ???????????', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2100);
$textrun = $cell->addTextRun();
$textrun->addText('www.jingga.app', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('info@jingga.app', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('+49 0152 ???????', ['name' => 'helvetica', 'size' => 7]);
return $section;
}
public function createSecondPage()
{
$section = $this->addSection([
'marginLeft' => 1000,
'marginRight' => 1000,
'marginTop' => 2000,
'marginBottom' => 2000,
// 'headerHeight' => 50,
// 'footerHeight' => 50,
]);
$header = $section->addHeader();
$table = $header->addTable();
$table->addRow();
// first column
$table->addCell(1300)->addImage(__DIR__ . '/../Web/Backend/img/logo.png', ['width' => 50, 'height' => 50]);
//second column
$cell = $table->addCell(8700, ['valign' => 'bottom']);
$textrun = $cell->addTextRun();
$textrun->addText('Jingga', ['name' => 'helvetica', 'bold' => true, 'size' => 20]);
$textrun = $cell->addTextRun();
$textrun->addText('Business solutions made simple.', ['name' => 'helvetica', 'size' => 10]);
$footer = $section->addFooter();
$footer->addPreserveText('Page {PAGE}/{NUMPAGES}', ['name' => 'helvetica', 'italic' => true], ['alignment' => \PhpOffice\PhpWord\SimpleType\Jc::END]);
$footer->addTextRun();
$table = $footer->addTable('FooterTableStyle');
$table->addRow();
// columns
$cell = $table->addCell(500);
$cell = $table->addCell(2000);
$textrun = $cell->addTextRun();
$textrun->addText('Jingga e.K.', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('Gartenstr. 26', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('61206 Woellstadt', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2700);
$textrun = $cell->addTextRun();
$textrun->addText('Geschäftsführer: Dennis Eichhorn', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('Finanzamt: HRB ???', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('USt Id: DE ??????????', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2700);
$textrun = $cell->addTextRun();
$textrun->addText('Volksbank Mittelhessen', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('BIC: ??????????', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('IBAN: ???????????', ['name' => 'helvetica', 'size' => 7]);
$cell = $table->addCell(2100);
$textrun = $cell->addTextRun();
$textrun->addText('www.jingga.app', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('info@jingga.app', ['name' => 'helvetica', 'size' => 7]);
$textrun = $cell->addTextRun();
$textrun->addText('+49 0152 ???????', ['name' => 'helvetica', 'size' => 7]);
return $section;
}
}
/*
[
'company' => '',
'slogan' => '',
'company_full' => '',
'address' => '',
'ciry' => '',
'manager' => '',
'tax_office' => '',
'tax_id' => '',
'tax_vat' => '',
'bank_name' => '',
'bank_bic' => '',
'bank_iban' => '',
'website' => '',
'email' => '',
'phone' => '',
'creator' => '',
'date' => '',
]
*/

View File

@ -1,15 +0,0 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
$exporter = null;

View File

@ -0,0 +1,33 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
$media = $this->getData('media');
$data = $this->getData('data') ?? [];
include $media->getSourceByName('template.php')->getAbsolutePath();
$word = new DefaultWord();
$section = $word->createFirstPage();
$file = \tempnam(\sys_get_temp_dir(), 'oms_');
$writer->save($file);
echo \file_get_contents($file);
\unlink($file);

View File

@ -0,0 +1,55 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
$media = $this->getData('media');
$data = $this->getData('data') ?? [];
include $media->getSourceByName('template.php')->getAbsolutePath();
$word = new DefaultWord();
$section = $word->createFirstPage();
$tbl = '<table border="1" cellpadding="0" cellspacing="0">';
foreach ($data as $i => $row) {
if ($i === 0) {
$tbl = '<thead><tr>';
foreach ($row as $j => $cell) {
$tbl .= '<td>' . $cell . '</td>';
}
$tbl .= '</tr></thead>';
} else {
$tbl .= '<tr>';
foreach ($row as $j => $cell) {
$tbl .= '<td>' . $cell . '</td>';
}
$tbl .= '</tr>';
}
}
$tbl .= '</table>';
\PhpOffice\PhpWord\Shared\Html::addHtml($section, $tbl, false, false);
$file = \tempnam(\sys_get_temp_dir(), 'oms_');
$writer->save($file);
echo \file_get_contents($file);
\unlink($file);

View File

@ -0,0 +1,33 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
require_once __DIR__ . '/../phpOMS/Autoloader.php';
use phpOMS\Autoloader;
Autoloader::addPath(__DIR__ . '/../Resources');
$media = $this->getData('media');
$data = $this->getData('data') ?? [];
include $media->getSourceByName('template.php')->getAbsolutePath();
$word = new DefaultWord();
$section = $word->createFirstPage();
$file = \tempnam(\sys_get_temp_dir(), 'oms_');
$writer->save($file);
echo \file_get_contents($file);
\unlink($file);

View File

@ -507,6 +507,56 @@
}
}
},
"unit": {
"name": "unit",
"fields": {
"unit_id": {
"name": "unit_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"unit_name": {
"name": "unit_name",
"type": "VARCHAR(50)",
"default": null,
"null": true
},
"unit_image": {
"name": "unit_image",
"type": "INT",
"default": null,
"null": true
},
"unit_description": {
"name": "unit_description",
"type": "TEXT",
"default": null,
"null": true
},
"unit_descriptionraw": {
"name": "unit_descriptionraw",
"type": "TEXT",
"default": null,
"null": true
},
"unit_parent": {
"name": "unit_parent",
"type": "INT",
"default": null,
"null": true,
"foreignTable": "unit",
"foreignKey": "unit_id"
},
"unit_status": {
"name": "unit_status",
"type": "TINYINT",
"default": null,
"null": true
}
}
},
"app": {
"name": "app",
"fields": {
@ -553,7 +603,8 @@
"group_name": {
"name": "group_name",
"type": "VARCHAR(50)",
"null": false
"null": false,
"unique": true
},
"group_status": {
"name": "group_status",
@ -601,9 +652,12 @@
"name": "group_permission_unit",
"type": "INT",
"default": null,
"null": true
"null": true,
"foreignTable": "unit",
"foreignKey": "unit_id"
},
"group_permission_app": {
"description": "@todo: consider to use int as value and create foreign key",
"name": "group_permission_app",
"type": "VARCHAR(255)",
"default": null,
@ -681,6 +735,11 @@
"primary": true,
"autoincrement": true
},
"account_id_temp": {
"name": "account_id_temp",
"type": "VARCHAR(65)",
"null": false
},
"account_status": {
"name": "account_status",
"type": "TINYINT",
@ -748,6 +807,14 @@
"gdpr": true
}
},
"account_email_temp": {
"name": "account_email_temp",
"type": "VARCHAR(70)",
"null": false,
"annotations": {
"gdpr": true
}
},
"account_tries": {
"name": "account_tries",
"type": "TINYINT",
@ -775,6 +842,87 @@
}
}
},
"account_address_rel": {
"name": "account_address_rel",
"fields": {
"account_address_rel_id": {
"name": "account_address_rel_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"account_address_rel_contact": {
"name": "account_address_rel_contact",
"type": "INT",
"null": false,
"foreignTable": "profile_contact",
"foreignKey": "profile_contact_id"
},
"account_address_rel_module": {
"name": "account_address_rel_module",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "module",
"foreignKey": "module_id"
},
"account_address_rel_address": {
"name": "account_address_rel_address",
"type": "INT",
"null": false,
"foreignTable": "address",
"foreignKey": "address_id"
}
}
},
"account_contact": {
"name": "account_contact",
"fields": {
"account_contact_id": {
"name": "account_contact_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"account_contact_type": {
"name": "account_contact_type",
"type": "TINYINT",
"null": false
},
"account_contact_subtype": {
"name": "account_contact_subtype",
"type": "TINYINT",
"null": false
},
"account_contact_order": {
"name": "account_contact_order",
"type": "INT",
"null": false
},
"account_contact_content": {
"name": "account_contact_content",
"type": "VARCHAR(255)",
"null": false
},
"account_contact_module": {
"name": "account_contact_module",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "module",
"foreignKey": "module_id"
},
"account_contact_account": {
"name": "account_contact_account",
"type": "INT",
"null": false,
"foreignTable": "account",
"foreignKey": "account_id"
}
}
},
"account_account_rel": {
"description": "Accounts can belong to other accounts. E.g. a user can belong to a company account",
"name": "account_account_rel",
@ -919,6 +1067,41 @@
}
}
},
"account_api": {
"name": "account_api",
"fields": {
"account_api_id": {
"name": "account_api_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"account_api_status": {
"name": "account_api_status",
"type": "TINYINT",
"null": false
},
"account_api_key": {
"name": "account_api_key",
"type": "VARCHAR(129)",
"null": false,
"unique": true
},
"account_api_created_at": {
"name": "account_api_created_at",
"type": "DATETIME",
"null": false
},
"account_api_account": {
"name": "account_api_account",
"type": "INT",
"null": false,
"foreignTable": "account",
"foreignKey": "account_id"
}
}
},
"settings": {
"name": "settings",
"fields": {
@ -944,6 +1127,14 @@
"type": "TEXT",
"null": true
},
"settings_unit": {
"name": "settings_unit",
"type": "INT",
"default": null,
"null": true,
"foreignTable": "unit",
"foreignKey": "unit_id"
},
"settings_app": {
"name": "settings_app",
"type": "INT",
@ -977,5 +1168,46 @@
"foreignKey": "account_id"
}
}
},
"data_change": {
"name": "data_change",
"fields": {
"data_change_id": {
"name": "data_change_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"data_change_type": {
"name": "data_change_type",
"type": "VARCHAR(65)",
"null": false,
"unique": true
},
"data_change_hash": {
"name": "data_change_hash",
"type": "VARCHAR(65)",
"null": false,
"unique": true
},
"data_change_data": {
"name": "data_change_data",
"type": "VARCHAR(255)",
"null": false
},
"data_change_created_by": {
"name": "data_change_created_by",
"type": "INT",
"null": false,
"foreignTable": "account",
"foreignKey": "account_id"
},
"data_change_created_at": {
"name": "data_change_created_at",
"type": "DATETIME",
"null": false
}
}
}
}

View File

@ -78,7 +78,7 @@ final class Installer extends InstallerAbstract
**/
private static function installDefaultSettings() : void
{
SettingMapper::create()->execute(new Setting(0, SettingsEnum::PASSWORD_PATTERN, '', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::PASSWORD_PATTERN, '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::LOGIN_TRIES, '3', '\\d+', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::LOGIN_TIMEOUT, '3', '\\d+', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::PASSWORD_INTERVAL, '90', '\\d+', module: 'Admin'));
@ -86,7 +86,7 @@ final class Installer extends InstallerAbstract
SettingMapper::create()->execute(new Setting(0, SettingsEnum::LOGGING_STATUS, '1', '[0-3]', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::LOGGING_PATH, '', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::DEFAULT_ORGANIZATION, '1', '\\d+', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::DEFAULT_UNIT, '1', '\\d+', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::LOGIN_STATUS, '1', '[0-3]', module: 'Admin'));
@ -101,6 +101,8 @@ final class Installer extends InstallerAbstract
SettingMapper::create()->execute(new Setting(0, SettingsEnum::MAIL_SERVER_KEYPASS, '', module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::MAIL_SERVER_TLS, (string) false, module: 'Admin'));
SettingMapper::create()->execute(new Setting(0, SettingsEnum::GROUP_GENERATE_AUTOMATICALLY_APP, (string) true, module: 'Admin'));
$cmdResult = \shell_exec(
(OperatingSystem::getSystem() === SystemType::WIN
? 'php.exe'

View File

@ -14,6 +14,8 @@ declare(strict_types=1);
namespace Modules\Admin\Controller;
use Model\Setting;
use Model\SettingMapper;
use Modules\Admin\Models\Account;
use Modules\Admin\Models\AccountCredentialMapper;
use Modules\Admin\Models\AccountMapper;
@ -36,6 +38,9 @@ use Modules\Media\Models\UploadFile;
use Modules\Admin\Models\App;
use phpOMS\Application\ApplicationType;
use Modules\Admin\Models\AppMapper;
use Modules\Admin\Models\DataChange;
use Modules\Admin\Models\NullDataChange;
use Modules\Admin\Models\DataChangeMapper;
use phpOMS\Account\AccountStatus;
use phpOMS\Account\AccountType;
use phpOMS\Account\GroupStatus;
@ -420,6 +425,7 @@ final class ApiController extends Controller
'response' => $this->app->appSettings->get(
$id !== null ? (int) $id : $id,
$request->getData('name') ?? '',
$request->getData('unit') ?? null,
$request->getData('app') ?? null,
$request->getData('module') ?? null,
$group !== null ? (int) $group : $group,
@ -478,36 +484,133 @@ final class ApiController extends Controller
$id = isset($data['id']) ? (int) $data['id'] : null;
$name = $data['name'] ?? null;
$content = $data['content'] ?? null;
$unit = $data['unit'] ?? null;
$app = $data['app'] ?? null;
$module = $data['module'] ?? null;
$group = isset($data['group']) ? (int) $data['group'] : null;
$account = isset($data['account']) ? (int) $data['account'] : null;
$this->updateModel(
$request->header->account,
$this->app->appSettings->get($id, $name, $app, $module, $group, $account),
$data,
function () use ($id, $name, $content, $app, $module, $group, $account) : void {
$this->app->appSettings->set([
[
'id' => $id,
'name' => $name,
'content' => $content,
'app' => $app,
'module' => $module,
'group' => $group,
'account' => $account,
],
], true);
},
'settings',
$request->getOrigin()
);
$old = $this->app->appSettings->get($id, $name, $unit, $app, $module, $group, $account);
$new = clone $old;
$new->name = $name ?? $new->name;
$new->content = $content ?? $new->content;
$new->unit = $unit ?? $new->unit;
$new->app = $app ?? $new->app;
$new->module = $module ?? $new->module;
$new->group = $group ?? $new->group;
$new->account = $account ?? $new->account;
$this->app->appSettings->set([
[
'id' => $new->id,
'name' => $new->name,
'content' => $new->content,
'unit' => $new->unit,
'app' => $new->app,
'module' => $new->module,
'group' => $new->group,
'account' => $new->account
]
], false);
$this->updateModel($request->header->account, $old, $new, SettingMapper::class, 'settings',$request->getOrigin());
}
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Settings', 'Settings successfully modified', $dataSettings);
}
/**
* Api method for modifying account password
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiSettingsAccountPasswordSet(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
// has required data
if (!empty($val = $this->validatePasswordUpdate($request))) {
$response->set('password_update', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$requestAccount = $request->header->account;
// request account is valid
if ($requestAccount <= 0) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
$account = AccountMapper::get()
->where('id', $requestAccount)
->execute();
// test old password is correct
if (AccountMapper::login($account->login, (string) $request->getData('oldpass')) !== $requestAccount) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
// test password repetition
if (((string) $request->getData('newpass')) !== ((string) $request->getData('reppass'))) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
// test password complexity
$complexity = $this->app->appSettings->get(names: [SettingsEnum::PASSWORD_PATTERN], module: 'Admin');
if (\preg_match($complexity->content, (string) $request->getData('newpass')) !== 1) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
$account->generatePassword((string) $request->getData('newpass'));
AccountMapper::update()->execute($account);
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Password', 'Password successfully modified', $account);
}
/**
* Validate password update request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validatePasswordUpdate(RequestAbstract $request) : array
{
$val = [];
if (($val['oldpass'] = empty($request->getData('oldpass')))
|| ($val['newpass'] = empty($request->getData('newpass')))
|| ($val['reppass'] = empty($request->getData('reppass')))
) {
return $val;
}
return [];
}
/**
* Api method for modifying account localization
*
@ -529,7 +632,7 @@ final class ApiController extends Controller
if ($requestAccount !== $accountId
&& !$this->app->accountManager->get($accountId)->hasPermission(
PermissionType::MODIFY,
$this->app->orgId,
$this->app->unitId,
$this->app->appName,
self::NAME,
PermissionCategory::ACCOUNT_SETTINGS,
@ -703,11 +806,33 @@ final class ApiController extends Controller
}
$app = $this->createApplicationFromRequest($request);
$this->createModel($request->header->account, $app, AppMapper::class, 'application', $request->getOrigin());
$this->createDefaultAppSettings($app, $request);
/** @var \Model\Setting $setting */
$setting = $this->app->appSettings->get(null, SettingsEnum::GROUP_GENERATE_AUTOMATICALLY_APP);
if ($setting->content === '1') {
$newRequest = new HttpRequest();
$newRequest->header->account = $request->header->account;
$newRequest->setData('name', 'app:' . \strtolower($app->name));
$newRequest->setData('status', GroupStatus::ACTIVE);
$this->apiGroupCreate($newRequest, $response, $data);
}
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Application', 'Application successfully created', $app);
}
private function createDefaultAppSettings(App $app, RequestAbstract $request) : void
{
$settings = [];
$settings[] = new Setting(0, SettingsEnum::REGISTRATION_ALLOWED, '0', '\\d+', app: $app->getId(), module: 'Admin');
$settings[] = new Setting(0, SettingsEnum::APP_DEFAULT_GROUPS, '[]', app: $app->getId(), module: 'Admin');
foreach ($settings as $setting) {
$this->createModel($request->header->account, $setting, SettingMapper::class, 'setting', $request->getOrigin());
}
}
/**
* Validate app create request
*
@ -1148,6 +1273,37 @@ final class ApiController extends Controller
$this->createProfileForAccount($account, $request);
$this->createMediaDirForAccount($account->getId(), $account->login ?? '', $request->header->account);
// find default groups and create them
$defaultGroups = [];
$defaultGroupIds = [];
if ($request->hasData('app')) {
$defaultGroupSettings = $this->app->appSettings->get(
names: SettingsEnum::APP_DEFAULT_GROUPS,
app: (int) $request->getData('app'),
module: 'Admin'
);
$defaultGroups = \array_merge($defaultGroups, \json_decode($defaultGroupSettings->content, true));
}
if ($request->hasData('unit')) {
$defaultGroupSettings = $this->app->appSettings->get(
names: SettingsEnum::UNIT_DEFAULT_GROUPS,
unit: (int) $request->getData('unit'),
module: 'Admin'
);
$defaultGroups = \array_merge($defaultGroups, \json_decode($defaultGroupSettings->content, true));
}
foreach ($defaultGroups as $group) {
$defaultGroupIds[] = $group->getId();
}
if (!empty($defaultGroupIds)) {
$this->createModelRelation($account->getId(), $account->getId(), $defaultGroupIds, AccountMapper::class, 'groups', 'account', $request->getOrigin());
}
$this->fillJsonResponse(
$request,
$response,
@ -1160,6 +1316,240 @@ final class ApiController extends Controller
);
}
public function apiAccountRegister(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateRegistration($request))) {
$response->set('account_registration', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$allowed = $this->app->appSettings->get(
names: [SettingsEnum::REGISTRATION_ALLOWED],
app: (int) $request->getData('app'),
module: 'Admin'
);
if ($allowed->content !== '1') {
$this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Registration', 'Registration not allowed', []);
$response->header->status = RequestStatusCode::R_400;
return;
}
$complexity = $this->app->appSettings->get(names: [SettingsEnum::PASSWORD_PATTERN], module: 'Admin');
if ($request->hasData('password')
&& \preg_match($complexity->content, (string) $request->getData('password')) !== 1
) {
$this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Registration', 'Invalid password format', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
// Check if account already exists
/** @var Account $emailAccount */
$emailAccount = AccountMapper::get()->where('email', (string) $request->getData('email'))->execute();
/** @var Account $loginAccount */
$loginAccount = AccountMapper::get()->where('login', (string) ($request->getData('login') ?? $request->getData('email')))->execute();
/** @var null|Account $account */
$account = null;
// email already in use
if (!($emailAccount instanceof NullAccount)
&& AccountMapper::login($emailAccount->login, (string) $request->getData('password')) !== LoginReturnType::OK
) {
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Registration', 'Email already in use, use your login details to login or activate your account also for this service.', []);
$response->header->status = RequestStatusCode::R_400;
return;
} elseif (!($emailAccount instanceof NullAccount)) {
$account = $emailAccount;
}
// login already in use by different email
if ($account === null
&& !($loginAccount instanceof NullAccount)
&& $loginAccount->getEmail() !== $request->getData('email')
) {
$this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Registration', 'Login already in use with a different email', []);
$response->header->status = RequestStatusCode::R_400;
return;
} elseif ($account === null
&& !($loginAccount instanceof NullAccount)
&& AccountMapper::login($loginAccount->login, (string) $request->getData('password')) !== LoginReturnType::OK
) {
$account = $loginAccount;
}
$defaultGroups = [];
$defaultGroupIds = [];
$defaultGroupSettings = $this->app->appSettings->get(
names: SettingsEnum::APP_DEFAULT_GROUPS,
app: (int) $request->getData('app'),
module: 'Admin'
);
$defaultGroups = \array_merge($defaultGroups, \json_decode($defaultGroupSettings->content, true));
$defaultGroupSettings = $this->app->appSettings->get(
names: SettingsEnum::UNIT_DEFAULT_GROUPS,
unit: (int) $request->getData('unit'),
module: 'Admin'
);
$defaultGroups = \array_merge($defaultGroups, \json_decode($defaultGroupSettings->content, true));
foreach ($defaultGroups as $group) {
$defaultGroupIds[] = $group->getId();
}
// Already registered
if ($account !== null) {
$account = AccountMapper::get()
->with('groups')
->where('id', $account->getId())
->execute();
foreach ($defaultGroupIds as $index => $id) {
if ($account->hasGroup($id)) {
unset($defaultGroupIds[$index]);
}
}
if (empty($defaultGroupIds)
&& $account->getStatus() === AccountStatus::ACTIVE
) {
$this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Registration', 'You are already registered, use your login data.', []);
$response->header->status = RequestStatusCode::R_403;
return;
} elseif (empty($defaultGroupIds)
&& $account->getStatus() === AccountStatus::INACTIVE
) {
$this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Registration', 'You are already registered, please activate your account through the email we sent you.', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
// Create missing account / group relationships
$this->createModelRelation($account->getId(), $account->getId(), $defaultGroupIds, AccountMapper::class, 'groups', 'registration', $request->getOrigin());
} else {
$request->setData('status', AccountStatus::INACTIVE);
$request->setData('type', AccountType::USER);
$request->setData('name1', !$request->hasData('name1')
? \explode('@', $request->getData('email'))[0]
: $request->getData('name1')
);
$request->setData('login', $request->getData('login') ?? $request->getData('email'));
$this->apiAccountCreate($request, $response, $data);
$account = $response->get($request->uri->__toString())['response'];
// Create confirmation pending entry
$dataChange = new DataChange();
$dataChange->type = 'account';
$dataChange->createdBy = $account->getId();
$dataChange->data = \json_encode([
'status' => AccountStatus::ACTIVE
]);
$tries = 0;
do {
$dataChange->reHash();
$this->createModel($account->getId(), $dataChange, DataChangeMapper::class, 'datachange', $request->getOrigin());
++$tries;
} while($dataChange->getId() === 0 && $tries < 5);
}
// Create confirmation email
// @todo: send email for activation
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Registration', 'We have sent you an email to confirm your registration.', $account);
}
/**
* Method to validate account registration from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateRegistration(RequestAbstract $request) : array
{
$val = [];
if (($val['email'] = !empty($request->getData('email'))
&& !EmailValidator::isValid((string) $request->getData('email')))
|| ($val['unit'] = empty($request->getData('unit')))
|| ($val['app'] = empty($request->getData('app')))
|| ($val['password'] = empty($request->getData('password')))
) {
return $val;
}
return [];
}
// @todo: maybe move to job/workflow??? This feels very much like a job/event especially if we make the 'type' an event-trigger
public function apiDataChange(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateDataChange($request))) {
$response->set('data_change', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
/** @var DataChange $dataChange */
$dataChange = DataChangeMapper::get()->where('hash', (string) $request->getData('hash'))->execute();
if ($dataChange instanceof NullDataChange) {
$response->header->status = RequestStatusCode::R_400;
return;
}
switch ($dataChange->type) {
case 'account':
$old = AccountMapper::get()->where('id', $dataChange->createdBy)->execute();
$new = clone $old;
$data = \json_decode($dataChange->data, true);
$new->setStatus((int) $data['status']);
$this->updateModel($dataChange->createdBy, $old, $new, AccountMapper::class, 'datachange', $request->getOrigin());
$this->deleteModel($dataChange->createdBy, $dataChange, DataChangeMapper::class, 'datachange', $request->getOrigin());
break;
}
}
/**
* Method to validate account registration from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateDataChange(RequestAbstract $request) : array
{
$val = [];
if (($val['hash'] = empty($request->getData('hash')))) {
return $val;
}
return [];
}
/**
* Create directory for an account
*
@ -1285,11 +1675,18 @@ final class ApiController extends Controller
public function apiAccountUpdate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
/** @var Account $old */
$old = AccountMapper::get()->where('id', (int) $request->getData('id'))->execute();
$old = AccountMapper::get()
->where('id', (int) $request->getData('id'))
->execute();
$new = $this->updateAccountFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, AccountMapper::class, 'account', $request->getOrigin());
if (\Modules\Profile\Models\ProfileMapper::get()->where('account', $new->getId())->execute() instanceof \Modules\Profile\Models\NullProfile) {
$profile = \Modules\Profile\Models\ProfileMapper::get()
->where('account', $new->getId())
->execute();
if ($profile instanceof \Modules\Profile\Models\NullProfile) {
$this->createProfileForAccount($new, $request);
}
@ -1418,6 +1815,7 @@ final class ApiController extends Controller
$moduleObj->theme = 'Default';
$moduleObj->path = $moduleInfo->getDirectory();
$moduleObj->version = $moduleInfo->getVersion();
$moduleObj->name = $moduleInfo->getExternalName();
$moduleObj->setStatus(ModuleStatus::AVAILABLE);
@ -1725,7 +2123,7 @@ final class ApiController extends Controller
$permission->setUnit(empty($request->getData('permissionunit')) ? null : (int) $request->getData('permissionunit'));
$permission->setApp(empty($request->getData('permissionapp')) ? null : (string) $request->getData('permissionapp'));
$permission->setModule(empty($request->getData('permissionmodule')) ? null : (string) $request->getData('permissionmodule'));
$permission->setCategory(empty($request->getData('permissiontype')) ? null : (int) $request->getData('permissiontype'));
$permission->setCategory(empty($request->getData('permissioncategory')) ? null : (int) $request->getData('permissioncategory'));
$permission->setElement(empty($request->getData('permissionelement')) ? null : (int) $request->getData('permissionelement'));
$permission->setComponent(empty($request->getData('permissioncomponent')) ? null : (int) $request->getData('permissioncomponent'));
$permission->setPermission(
@ -1811,7 +2209,7 @@ final class ApiController extends Controller
$permission->setUnit(empty($request->getData('permissionunit')) ? $permission->getUnit() : (int) $request->getData('permissionunit'));
$permission->setApp(empty($request->getData('permissionapp')) ? $permission->getApp() : (string) $request->getData('permissionapp'));
$permission->setModule(empty($request->getData('permissionmodule')) ? $permission->getModule() : (string) $request->getData('permissionmodule'));
$permission->setCategory(empty($request->getData('permissiontype')) ? $permission->getCategory() : (int) $request->getData('permissiontype'));
$permission->setCategory(empty($request->getData('permissioncategory')) ? $permission->getCategory() : (int) $request->getData('permissioncategory'));
$permission->setElement(empty($request->getData('permissionelement')) ? $permission->getElement() : (int) $request->getData('permissionelement'));
$permission->setComponent(empty($request->getData('permissioncomponent')) ? $permission->getComponent() : (int) $request->getData('permissioncomponent'));
$permission->setPermission((int) ($request->getData('permissioncreate') ?? 0)

View File

@ -720,7 +720,7 @@ final class BackendController extends Controller
/** @var \Model\Setting[] $generalSettings */
$generalSettings = $this->app->appSettings->get(
names: [
SettingsEnum::PASSWORD_PATTERN, SettingsEnum::LOGIN_TIMEOUT, SettingsEnum::PASSWORD_INTERVAL, SettingsEnum::PASSWORD_HISTORY, SettingsEnum::LOGIN_TRIES, SettingsEnum::LOGGING_STATUS, SettingsEnum::LOGGING_PATH, SettingsEnum::DEFAULT_ORGANIZATION,
SettingsEnum::PASSWORD_PATTERN, SettingsEnum::LOGIN_TIMEOUT, SettingsEnum::PASSWORD_INTERVAL, SettingsEnum::PASSWORD_HISTORY, SettingsEnum::LOGIN_TRIES, SettingsEnum::LOGGING_STATUS, SettingsEnum::LOGGING_PATH, SettingsEnum::DEFAULT_UNIT,
SettingsEnum::LOGIN_STATUS, SettingsEnum::DEFAULT_LOCALIZATION, SettingsEnum::MAIL_SERVER_ADDR,
],
module: 'Admin'

View File

@ -213,7 +213,7 @@ class AccountMapper extends DataMapperFactory
'account_lactive' => new \DateTime('now'),
'account_tries' => 0,
])
->where('account_login', '=', $login)
->where('account_id', '=', (int) $result['account_id'])
->execute();
return $result['account_id'];
@ -230,7 +230,7 @@ class AccountMapper extends DataMapperFactory
'account_lactive' => new \DateTime('now'),
'account_tries' => 0,
])
->where('account_login', '=', $login)
->where('account_id', '=', (int) $result['account_id'])
->execute();
return $result['account_id'];
@ -240,7 +240,7 @@ class AccountMapper extends DataMapperFactory
->set([
'account_tries' => $result['account_tries'] + 1,
])
->where('account_login', '=', $login)
->where('account_id', '=', (int) $result['account_id'])
->execute();
return LoginReturnType::WRONG_PASSWORD;

79
Models/ApiKey.php Normal file
View File

@ -0,0 +1,79 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Admin\Models;
use phpOMS\Account\AccountStatus;
/**
* Account class.
*
* @package Modules\Admin\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class Account
{
/**
* Id.
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Names.
*
* @var string
* @since 1.0.0
*/
public string $key = '';
/**
* Account status.
*
* @var int
* @since 1.0.0
*/
protected int $status = AccountStatus::INACTIVE;
/**
* Creator.
*
* @var int
* @since 1.0.0
*/
public int $account = 0;
/**
* Created.
*
* @var \DateTimeImmutable
* @since 1.0.0
*/
public \DateTimeImmutable $createdAt;
/**
* Constructor.
*
* @since 1.0.0
*/
public function __construct()
{
$this->key = \random_bytes(128);
$this->createdAt = new \DateTimeImmutable('now');
}
}

121
Models/ApiKeyMapper.php Normal file
View File

@ -0,0 +1,121 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Admin\Models;
use phpOMS\Account\AccountStatus;
use phpOMS\Auth\LoginReturnType;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
use phpOMS\DataStorage\Database\Query\Builder;
/**
* Account mapper class.
*
* @package Modules\Admin\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class ApiKeyMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'account_api_id' => ['name' => 'account_api_id', 'type' => 'int', 'internal' => 'id'],
'account_api_key' => ['name' => 'account_api_key', 'type' => 'string', 'internal' => 'key'],
'account_api_status' => ['name' => 'account_api_status', 'type' => 'int', 'internal' => 'status'],
'account_api_account' => ['name' => 'account_api_account', 'type' => 'int', 'internal' => 'account'],
'account_api_created_at' => ['name' => 'account_api_created_at', 'type' => 'DateTime', 'internal' => 'createdAt'],
];
/**
* Model to use by the mapper.
*
* @var string
* @since 1.0.0
*/
public const MODEL = ApiKey::class;
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'account_api';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='account_api_id';
/**
* Created at column
*
* @var string
* @since 1.0.0
*/
public const CREATED_AT = 'account_api_created_at';
public static function authenticateApiKey(string $api) : int
{
if (empty($api)) {
return LoginReturnType::WRONG_PASSWORD;
}
try {
$result = null;
$query = new Builder(self::$db);
$result = $query->select('account.account_id', 'account.account_status')
->from('account')
->innerJoin('account_api')->on('account.account_id', '=', 'account_api.account_api_account')
->where('account_api.account_api_key', '=', $api)
->execute()
?->fetchAll();
if ($result === null || !isset($result[0])) {
return LoginReturnType::WRONG_USERNAME; // wrong api key
}
$result = $result[0];
if ($result['account_status'] !== AccountStatus::ACTIVE) {
return LoginReturnType::INACTIVE;
}
if (empty($result['account_password'])) {
return LoginReturnType::EMPTY_PASSWORD;
}
$query->update('account')
->set([
'account_lactive' => new \DateTime('now'),
])
->where('account_id', '=', (int) $result['account_id'])
->execute();
return (int) $result['account_id'];
} catch (\Exception $e) {
return LoginReturnType::FAILURE; // @codeCoverageIgnore
}
}
}

View File

@ -25,7 +25,7 @@ use phpOMS\Application\ApplicationType;
* @link https://jingga.app
* @since 1.0.0
*/
class App
class App implements \JsonSerializable
{
/**
* Id
@ -78,4 +78,25 @@ class App
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'name' => $this->name,
'type' => $this->type,
'status' => $this->status,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

109
Models/DataChange.php Normal file
View File

@ -0,0 +1,109 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Admin\Models;
/**
* App model.
*
* @package Modules\Admin\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class DataChange
{
/**
* Id
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Hash
*
* @var string
* @since 1.0.0
*/
protected string $hash = '';
public string $data = '';
public string $type = '';
public int $createdBy = 0;
public \DateTimeImmutable $createdAt;
/**
* Constructor.
*
* @since 1.0.0
*/
public function __construct()
{
$this->createdAt = new \DateTimeImmutable('now');
$this->reHash();
}
/**
* Get id
*
* @return int
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
public function reHash() : void
{
$this->hash = \random_bytes(64);
}
/**
* Get hash
*
* @return string
*
* @since 1.0.0
*/
public function getHash() : string
{
return $this->hash;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'data' => $this->data,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Admin\Models;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* Account mapper class.
*
* @package Modules\Admin\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class DataChangeMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'data_change_id' => ['name' => 'data_change_id', 'type' => 'int', 'internal' => 'id'],
'data_change_type' => ['name' => 'data_change_type', 'type' => 'string', 'internal' => 'type'],
'data_change_hash' => ['name' => 'data_change_hash', 'type' => 'string', 'internal' => 'hash'],
'data_change_data' => ['name' => 'data_change_data', 'type' => 'string', 'internal' => 'data'],
'data_change_created_by' => ['name' => 'data_change_created_by', 'type' => 'int', 'internal' => 'createdBy'],
'data_change_created_at' => ['name' => 'data_change_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt'],
];
/**
* Model to use by the mapper.
*
* @var string
* @since 1.0.0
*/
public const MODEL = DataChange::class;
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'data_change';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='data_change_id';
}

View File

@ -41,8 +41,8 @@ class GroupPermission extends PermissionAbstract
* Constructor.
*
* @param int $group Group id
* @param null|int $unit Unit Unit to check (null if all are acceptable)
* @param null|string $app App App to check (null if all are acceptable)
* @param null|int $unit Unit to check (null if all are acceptable)
* @param null|string $app App to check (null if all are acceptable)
* @param null|string $module Module to check (null if all are acceptable)
* @param null|string $from Module providing this permission
* @param null|int $category Category (e.g. customer) (null if all are acceptable)

View File

@ -34,6 +34,7 @@ final class ModuleMapper extends DataMapperFactory
*/
public const COLUMNS = [
'module_id' => ['name' => 'module_id', 'type' => 'string', 'internal' => 'id'],
'module_name' => ['name' => 'module_name', 'type' => 'string', 'internal' => 'name'],
'module_path' => ['name' => 'module_path', 'type' => 'string', 'internal' => 'path'],
'module_theme' => ['name' => 'module_theme', 'type' => 'string', 'internal' => 'theme'],
'module_version' => ['name' => 'module_version', 'type' => 'string', 'internal' => 'version'],

47
Models/NullDataChange.php Normal file
View File

@ -0,0 +1,47 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Admin\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Admin\Models;
/**
* Null model
*
* @package Modules\Admin\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullDataChange extends DataChange
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -42,7 +42,9 @@ abstract class SettingsEnum extends Enum
public const LOGGING_PATH = '1000000007';
/* Organization settings */
public const DEFAULT_ORGANIZATION = '1000000009';
public const DEFAULT_UNIT = '1000000008';
public const UNIT_DEFAULT_GROUPS = '1000000009';
/* Login settings */
public const LOGIN_FORGOTTEN_COUNT = '1000000010';
@ -77,13 +79,14 @@ abstract class SettingsEnum extends Enum
public const CLI_ACTIVE = '1000000023';
/* Global default templates */
public const DEFAULT_PDF_EXPORT_TEMPLATE = '1000000024';
public const DEFAULT_LIST_EXPORTS = '1000000024';
public const DEFAULT_CSV_EXPORT_TEMPLATE = '1000000025';
public const DEFAULT_LETTERS = '1000000025';
public const DEFAULT_EXCEL_EXPORT_TEMPLATE = '1000000026';
/* App settings */
public const REGISTRATION_ALLOWED = '1000000029';
public const DEFAULT_WORD_EXPORT_TEMPLATE = '1000000027';
public const GROUP_GENERATE_AUTOMATICALLY_APP = '1000000030';
public const DEFAULT_EMAIL_EXPORT_TEMPLATE = '1000000028';
public const APP_DEFAULT_GROUPS = '1000000031';
}

View File

@ -59,7 +59,7 @@ final class ApiControllerTest extends \PHPUnit\Framework\TestCase
};
$this->app->dbPool = $GLOBALS['dbpool'];
$this->app->orgId = 1;
$this->app->unitId = 1;
$this->app->accountManager = new AccountManager($GLOBALS['session']);
$this->app->appSettings = new CoreSettings();
$this->app->moduleManager = new ModuleManager($this->app, __DIR__ . '/../../../../Modules/');