Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,403 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Database\Database;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Converter\Converter;
use Xentral\Modules\Api\Converter\Exception\ConvertionException;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
use Xentral\Modules\Api\Resource\AbstractResource;
use Xentral\Modules\Api\Resource\ResourceManager;
use Xentral\Modules\Api\Resource\Result\AbstractResult;
abstract class AbstractController
{
/** @var Database $db */
protected $db;
/** @var Request $request */
protected $request;
/** @var Response $response */
protected $response;
/** @var ResourceManager $resourceManager */
protected $resourceManager;
/** @var string $resourceClass */
protected $resourceClass;
/** @var \Api $db */
protected $legacyApi;
/**
* @param \Api $legacyApi
* @param Database $database
* @param Converter $converter
* @param Request $request
* @param ResourceManager $resource
*/
public function __construct($legacyApi, $database, $converter, $request, $resource)
{
$this->resourceManager = $resource;
$this->legacyApi = $legacyApi;
$this->converter = $converter;
$this->request = $request;
$this->db = $database;
}
/**
* @param string $action Controller-Action
*
* @return Response
*/
public function dispatch($action)
{
if (substr($action, -6) !== 'Action') {
throw new \RuntimeException(sprintf(
'API controller action "%s" is not dispatchable.', $action
));
}
if (!method_exists($this, $action)) {
throw new \RuntimeException(sprintf(
'API controller method "%s" not found', $action
));
}
$this->response = $this->$action();
if ($this->response === null) {
throw new \RuntimeException('Controller must return a Response object. Null given.');
}
if (!$this->response instanceof Response) {
throw new \RuntimeException('Controller must return a Response object.');
}
return $this->response;
}
/**
* @param string $className
*/
public function setResourceClass($className)
{
$this->resourceClass = $className;
}
/**
* ID aus der URL (Route) bekommen
*
* @return int
*/
protected function getResourceId()
{
return (int)$this->request->attributes->getDigits('id');
}
/**
* @param string|null $className
*
* @return AbstractResource
*/
protected function getResource($className = null)
{
return $this->resourceManager->get($className !== null ? $className : $this->resourceClass);
}
/**
* Request-Body in Array wandeln
*
* @return array
*/
protected function getRequestData()
{
try {
return $this->converter->toArray($this->getContentType(), $this->request->getContent());
} catch (ConvertionException $e) {
throw new BadRequestException(
sprintf('%s could not be decoded.', strtoupper($this->getContentType())),
ApiError::CODE_MALFORMED_REQUEST_BODY
);
}
}
/**
* @return null|string [json|xml]
*/
protected function getContentType()
{
return $this->request->getContentType();
}
/**
* @param AbstractResult $result
* @param int $statusCode
*
* @return Response
*/
protected function sendResult(AbstractResult $result, $statusCode = Response::HTTP_OK)
{
$contentType = $this->determineResponseContentType();
$data = [];
if ($contentType === 'xml') {
if ($result->isCollection()) {
$data['items'] = $result->getData();
$data['pagination'] = $result->getPagination();
} else {
$data['item'] = $result->getData();
}
}
if ($contentType === 'json') {
$data = $result->getResult();
}
return $this->sendResponse($data, $contentType, $statusCode);
}
/**
* Content-Type für die Ausgabe bestimmen
*
* @return string [xml|json]
*/
protected function determineResponseContentType()
{
// Accept-Header auslesen
$acceptable = $this->request->getAcceptableContentTypes();
switch ($acceptable[0]) {
// Client ist vermutlich ein Browser > JSON ausliefern
case 'text/html':
$contentType = 'json';
break;
// Client hat JSON angefragt
case 'application/json':
$contentType = 'json';
break;
// Client hat XML angefragt
case 'application/xml':
$contentType = 'xml';
break;
// Nicht eindeutig > JSON bevorzugen
default:
if (in_array('application/xml', $acceptable)) {
$contentType = 'xml';
break;
}
$contentType = 'json';
break;
}
return $contentType;
}
/**
* @param array $data
* @param string $contentType [xml|json]
* @param int $statusCode HTTP-Statuscode
*
* @return Response
*/
protected function sendResponse($data, $contentType, $statusCode = Response::HTTP_OK)
{
if ($contentType === 'xml') {
return new Response(
$this->converter->arrayToXml($data, 'result'),
$statusCode,
['Content-Type' => 'application/xml; charset=UTF-8']
);
}
return new Response(
$this->converter->arrayToJson($data),
$statusCode,
['Content-Type' => 'application/json; charset=UTF-8']
);
}
/**
* Filterparameter aufbereiten
*
* @example /resource?title=123&project=1
* @example /resource?title_starts_with=123&project=1
*
* @return array
*/
protected function prepareFilterParams()
{
$queryParams = $this->request->get->all();
// Reservierte Parameter ignorieren
unset(
$queryParams['sort'],
$queryParams['page'],
$queryParams['items'],
$queryParams['filter'],
$queryParams['include']
);
$filter = [];
foreach ($queryParams as $filterKey => $filterValue) {
$filter[$filterKey] = filter_var($filterValue, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
}
// Komplexe Suchfilter enthalten Array
$filter['filter'] = $this->prepareComplexFilterParams();
return $filter;
}
/**
* Filterparameter für komplexe Suche aufbereiten
*
* @example /resource?filter[0][property]=satz&filter[0][expression]=gte&filter[0][value]=10
* &filter[1][property]=bezeichnung&filter[1][value]=%Irland%
*
* @return array
*/
protected function prepareComplexFilterParams()
{
$filter = [];
$params = $this->request->get->get('filter');
if (!is_array($params)) {
return $filter;
}
ksort($params);
$params = array_values($params);
return $params;
foreach ($params as $param) {
echo "<pre>";
var_dump($params);
echo "</pre>";
exit;
// @todo Sanitize
echo "<pre>";
var_dump($param);
echo "</pre>";
exit;
}
return $filter;
}
/**
* Sortierungsparameter aufbereiten
*
* @example /resource?sort=name,project
* @example /resource?sort=-name,project
*
* @return array
*/
protected function prepareSortingParams()
{
$sorting = [];
$sortQuery = filter_var($this->request->get->get('sort'), FILTER_SANITIZE_URL);
if (empty($sortQuery)) {
return $sorting;
}
/**
* Alte Syntax
*
* @example /resource?sort=title:desc|projekt:asc
*/
if (strpos($sortQuery, '|')) {
$sortParams = explode('|', $sortQuery);
foreach ($sortParams as $sortParam) {
if (strpos($sortParam, ':')) {
list($sortField, $sortOrder) = explode(':', $sortParam, 2);
} else {
$sortField = $sortParam;
$sortOrder = 'asc';
}
if (empty($sortField) || $sortField === ':') {
throw new InvalidArgumentException('Sorting parameter can not be empty');
}
if (!in_array(strtolower($sortOrder), ['asc', 'desc'], true)) {
throw new InvalidArgumentException(sprintf(
'Sorting order "%s" is not valid. Use "asc" or "desc".', $sortOrder
));
}
$sortOrder = strtolower($sortOrder) === 'desc' ? 'DESC' : 'ASC';
$sorting[$sortField] = $sortOrder;
}
return $sorting;
}
/**
* Neue Syntax: Minuszeichen vor dem Feld kehrt die Sortierung um
*
* @example /resource?sort=-title,projekt
*/
$sortParams = explode(',', $sortQuery);
foreach ($sortParams as $sortParam) {
if (strpos($sortParam, '-') === 0) {
$sortField = substr_replace($sortParam, '', 0, 1);
$sortOrder = 'DESC';
} else {
$sortField = $sortParam;
$sortOrder = 'ASC';
}
if (empty($sortField) || $sortField === '-') {
throw new InvalidArgumentException('Sorting parameter can not be empty');
}
$sorting[$sortField] = $sortOrder;
}
return $sorting;
}
/**
* @return array
*/
protected function prepareIncludeParams()
{
$includesQuery = $this->request->get->get('include');
if (empty($includesQuery)) {
return [];
}
$includes = explode(',', $includesQuery);
$includes = array_map('trim', $includes);
$includes = array_map('htmlspecialchars', $includes);
return $includes;
}
/**
* @return int
*/
protected function getPaginationPage()
{
$page = $this->request->get->getInt('page');
return $page > 0 && $page <= 1000 ? $page : 1;
}
/**
* @return int
*/
protected function getPaginationCount()
{
$items = $this->request->get->getInt('items');
return $items > 0 && $items <= 1000 ? $items : 20;
}
}
@@ -0,0 +1,216 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use SimpleXMLElement;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Resource\Result\CollectionResult;
use Xentral\Modules\Api\Resource\Result\ItemResult;
class AddressController extends AbstractController
{
/**
* Adressliste abrufen
*
* @example GET /v1/adressen
*
* @return Response
*/
public function listAction()
{
// Kundennummer ist optional; dann nur eine Adresse zurückliefern
$kundennummer = filter_var($this->request->get->get('kundennummer'), FILTER_SANITIZE_STRING);
if (!empty($kundennummer)) {
return $this->findByCustomerNumberAction($kundennummer);
}
// Optionale GET-Parameter
$page = $this->getPaginationPage();
$itemsPerPage = $this->getPaginationCount();
// Limit und Offset aus Parameter berechnen
$limit = $itemsPerPage;
$offset = ($page - 1) * $itemsPerPage;
$this->legacyApi->app->Secure->GET['action'] = 'AdresseListeGet';
$this->legacyApi->app->Secure->GET['json'] = true;
$this->legacyApi->app->Secure->POST['xml'] =
'<xml>'.
'<limit>'.$limit.'</limit>'.
'<offset>'.$offset.'</offset>'.
'<gruppen><kennziffer></kennziffer></gruppen>'. // @todo kennziffer
'</xml>';
/** @var SimpleXMLElement $xml */
$xml = $this->legacyApi->ApiAdresseListeGet(true);
$data = $this->converter->xmlToArray($xml);
// Paginierung aus den Ergebnissen basteln
$pagination = array();
$pagination['items_per_page'] = $limit;
$pagination['items_current'] = (int)$data['anz_result'];
$pagination['items_total'] = (int)$data['anz_gesamt'];
$pagination['page_current'] = (int)floor($offset / $limit) + 1;
$pagination['page_last'] = (int)ceil($pagination['items_total'] / $limit);
// Ergebnis aus alter API umstrukturieren
$result = new CollectionResult($data['adresse'], $pagination);
return $this->sendResult($result);
}
/**
* Einzelne Adresse per ID abrufen
*
* @example GET /v1/adressen/999
*
* @return Response
*/
public function readAction()
{
$id = $this->request->attributes->getInt('id');
$data = $this->getAddressById($id);
$result = new ItemResult($data);
return $this->sendResult($result);
}
/**
* @param string $number Kundennummer
*
* @return Response
*/
public function findByCustomerNumberAction($number)
{
$data = $this->getAddressByCustomerNumber($number);
$result = new ItemResult($data);
return $this->sendResult($result);
}
/**
* Adresse anlegen
*
* @example POST /v1/adressen
*
* @return Response
*/
public function createAction()
{
// Request-Body in $_POST['json'] schreiben
$requestBody = file_get_contents('php://input');
$this->legacyApi->app->Secure->POST['json'] = $requestBody;
$this->legacyApi->app->Secure->GET['action'] = 'AdresseCreate';
// Adresse anlegen
$customerNumber = $this->legacyApi->ApiAdresseCreate(true);
if (intval($customerNumber) <= 0) {
// @todo Nicht sehr hilfreiche Meldung
// @todo Refaktorieren und besser Exception werfen
throw new BadRequestException('Adresse konnte nicht angelegt werden.');
}
// Anlage war erfolgreich > Erzeugte Resource zurückliefern
$data = $this->getAddressByCustomerNumber($customerNumber);
$result = new ItemResult($data);
return $this->sendResult($result);
}
/**
* Adresse aktualisieren
*
* @example PUT /v1/adressen/999
*
* @return Response
*/
public function updateAction()
{
$id = $this->request->attributes->getInt('id');
// Request-Body zu XML konvertieren
$requestBody = file_get_contents('php://input');
$requestData = json_decode($requestBody, true);
$requestData = array('adresse' => $requestData);
$requestData['adresse']['id'] = (int)$id; // ID hinzufügen
unset($requestData['adresse']['kundennummer']); // Kundennummer löschen, sonst wird evtl. die falsche Adresse aktualisiert // @todo Kundennummer änderbar machen
$requestXml = $this->converter->arrayToXml($requestData);
// Adresse ändern über alte API
$this->legacyApi->app->Secure->GET['action'] = 'AdresseEdit';
$this->legacyApi->app->Secure->GET['json'] = true;
$this->legacyApi->app->Secure->POST['xml'] = $requestXml;
$customerId = (int)$this->legacyApi->ApiAdresseEdit(true);
if ($customerId <= 0) {
// @todo Nicht sehr hilfreiche Meldung
// @todo Refaktorieren und besser Exception werfen
throw new BadRequestException('Adresse konnte nicht bearbeitet werden.');
}
// Bearbeiten war erfolgreich > Bearbeitete Resource zurückliefern
$data = $this->getAddressById($customerId);
$result = new ItemResult($data);
return $this->sendResult($result);
}
/**
* Einzelne Adresse per ID abrufen
*
* @param int $id
*
* @return array
*
* @throws \RuntimeException
* @throws ResourceNotFoundException
*/
protected function getAddressById($id)
{
if (intval($id) <= 0) {
throw new InvalidArgumentException('Benötigter Parameter \'id\' ungültig.');
}
/** @var SimpleXMLElement $xml */
$xml = $this->legacyApi->ApiAdresseGet(true, $id);
if (empty($xml)) {
throw new ResourceNotFoundException(sprintf('Adresse mit ID \'%s\' nicht gefunden', $id));
}
$data = $this->converter->xmlToArray($xml, true);
// Ergebnis aus alter API umstrukturieren
return $data;
}
/**
* Einzene Adresse per Kundennummer abrufen
*
* @param string $kundennummer
*
* @return array
*
* @throws \RuntimeException
* @throws ResourceNotFoundException
*/
protected function getAddressByCustomerNumber($kundennummer)
{
if (empty($kundennummer)) {
throw new InvalidArgumentException('Benötigter Parameter \'kundennummer\' ist leer.');
}
/** @var SimpleXMLElement $xml */
$this->legacyApi->app->Secure->GET['kundennummer'] = $kundennummer;
$xml = $this->legacyApi->ApiAdresseGet(true, '');
if (empty($xml)) {
throw new ResourceNotFoundException(sprintf('Adresse mit Kundennummer \'%s\' nicht gefunden', $kundennummer));
}
$data = $this->converter->xmlToArray($xml, true);
// Ergebnis aus alter API umstrukturieren
return ['data' => $data];
}
}
@@ -0,0 +1,151 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\ValidationErrorException;
/**
* Controller zum Anlegen und Bearbeiten von Abo-Artikeln
*
* Die Auflistung der Aboartikel-Ressource wird über den GenericController behandelt.
*/
class ArticleSubscriptionController extends AbstractController
{
/**
* Abo-Artikel anlegen
*
* @return Response
*/
public function createAction()
{
$input = $this->getRequestData();
$errors = [];
// Pflichtparameter prüfen
if (empty($input['bezeichnung'])) {
$errors[] = 'Required field "bezeichnung" is empty.';
}
if (empty($input['artikelnummer']) && empty($input['artikel'])) {
$errors[] = 'Required fields "artikelnummer" and "artikel" are empty. One of them must be filled.';
}
// Artikelnummer in ID wandeln
if (!empty($input['artikelnummer'])) {
$input['artikel'] = (int)$this->db->fetchValue(
'SELECT a.id FROM artikel AS a WHERE a.nummer = :artikelnummer',
['artikelnummer' => $input['artikelnummer']]
);
// Artikelnummer existiert nicht
if ($input['artikel'] === 0) {
$errors[] = 'Artikel not found with article number: ' . $input['artikelnummer'];
}
unset($input['artikelnummer']);
}
// Kundennummer in Adressen-ID wandeln
if (!empty($input['kundennummer'])) {
$input['adresse'] = (int)$this->db->fetchValue(
'SELECT a.id FROM adresse AS a WHERE a.kundennummer = :kundennummer',
['kundennummer' => $input['kundennummer']]
);
// Kundennummer existiert nicht
if ($input['adresse'] === 0) {
$errors[] = 'Address not found with customer number: ' . $input['kundennummer'];
}
unset($input['kundennummer']);
}
// Nach Pflichtfeld-Prüfung vorab Fehler anzeigen
if (count($errors) > 0) {
throw new ValidationErrorException($errors);
}
// Default-Werte hinterlegen
if (!array_key_exists('startdatum', $input)) {
$input['startdatum'] = date('Y-m-d');
}
if (!array_key_exists('zahlzyklus', $input)) {
$input['zahlzyklus'] = 1;
}
if (!array_key_exists('dokumenttyp', $input)) {
$input['dokumenttyp'] = 'rechnung';
}
if (!array_key_exists('preisart', $input)) {
$input['preisart'] = 'monat';
}
if (!array_key_exists('menge', $input)) {
$input['menge'] = '0.00';
}
if (!array_key_exists('preis', $input)) {
$input['preis'] = '0.00';
}
if (!array_key_exists('rabatt', $input)) {
$input['rabatt'] = '0.00';
}
if (!array_key_exists('waehrung', $input)) {
$input['waehrung'] = 'EUR';
}
if (!array_key_exists('reihenfolge', $input)) {
$input['reihenfolge'] = 1;
}
// Aboartikel-Eintrag anlegen
$resource = $this->getResource($this->resourceClass);
$result = $resource->insert($input);
return $this->sendResult($result, Response::HTTP_CREATED);
}
/**
* Abo-Artikel bearbeiten
*
* @return Response
*/
public function updateAction()
{
$resource = $this->getResource($this->resourceClass);
$id = $this->getResourceId();
$resource->checkOrFail($id);
$errors = [];
$input = $this->getRequestData();
// Artikelnummer in ID wandeln
if (!empty($input['artikelnummer'])) {
$input['artikel'] = (int)$this->db->fetchValue(
'SELECT a.id FROM artikel AS a WHERE a.nummer = :artikelnummer',
['artikelnummer' => $input['artikelnummer']]
);
// Artikelnummer existiert nicht
if ($input['artikel'] === 0) {
$errors[] = 'Artikel not found with article number: ' . $input['artikelnummer'];
}
unset($input['artikelnummer']);
}
// Kundennummer in Adressen-ID wandeln
if (!empty($input['kundennummer'])) {
$input['adresse'] = (int)$this->db->fetchValue(
'SELECT a.id FROM adresse AS a WHERE a.kundennummer = :kundennummer',
['kundennummer' => $input['kundennummer']]
);
// Kundennummer existiert nicht
if ($input['adresse'] === 0) {
$errors[] = 'Address not found with customer number: ' . $input['kundennummer'];
}
unset($input['kundennummer']);
}
// Nach Pflichtfeld-Prüfung vorab Fehler anzeigen
if (count($errors) > 0) {
throw new ValidationErrorException($errors);
}
if (empty($input)) {
throw new BadRequestException('Payload is empty.');
}
$result = $resource->edit($id, $input);
return $this->sendResult($result);
}
}
@@ -0,0 +1,357 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use DateTimeImmutable;
use Xentral\Components\Http\Response;
use Xentral\Components\Util\StringUtil;
use Xentral\Modules\Api\Engine\ApiUrlGenerator;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Exception\ServerErrorException;
use Xentral\Modules\Api\Resource\FileResource;
use Xentral\Modules\Api\Resource\Result\ItemResult;
class DocumentScannerController extends AbstractController
{
/**
* Resourcen-Liste abrufen
*
* @return Response
*/
public function listAction()
{
// Filter, Sortierung und Paginierung
$filter = $this->prepareFilterParams();
$sorting = $this->prepareSortingParams();
$includes = $this->prepareIncludeParams();
$currentPage = $this->getPaginationPage();
$itemsPerPage = $this->getPaginationCount();
// Liste laden
$resource = $this->getResource($this->resourceClass);
$result = $resource->getList($filter, $sorting, [], $includes, $currentPage, $itemsPerPage);
return $this->sendResult($result);
}
/**
* Einzelne Resource anhand ID laden
*
* @return Response
*/
public function readAction()
{
return $this->sendResult($this->readResult());
}
/**
* Datei anlegen/hochladen
*
* @throws BadRequestException Wenn Pflichtfelder leer, oder Content-Type falsch
* @throws ServerErrorException Wenn Datei aus unbekannten Gründen nicht angelegt werden konnte (sollte nicht auftreten)
*
* @return Response
*/
public function createAction()
{
$input = null;
$contentTypeRaw = $this->request->getHeader('Content-Type');
if (empty($contentTypeRaw)) {
$errorMsg = 'Content-Type header is empty. ';
$errorMsg .= 'Only "application/x-www-form-urlencoded" or "multipart/form-data" is supported.';
throw new BadRequestException(
'Unsupported Content-Type', ApiError::CODE_CONTENT_TYPE_NOT_SUPPORTED, null, [$errorMsg]
);
}
if (StringUtil::startsWith($contentTypeRaw, 'multipart/form-data')) {
$input = $this->getRequestDataFromMultipartForm();
}
if (StringUtil::startsWith($contentTypeRaw, 'application/x-www-form-urlencoded')) {
$input = $this->getRequestDataFromUrlEncodedForm();
}
if ($input === null) {
$errorMsg = sprintf('Content-Type "%s" is not supported. ', $contentTypeRaw);
$errorMsg .= 'Only "application/x-www-form-urlencoded" or "multipart/form-data" is supported.';
throw new BadRequestException(
'Unsupported Content-Type', ApiError::CODE_CONTENT_TYPE_NOT_SUPPORTED, null, [$errorMsg]
);
}
if (empty($input['dateiname'])) {
throw new BadRequestException('Required property "dateiname" is missing.');
}
if (empty($input['titel'])) {
throw new BadRequestException('Required property "titel" is missing.');
}
if (empty($input['file_content'])) {
throw new BadRequestException('Required property "file_content" is missing or file is empty.');
}
// Meta-Daten prüfen
if (!empty($input['meta'])) {
$this->checkMetaData($input['meta']);
$metaData = $input['meta'];
}
$fileName = $input['dateiname']; // Pflichtfeld
$fileTitle = $input['titel']; // Pflichtfeld
$fileDescription = $input['beschreibung'] ?? '';
$fileNumber = null;
$fileCreatorUserId = null;
$erp = $this->legacyApi->app->erp;
$fileId = (int)$erp->CreateDatei(
$fileName,
$fileTitle,
$fileDescription,
$fileNumber,
$input['file_content'],
$fileCreatorUserId
);
if ($fileId <= 0) {
throw new ServerErrorException('Failed to create file.');
}
// Datei in docscan-Tabelle verknüpfen und Datei-Stichwort hinzufügen
$this->db->perform(
'INSERT INTO `docscan` (`id`, `datei`, `kategorie`) VALUES (NULL, :file_id, NULL)',
['file_id' => $fileId]
);
$docscanId = $this->db->lastInsertId();
$erp->AddDateiStichwort($fileId, 'Sonstige', 'DocScan', $docscanId);
// Meta-Daten speichern
if (isset($metaData) && !empty($metaData)) {
$this->saveMetaData($docscanId, $metaData);
}
// Bei Erfolg die angelegte Resource zurückliefern; mit Success-Flag
/** @var FileResource $resource */
$result = $this->readResult($fileId);
$result->setSuccess(true);
return $this->sendResult($result, Response::HTTP_CREATED);
}
/**
* @throws ResourceNotFoundException
*
* @return void
*/
public function updateAction()
{
throw new ResourceNotFoundException();
}
/**
* @throws BadRequestException
*
* @return array
*/
protected function getRequestDataFromMultipartForm()
{
if ($this->request->getContentType() !== 'form-data') {
throw new BadRequestException(
'Unsupported Content-Type',
ApiError::CODE_CONTENT_TYPE_NOT_SUPPORTED,
null,
['Content-Type must be "multipart/form-data"']
);
}
$input = $this->request->post->all();
if (!isset($input['file_content']) && $this->request->files->has('file_content')) {
$upload = $this->request->files->get('file_content');
$input['file_content'] = $upload->getContent();
}
return $input;
}
/**
* @throws BadRequestException
*
* @return array
*/
protected function getRequestDataFromUrlEncodedForm()
{
if ($this->request->getContentType() !== 'x-www-form-urlencoded') {
throw new BadRequestException(
'Unsupported Content-Type',
ApiError::CODE_CONTENT_TYPE_NOT_SUPPORTED,
null,
['Content-Type must be "application/x-www-form-urlencoded"']
);
}
return $this->request->post->all();
}
/**
* @param array $data
*
* @throws BadRequestException
*
* @return void
*/
protected function checkMetaData($data)
{
if (!is_array($data)) {
throw new BadRequestException('Wrong value type in property "meta". Only type array is allowed.');
}
$allowedKeys = ['invoice_number', 'invoice_date', 'invoice_amount', 'invoice_tax', 'invoice_currency'];
foreach ($data as $key => $value) {
if (is_int($key)) {
throw new BadRequestException('Wrong format in property "meta". Numeric keys are not allowed.');
}
$cleanedKey = (string)preg_replace('#[^a-z0-9_]#', '', trim($key));
if ($key !== $cleanedKey) {
throw new BadRequestException(sprintf(
'Meta key "%s" contains an illegal character. Allowed characters: a-z, 0-9 and underscore.', $key
));
}
if (!in_array($key, $allowedKeys, true)) {
throw new BadRequestException(sprintf(
'Meta key "%s" is not allowed. Allowed keys: %s', $key, implode(', ', $allowedKeys)
));
}
if (mb_strlen($value) > 32) {
throw new BadRequestException(sprintf(
'Wrong value format in property "meta.%s". Max value length is 32 characters.',
$key
));
}
if ($key === 'invoice_number') {
if (!is_string($value)) {
throw new BadRequestException(
'Wrong value type in property "meta.invoice_number". Only type string is allowed.'
);
}
}
if ($key === 'invoice_date') {
$invoiceDate = DateTimeImmutable::createFromFormat('Y-m-d', $value);
if ($invoiceDate === false || array_sum($invoiceDate::getLastErrors()) > 0) {
throw new BadRequestException(
'Wrong value format or invalid date in property "meta.invoice_date". Allowed format: "YYYY-MM-DD"'
);
}
}
if ($key === 'invoice_amount') {
$cleanedInvoiceAmount = (string)preg_replace('#[^0-9.]#', '', $value);
if ($value !== $cleanedInvoiceAmount) {
throw new BadRequestException(
'Wrong value format in property "meta.invoice_amount". Value can only contain numbers and a period character.'
);
}
}
if ($key === 'invoice_tax') {
$cleanedInvoiceTax = (string)preg_replace('#[^0-9.]#', '', $value);
if ($value !== $cleanedInvoiceTax) {
throw new BadRequestException(
'Wrong value format in property "meta.invoice_tax". Value can only contain numbers and a period character.'
);
}
}
if ($key === 'invoice_currency') {
if (!is_string($value)) {
throw new BadRequestException(
'Wrong value type in property "meta.invoice_currency". Only type string is allowed.'
);
}
if (mb_strlen($value) !== 3) {
throw new BadRequestException(
'Wrong value format in property "meta.invoice_currency". Value must be three characters long.'
);
}
$cleanedCurrencyCode = (string)preg_replace('#[^A-Z]#', '', $value);
if ($value !== $cleanedCurrencyCode) {
throw new BadRequestException(
'Wrong value format in property "meta.invoice_currency". Value must contain three uppercase characters.'
);
}
}
}
}
/**
* @param int $docscanId
* @param array $metaData
*
* @return void
*/
protected function saveMetaData(int $docscanId, array $metaData)
{
if (empty($metaData)) {
return;
}
$this->db->beginTransaction();
foreach ($metaData as $metaKey => $metaValue) {
$this->db->perform(
'INSERT INTO `docscan_metadata` (`id`, `docscan_id`, `meta_key`, `meta_value`)
VALUES (NULL, :docscan_id, :meta_key, :meta_value)',
[
'docscan_id' => $docscanId,
'meta_key' => (string)$metaKey,
'meta_value' => (string)$metaValue,
]
);
}
$this->db->commit();
}
/**
* @param int|null $useFileId
*
* @throws ResourceNotFoundException
*
* @return ItemResult
*/
protected function readResult($useFileId = null)
{
$fileId = (int)$useFileId > 0 ? (int)$useFileId : $this->getResourceId();
$erp = $this->legacyApi->app->erp;
$filePath = $erp->GetDateiPfad($fileId);
if (!is_file($filePath)) {
throw new ResourceNotFoundException('File not found in filesystem.');
}
$fileMime = mime_content_type($filePath);
if ($fileMime === 'directory') {
throw new ResourceNotFoundException('File not found. File is a directory.');
}
$resource = $this->getResource($this->resourceClass);
$includes = ['metadata'];//$this->prepareIncludeParams();
$result = $resource->getOne($fileId, $includes);
$downloadBaseUrl = $this->buildDownloadBaseUrl($fileId);
// Daten anreichern um Download-Links
$data = $result->getData();
$data['mimetype'] = $fileMime;
$data['links'] = [
'download' => $downloadBaseUrl . '/download',
'base64' => $downloadBaseUrl . '/base64',
];
return new ItemResult($data);
}
/**
* @param int $fileId
*
* @return string
*/
protected function buildDownloadBaseUrl($fileId)
{
$urlGenerator = new ApiUrlGenerator($this->request);
return $urlGenerator->generate('/v1/dateien/' . (int)$fileId);
}
}
@@ -0,0 +1,231 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Exception\ServerErrorException;
use Xentral\Modules\Api\Resource\FileResource;
use Xentral\Modules\Api\Resource\Result\ItemResult;
class FileController extends AbstractController
{
/**
* Resourcen-Liste abrufen
*
* @return Response
*/
public function listAction()
{
// Filter, Sortierung und Paginierung
$filter = $this->prepareFilterParams();
$sorting = $this->prepareSortingParams();
$includes = $this->prepareIncludeParams();
$currentPage = $this->getPaginationPage();
$itemsPerPage = $this->getPaginationCount();
// Liste laden
$resource = $this->getResource($this->resourceClass);
$result = $resource->getList($filter, $sorting, [], $includes, $currentPage, $itemsPerPage);
return $this->sendResult($result);
}
/**
* Einzelne Resource anhand ID laden
*
* @return Response
*/
public function readAction()
{
return $this->sendResult($this->readResult());
}
/**
* Datei als Download senden
*
* @return Response
*/
public function downloadAction()
{
$fileId = $this->getResourceId();
$erp = $this->legacyApi->app->erp;
$filePath = $erp->GetDateiPfad($fileId);
$fileName = $erp->GetDateiName($fileId);
if (!is_file($filePath)) {
throw new ResourceNotFoundException('File not found in filesystem.');
}
$fileMime = mime_content_type($filePath);
if ($fileMime === 'directory') {
throw new ResourceNotFoundException('File not found. File is a directory.');
}
$header = [
'Content-Type' => $fileMime,
'Content-Disposition' => sprintf('attachment; filename="%s"', $fileName),
'Content-Length' => (string)filesize($filePath),
];
return new Response(file_get_contents($filePath), 200, $header);
}
/**
* Datei base64-kodiert senden
*
* @return Response
*/
public function base64Action()
{
$fileId = $this->getResourceId();
$erp = $this->legacyApi->app->erp;
$filePath = $erp->GetDateiPfad($fileId);
if (!is_file($filePath)) {
throw new ResourceNotFoundException('File not found in filesystem.');
}
$fileMime = mime_content_type($filePath);
if ($fileMime === 'directory') {
throw new ResourceNotFoundException('File not found. File is a directory.');
}
$prefix = 'data:' . $fileMime . ';base64,';
$header = [
'Content-Type' => 'text/plain',
'Content-Disposition' => 'inline',
];
return new Response($prefix . base64_encode(file_get_contents($filePath)), 200, $header);
}
/**
* Datei anlegen/hochladen
*
* @throws BadRequestException Wenn Pflichtfelder leer, oder Content-Type falsch
* @throws ServerErrorException Wenn Datei aus unbekannten Gründen nicht angelegt werden konnte (sollte nicht auftreten)
*
* @return Response
*/
public function createAction()
{
$input = $this->getRequestDataFromUrlEncodedForm();
if (empty($input['dateiname'])) {
throw new BadRequestException('Required property "dateiname" is missing.');
}
if (empty($input['titel'])) {
throw new BadRequestException('Required property "titel" is missing.');
}
if (empty($input['file_content'])) {
throw new BadRequestException('Required property "file_content" is missing.');
}
$fileName = $input['dateiname']; // Pflichtfeld
$fileTitle = $input['titel']; // Pflichtfeld
$fileDescription = $input['beschreibung'] ?? '';
$fileNumber = null;
$fileCreatorUserId = null;
$erp = $this->legacyApi->app->erp;
$fileId = (int)$erp->CreateDatei(
$fileName,
$fileTitle,
$fileDescription,
$fileNumber,
$input['file_content'],
$fileCreatorUserId
);
if ($fileId <= 0) {
throw new ServerErrorException('Failed to create file.');
}
// if (!empty($input['belegtyp'])) {
// $erp->AddDateiStichwort($fileId, 'Belege', $input['belegtyp'], $belegId); // @todo $belegId
// }
// Bei Erfolg die angelegte Resource zurückliefern; mit Success-Flag
/** @var FileResource $resource */
$result = $this->readResult($fileId);
$result->setSuccess(true);
return $this->sendResult($result, Response::HTTP_CREATED);
}
/**
* @throws ResourceNotFoundException
*
* @return void
*/
public function updateAction()
{
throw new ResourceNotFoundException();
}
/**
* @throws BadRequestException
*
* @return array
*/
protected function getRequestDataFromUrlEncodedForm()
{
$request = $this->request;
if ($request->getContentType() !== 'x-www-form-urlencoded') {
throw new BadRequestException(
'Unsupported Content-Type',
ApiError::CODE_CONTENT_TYPE_NOT_SUPPORTED,
null,
['Content-Type must be "application/x-www-form-urlencoded"']
);
}
return $request->post->all();
}
/**
* @param int|null $useFileId
*
* @throws ResourceNotFoundException
*
* @return ItemResult
*/
protected function readResult($useFileId = null)
{
$fileId = (int)$useFileId > 0 ? (int)$useFileId : $this->getResourceId();
$erp = $this->legacyApi->app->erp;
$filePath = $erp->GetDateiPfad($fileId);
if (!is_file($filePath)) {
throw new ResourceNotFoundException('File not found in filesystem.');
}
$fileMime = mime_content_type($filePath);
if ($fileMime === 'directory') {
throw new ResourceNotFoundException('File not found. File is a directory.');
}
$resource = $this->getResource($this->resourceClass);
$includes = $this->prepareIncludeParams();
$result = $resource->getOne($fileId, $includes);
$fullUri = $this->request->getFullUrl();
// URI um File-ID erweitern, wenn der Request ohne ID war (beim Anlegen)
if ((int)$useFileId > 0) {
$fullUri .= '/' . $useFileId;
}
// Daten anreichern um Download-Links
$data = $result->getData();
$data['mimetype'] = $fileMime;
$data['links'] = [
'download' => $fullUri . '/download',
'base64' => $fullUri . '/base64',
];
return new ItemResult($data);
}
}
@@ -0,0 +1,95 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Http\Response;
class GenericController extends AbstractController
{
/**
* Resourcen-Liste abrufen
*
* @return Response
*/
public function listAction()
{
// Filter, Sortierung und Paginierung
$filter = $this->prepareFilterParams();
$sorting = $this->prepareSortingParams();
$includes = $this->prepareIncludeParams();
$currentPage = $this->getPaginationPage();
$itemsPerPage = $this->getPaginationCount();
// Liste laden
$resource = $this->getResource($this->resourceClass);
$result = $resource->getList($filter, $sorting, [], $includes, $currentPage, $itemsPerPage);
return $this->sendResult($result);
}
/**
* Einzelne Resource anhand ID laden
*
* @return Response
*/
public function readAction()
{
$resource = $this->getResource($this->resourceClass);
$includes = $this->prepareIncludeParams();
$id = $this->getResourceId();
$result = $resource->getOne($id, $includes);
return $this->sendResult($result);
}
/**
* Resource anlegen
*
* @return Response
*/
public function createAction()
{
$resource = $this->getResource($this->resourceClass);
$input = $this->getRequestData();
$result = $resource->insert($input);
return $this->sendResult($result, Response::HTTP_CREATED);
}
/**
* Resource ändern
*
* @return Response
*/
public function updateAction()
{
$resource = $this->getResource($this->resourceClass);
$id = $this->getResourceId();
$resource->checkOrFail($id);
$input = $this->getRequestData();
$result = $resource->edit($id, $input);
return $this->sendResult($result);
}
/**
* Resource löschen
*
* @return Response
*/
public function deleteAction()
{
$resource = $this->getResource($this->resourceClass);
$id = $this->getResourceId();
$resource->checkOrFail($id);
$result = $resource->delete($id);
return $this->sendResult($result);
}
}
@@ -0,0 +1,118 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Exception;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Exception\ServerErrorException;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
use Xentral\Modules\Report\ReportCsvExportService;
use Xentral\Modules\Report\ReportGateway;
use Xentral\Modules\Report\ReportPdfExportService;
class ReportsController
{
/** @var LegacyApplication $api*/
private $app;
/** @var Request $request */
private $request;
/** @var int $apiAccountId */
private $apiAccountId;
/**
* @param LegacyApplication $app
* @param Request $request
* @param int $apiAccountId
*/
public function __construct(LegacyApplication $app, Request $request, $apiAccountId)
{
$this->app = $app;
$this->request = $request;
$this->apiAccountId = $apiAccountId;
}
/**
* Datei als Download senden
*
* @return Response
*/
public function downloadAction()
{
$reportId = $this->request->attributes->getInt('id');
$parameters = $this->request->get->all();
/** @var ReportGateway $gateway */
$gateway = $this->app->Container->get('ReportGateway');
$reportObject = $gateway->getReportById($reportId);
if ($reportObject === null) {
throw new ResourceNotFoundException('Resource not found');
}
/** @var ReportGateway $gateway */
$gateway = $this->app->Container->get('ReportGateway');
$transferOptions = $gateway->findTransferArrayByReportId($reportId);
if (
empty($transferOptions)
|| !isset(
$transferOptions['api_active'],
$transferOptions['api_account_id'],
$transferOptions['api_format']
)
|| $transferOptions['api_active'] === 0
|| $transferOptions['api_account_id'] !== $this->apiAccountId
) {
return new Response(
json_encode(
['error' => ['http_code' => 403, 'message' => 'Access denied']]
, JSON_PRETTY_PRINT
),
Response::HTTP_FORBIDDEN
);
}
$clientFileName = '';
$filePath = '';
try {
switch ($transferOptions['api_format']) {
case 'csv':
/** @var ReportCsvExportService $csvExporter */
$csvExporter = $this->app->Container->get('ReportCsvExportService');
$clientFileName = $csvExporter->generateFileName($reportObject);
$filePath = $csvExporter->createCsvFileFromReport($reportObject, $parameters);
break;
case 'pdf':
/** @var ReportPdfExportService $pdfExporter */
$pdfExporter = $this->app->Container->get('ReportPdfExportService');
$clientFileName = $pdfExporter->generateFileName($reportObject);
$filePath = $pdfExporter->createPdfFileFromReport($reportObject, $parameters);
break;
default:
}
} catch (Exception $e) {
throw new ServerErrorException();
}
if (!is_file($filePath)) {
throw new ServerErrorException();
}
$fileMime = mime_content_type($filePath);
$header = [
'Content-Type' => $fileMime,
'Content-Disposition' => sprintf('attachment; filename="%s"', $clientFileName),
'Content-Length' => (string)filesize($filePath),
];
$response = new Response(file_get_contents($filePath), 200, $header);
unlink($filePath);
return $response;
}
}
@@ -0,0 +1,119 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Exception\RouteNotFoundException;
use Xentral\Modules\Api\Exception\ServerErrorException;
use Xentral\Modules\Api\Exception\WebserverMisconfigurationException;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Http\PathInfoDetector;
use Xentral\Modules\Api\Resource\Result\ItemResult;
class StartController extends AbstractController
{
/**
* @throws HttpException
*
* @return Response
*/
public function indexAction()
{
if (!$this->request->isFailsafeUri()) {
/*
* Erkennung von fehlerhafter Server-Konfiguration
*
* Problem:
* Nginx übermittelt in der Standard-Konfiguration nicht den PathInfo an PHP.
* Wenn PathInfo nicht gesetzt ist, landet man immer in diesem Controller und es sieht so aus
* als würde die API grundsätzlich funktionieren, obwohl man im falschen Endpunkt rauskommt.
*
* Lösung:
* Nachfolgend wird versucht den PathInfo-Teil aus anderen Server-Variablen zu ermitteln.
* Bei Unterschieden zwischen dem ermittelten und dem gesetzten PathInfo wird eine Exception geworfen.
*/
$pathInfoDetector = new PathInfoDetector($this->request);
$pathInfoExpected = $pathInfoDetector->detect();
$pathInfoActual = (string)$this->request->server->get('PATH_INFO');
if ($pathInfoActual !== $pathInfoExpected) {
throw new WebserverMisconfigurationException(
'Webserver configuration incorrect. Pathinfo is invalid.',
ApiError::CODE_WEBSERVER_PATHINFO_INVALID
);
}
}
return $this->sendResult(new ItemResult(['info' => 'Nothing here']));
}
/**
* Action zum Ausliefern der /api/docs.html
*
* Action greift nur wenn der Webserver falsch konfiguriert ist. Der Webserver müsste existierende
* Dateien direkt ausliefern ohne Umweg über den API-Frontcontroller.
*
* @throws ServerErrorException
*
* @return Response
*/
public function docsAction()
{
$docsHtmlFilePath = $this->getApiRootPath() . DIRECTORY_SEPARATOR . 'docs.html';
if (!is_file($docsHtmlFilePath)) {
throw new ServerErrorException(sprintf('File not found: %s', $docsHtmlFilePath));
}
return new Response(file_get_contents($docsHtmlFilePath), Response::HTTP_OK, ['Content-Type' => 'text/html']);
}
/**
* Action zum Ausliefern von Assets (CSS und JS) der /api/docs.html
*
* @throws RouteNotFoundException
* @throws ResourceNotFoundException
* @throws ServerErrorException
*
* @return Response
*/
public function docsAssetsAction()
{
$assetFileName = $this->request->attributes->get('assetfile');
if (empty($assetFileName)) {
throw new RouteNotFoundException('Empty asset file name');
}
$mapping = [
'docs.css' => 'text/css',
'docs_custom.css' => 'text/css',
'docs.js' => 'application/json',
'0.docs.js' => 'application/json',
];
if (!array_key_exists($assetFileName, $mapping)) {
throw new ResourceNotFoundException(sprintf('Asset file "%s" not found.', $assetFileName));
}
$apiRootDir = $this->getApiRootPath() . DIRECTORY_SEPARATOR;
$assetFilePath = $apiRootDir . 'assets' . DIRECTORY_SEPARATOR . $assetFileName;
$contentType = $mapping[$assetFileName];
if (!is_file($assetFilePath)) {
throw new ServerErrorException(sprintf('File not found: %s', $assetFilePath));
}
return new Response(file_get_contents($assetFilePath), Response::HTTP_OK, ['Content-Type' => $contentType]);
}
/**
* @return string Absoute Path without trailing slash
*/
private function getApiRootPath()
{
return dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'www' . DIRECTORY_SEPARATOR . 'api';
}
}
@@ -0,0 +1,343 @@
<?php
namespace Xentral\Modules\Api\Controller\Version1;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\ValidationErrorException;
/**
* Controller zum Anlegen und Bearbeiten von Trackingnummern
*
* Die Auflistung der Trackingnummer-Ressource wird über den GenericController behandelt.
*/
class TrackingNumberController extends AbstractController
{
/**
* Trackingsnummer anlegen
*
* @return Response
*/
public function createAction()
{
$input = $this->getRequestData();
$errors = [];
// Pflichtfelder prüfen
if (empty($input['tracking'])) {
$errors[] = 'Required field "tracking" is empty.';
}
if (empty($input['internet']) && empty($input['auftrag']) && empty($input['lieferschein'])) {
$errors[] =
'Required fields "internet", "auftrag" and "lieferschein" are empty. ' .
'One of them has to be filled.';
}
if (empty($input['gewicht'])) {
$errors[] = 'Required field "gewicht" is empty.';
}
if (empty($input['anzahlpakete'])) {
$errors[] = 'Required field "anzahlpakete" is empty.';
}
if (empty($input['versendet_am'])) {
$errors[] = 'Required field "versendet_am" is empty.';
}
// Nach Pflichtfeld-Prüfung vorab Fehler anzeigen
if (count($errors) > 0) {
throw new ValidationErrorException($errors);
}
// Format der Pflichtfelder prüfen
$input['versendet_am'] = $this->ensureShippingDateFormat($input['versendet_am']);
$input['anzahlpakete'] = $this->ensureParcelCountFormat($input['anzahlpakete']);
// Prüfen ob Auftragsdaten gültig
$orderData = $this->ensureOrderData($input['lieferschein'], $input['auftrag'], $input['internet']);
// Trackingnummer-Eintrag anlegen
$resource = $this->getResource($this->resourceClass);
$bindValues = [
'adresse' => $orderData['adresseid'],
'lieferschein' => $orderData['lieferscheinid'],
'projekt' => $orderData['projektid'],
'firma' => $orderData['firmenid'],
'gewicht' => $input['gewicht'],
'anzahlpakete' => $input['anzahlpakete'],
'versendet_am' => $input['versendet_am'],
'tracking' => $input['tracking'],
'abgeschlossen' => 1,
];
$result = $resource->insert($bindValues);
return $this->sendResult($result, Response::HTTP_CREATED);
}
/**
* Trackingnummer bearbeiten
*
* @return Response
*/
public function updateAction()
{
$resource = $this->getResource($this->resourceClass);
$id = $this->getResourceId();
$resource->checkOrFail($id);
$input = $this->getRequestData();
$updateData = [];
// Format prüfen
if (isset($input['versendet_am'])) {
$updateData['versendet_am'] = $this->ensureShippingDateFormat($input['versendet_am']);
}
if (isset($input['anzahlpakete'])) {
$updateData['anzahlpakete'] = $this->ensureParcelCountFormat($input['anzahlpakete']);
}
if (isset($input['gewicht'])) {
$updateData['gewicht'] = (string)$input['gewicht'];
}
if (isset($input['tracking'])) {
$updateData['tracking'] = (string)$input['tracking'];
}
// Prüfen ob Auftragsdaten gültig
if (isset($input['lieferschein']) || isset($input['auftrag']) || isset($input['internet'])) {
$orderData = $this->ensureOrderData($input['lieferschein'], $input['auftrag'], $input['internet']);
$updateData['adresse'] = $orderData['adresseid'];
$updateData['lieferschein'] = $orderData['lieferscheinid'];
$updateData['projekt'] = $orderData['projektid'];
$updateData['firma'] = $orderData['firmenid'];
}
if (empty($updateData)) {
throw new BadRequestException('Payload is empty.');
}
$result = $resource->edit($id, $updateData);
return $this->sendResult($result);
}
/**
* Prüft ob Auftragsdaten gültig und gibt diese zurück
*
* @param string|null $deliveryNoteNumber Lieferscheinnummer
* @param string|null $orderNumber Auftragsnummer
* @param string|null $internetNumber Internetnummer aus Auftrag
*
* @throws ValidationErrorException
*
* @return array
*/
protected function ensureOrderData($deliveryNoteNumber = null, $orderNumber = null, $internetNumber = null)
{
$orderData = [];
if (!empty($deliveryNoteNumber)) {
$orderData = $this->ensureOrderDataByDeliveryNoteNumber($deliveryNoteNumber);
}
if (!empty($orderNumber)) {
$orderData = $this->ensureOrderDataByOrderNumber($orderNumber);
}
if (!empty($internetNumber)) {
$orderData = $this->ensureOrderDataByInternetNumber($internetNumber);
}
if (count($orderData) === 0) {
throw new ValidationErrorException(['Could not find order data.']);
}
return $orderData;
}
/**
* Auftrag anhand der Internetnummer (im Auftrag) finden
*
* @param string $internetNumber
*
* @throws ValidationErrorException
*
* @return array
*/
protected function ensureOrderDataByInternetNumber($internetNumber)
{
$order = $this->db->fetchAll(
'SELECT
au.id AS auftragsid,
au.projekt AS projektid,
au.adresse AS adresseid,
au.belegnr AS auftragsnummer,
au.internet AS internetnummer,
au.firma AS firmenid
FROM auftrag AS au
WHERE au.internet = :internetnummer',
['internetnummer' => $internetNumber]
);
if (count($order) === 0) {
throw new ValidationErrorException([
sprintf('Order not found with internet number "%s".', $internetNumber),
]);
}
if (count($order) > 1) {
throw new ValidationErrorException([
sprintf('Logic error: Found more than one order with internet number "%s".', $internetNumber),
]);
}
$orderData = $order[0];
$deliveryNotes = $this->db->fetchAll(
'SELECT l.id AS lieferscheinid , l.belegnr AS lieferscheinnummer
FROM lieferschein AS l
WHERE l.auftragid = :order_id',
['order_id' => $orderData['auftragsid']]
);
if (count($deliveryNotes) === 0) {
throw new ValidationErrorException([
sprintf('Delivery note not found for internet number "%s".', $internetNumber),
]);
}
if (count($deliveryNotes) > 1) {
throw new ValidationErrorException([
sprintf('Logic error: Found more than one delivery note for internet number "%s".', $internetNumber),
]);
}
$orderData['lieferscheinid'] = $deliveryNotes[0]['lieferscheinid'];
$orderData['lieferscheinnummer'] = $deliveryNotes[0]['lieferscheinnummer'];
return $orderData;
}
/**
* Auftrag anhand der Auftragsnummer finden
*
* @param string $orderNumber
*
* @throws ValidationErrorException
*
* @return array
*/
protected function ensureOrderDataByOrderNumber($orderNumber)
{
$order = $this->db->fetchAll(
'SELECT
au.id AS auftragsid,
au.projekt AS projektid,
au.adresse AS adresseid,
au.belegnr AS auftragsnummer,
au.internet AS internetnummer,
au.firma AS firmenid
FROM auftrag AS au
WHERE au.belegnr = :auftragsnummer',
['auftragsnummer' => $orderNumber]
);
if (count($order) === 0) {
throw new ValidationErrorException([
sprintf('Order not found with order number "%s".', $orderNumber),
]);
}
if (count($order) > 1) {
throw new ValidationErrorException([
sprintf('Logic error: Found more than one order with order number "%s".', $orderNumber),
]);
}
$orderData = $order[0];
$deliveryNotes = $this->db->fetchAll(
'SELECT l.id AS lieferscheinid , l.belegnr AS lieferscheinnummer
FROM lieferschein AS l
WHERE l.auftragid = :order_id',
['order_id' => $orderData['auftragsid']]
);
if (count($deliveryNotes) === 0) {
throw new ValidationErrorException([
sprintf('Delivery note not found for order number "%s".', $orderNumber),
]);
}
if (count($deliveryNotes) > 1) {
throw new ValidationErrorException([
sprintf('Logic error: Found more than one delivery note for order number "%s".', $orderNumber),
]);
}
$orderData['lieferscheinid'] = $deliveryNotes[0]['lieferscheinid'];
$orderData['lieferscheinnummer'] = $deliveryNotes[0]['lieferscheinnummer'];
return $orderData;
}
/**
* Auftrag anhand der Lieferscheinnummer finden
*
* @param string $deliveryNoteNumber
*
* @throws ValidationErrorException
*
* @return array
*/
protected function ensureOrderDataByDeliveryNoteNumber($deliveryNoteNumber)
{
$order = $this->db->fetchAll(
'SELECT
au.id AS auftragsid,
au.projekt AS projektid,
au.adresse AS adresseid,
au.belegnr AS auftragsnummer,
au.internet AS internetnummer,
l.belegnr AS lieferscheinnummer,
l.id AS lieferscheinid,
au.firma AS firmenid
FROM lieferschein AS l
INNER JOIN auftrag AS au ON l.auftragid = au.id
WHERE l.belegnr = :lieferschein',
['lieferschein' => $deliveryNoteNumber]
);
if (count($order) === 0) {
throw new ValidationErrorException([
sprintf('Order not found with delivery note number "%s".', $deliveryNoteNumber),
]);
}
if (count($order) > 1) {
throw new ValidationErrorException([
sprintf('Logic error: Found more than one order with delivery note number "%s".', $deliveryNoteNumber),
]);
}
return $order[0];
}
/**
* @param string $shippingDate
*
* @throws ValidationErrorException
*
* @return string
*/
protected function ensureShippingDateFormat($shippingDate)
{
if (!preg_match('#^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$#', $shippingDate)) {
throw new ValidationErrorException(['Field "versendet_am" does not match required format: YYYY-MM-DD']);
}
return $shippingDate;
}
/**
* @param string $parcelCount
*
* @throws ValidationErrorException
*
* @return int
*/
protected function ensureParcelCountFormat($parcelCount)
{
if (!preg_match('#^[0-9]+$#', $parcelCount)) {
throw new ValidationErrorException(['Field "anzahlpakete" does not match required format: [0-9]']);
}
return (int)$parcelCount;
}
}