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
+374
View File
@@ -0,0 +1,374 @@
<?php
namespace Xentral\Modules\Api\Auth;
use Xentral\Components\Database\Database;
use Xentral\Components\Http\Request;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\AuthorizationErrorException;
class DigestAuth
{
/** @var Database $db */
protected $db;
/** @var Request $request */
protected $request;
/** @var bool $isAuthenticated Authentifizierung erfolgreich? */
protected $isAuthenticated = false;
/** @var bool $checkNonceCount Soll der NonceCount geprüft werden? */
protected $checkNonceCount = false;
/** @var int $nonceMaxAge Maximales Alter in Sekunden (86400 = 24 Stunden) */
protected $nonceMaxAge = 86400;
/** @var string $realm */
protected $realm = 'Xentral-API';
/** @var string $nonce Server-Nonce */
protected $nonce;
/** @var string $opaque */
protected $opaque;
/** @var array $digestParts Header-Bestandteile für Digest-Authentifizierung */
protected $digestParts;
/** @var int|null $apiAccountId */
protected $apiAccountId;
/**
* @param Database $db
* @param Request $request
*/
public function __construct($db, $request)
{
$this->db = $db;
$this->request = $request;
// 30 Tage alte Serverkey löschen
if (mt_rand(0, 99) === 0) {
$this->db->exec('DELETE FROM `api_keys` WHERE zeitstempel < DATE_SUB(CURRENT_DATE, INTERVAL 30 DAY)');
}
}
/**
* @return void
*/
public function checkLogin()
{
$authHeader = $this->getAuthorizationRequestHeader();
if (!$authHeader) {
throw new AuthorizationErrorException(
'Unauthorized. You need to login.',
ApiError::CODE_UNAUTHORIZED
);
}
if (stripos($authHeader, 'digest ') !== 0) {
throw new AuthorizationErrorException(
'Authorization type not allowed.',
ApiError::CODE_AUTH_TYPE_NOT_ALLOWED
);
}
$digestHeader = $this->getDigestRequestHeader();
if (!$digestHeader) {
throw new AuthorizationErrorException(
'Unauthorized. You need to login.',
ApiError::CODE_UNAUTHORIZED
);
}
// Parameter für Authentifizierung extrahieren
$this->digestParts = $this->parseDigest($digestHeader);
// Benötigte Teile im Digest-Header fehlen
if ($this->digestParts === false) {
throw new AuthorizationErrorException(
'Authorization failure',
ApiError::CODE_DIGEST_HEADER_INCOMPLETE
);
}
// Benutzername wurde leer eingegeben
if (empty($this->digestParts['username'])) {
throw new AuthorizationErrorException(
'Authorization failure. Username is empty.',
ApiError::CODE_AUTH_USERNAME_EMPTY
);
}
// Alle aktiven API-Zugänge aus DB laden
$apiAccounts = $this->db->fetchAll(
'SELECT a.remotedomain as appname, a.initkey, a.id FROM api_account AS a WHERE a.aktiv = 1'
);
if (empty($apiAccounts)) {
throw new AuthorizationErrorException(
'Authorization failure. API Account not existing.',
ApiError::CODE_API_ACCOUNT_MISSING
);
}
foreach ($apiAccounts as $account) {
$validUser = $account['appname'];
$validPass = $account['initkey'];
// Username im Header stimmt nicht mit Account überein
if ($validUser !== $this->digestParts['username']) {
continue; // Nächsten Account probieren
}
// Digest-Algo validieren
if (!$this->validateDigestLogin($validUser, $validPass)) {
continue; // Mit nächsten Account weitermachen
// @todo API-Accounts mit gleichen Usernamen verhindern?
//throw new AuthorizationErrorException(
//'Validation failure. Digest not valid.',
// ApiError::CODE_DIGEST_VALIDDATION_FAILED
//);
}
// Key-Details aus DB laden
$keyDetails = $this->getKeyDetails($this->digestParts['nonce'], $this->digestParts['opaque']);
// Authentifizierung war gültig; Serverkeys sind aber abgelaufen, oder Client hat sich die Keys ausgedacht
if (!$keyDetails) {
$this->nonce = $this->opaque = null;
throw new AuthorizationErrorException(
'Authorization failure. Nonce is invalid or expired.',
ApiError::CODE_DIGEST_NONCE_INVALID
);
}
// Serverkeys sind abgelaufen (aber noch vorhanden in DB)
if ($keyDetails['age'] > $this->nonceMaxAge) {
$this->nonce = $this->opaque = null;
throw new AuthorizationErrorException(
'Authorization failure. Nonce is expired.',
ApiError::CODE_DIGEST_NONCE_EXPIRED
);
}
// NonceCount prüfen?
if ($this->checkNonceCount) {
// NonceCount zu Hexadezimal wandeln
$nonceCountHex = dechex($keyDetails['nonce_count_decimal']);
$this->digestParts['nc'] = ltrim($this->digestParts['nc'], '0');
// NonceCount stimmt nicht überein
if ($this->digestParts['nc'] !== $nonceCountHex) {
throw new AuthorizationErrorException(
'Authorization failure. Nonce count doesn\'t match.',
ApiError::CODE_DIGEST_NC_NOT_MATCHING
);
}
}
// NonceCount in DB hochzählen
$this->incrementNonceCount($this->digestParts['nonce']);
// Wenn bis hierhin kein Fehler passiert ist, passt alles.
// Serverkeys sind noch gültig
$this->isAuthenticated = true;
$this->apiAccountId = (int)$account['id'];
return;
}
// Alle Accounts durchprobiert > Kein Erfolg
throw new AuthorizationErrorException(
'Authorization failure. API Account invalid.',
ApiError::CODE_API_ACCOUNT_INVALID
);
}
/**
* @return bool
*/
public function isAuthenticated()
{
return $this->isAuthenticated;
}
/**
* @return int|null
*/
public function getApiAccountId()
{
return $this->apiAccountId;
}
/**
* Header-String generieren den der Client zum Authentifizieren benötigt
*
* @return string
*/
public function generateAuthenticationString()
{
// Neue Server-Key generieren
if (!$this->nonce && !$this->opaque) {
$this->createServerKeys();
}
return sprintf(
'Digest realm="%s",qop="auth",nonce="%s",opaque="%s"',
$this->realm, $this->nonce, $this->opaque
);
}
/**
* @param string $nonce
* @param string $opaque
*
* @return array|bool
*/
protected function getKeyDetails($nonce, $opaque)
{
if (empty($nonce) || empty($opaque)) {
return false;
}
$keyDetails = $this->db->fetchAll(
'SELECT k.nonce_count, k.zeitstempel FROM api_keys AS k '.
'WHERE k.nonce = :nonce AND k.opaque = :opaque',
array('nonce' => $nonce, 'opaque' => $opaque)
);
if (count($keyDetails) === 0) {
return false;
}
return array(
'nonce_count_decimal' => (int)$keyDetails[0]['nonce_count'],
'age' => time() - strtotime($keyDetails[0]['zeitstempel']),
);
}
/**
* @param string $username
* @param string $password
*
* @return bool Digest-Auth valide?
*/
protected function validateDigestLogin($username, $password)
{
// Based on all the info we gathered we can figure out what the response should be
$A1 = md5("{$username}:{$this->realm}:{$password}");
$A2 = md5("{$this->request->getMethod()}:".stripslashes($this->request->getRequestUri()));
// Im 'auth-int' Modus muss zusätzlich der Request-Body validiert werden
if ($this->digestParts['qop'] === 'auth-int') {
$A2 = md5("{$this->request->getMethod()}:".stripslashes($this->request->getRequestUri()).":{$this->request->getContent()}");
}
$validResponse = md5("{$A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}");
return ($this->digestParts['response'] === $validResponse);
}
/**
* @param string $nonce
*/
protected function incrementNonceCount($nonce)
{
$this->db->perform(
'UPDATE api_keys SET nonce_count = nonce_count + 1 WHERE nonce = :nonce',
array('nonce' => $nonce)
);
}
/**
* Neue Server-Keys (Nonce und Opaque) generieren und in DB ablegen
*/
protected function createServerKeys()
{
$this->nonce = md5(uniqid('', true));
$this->opaque = md5(uniqid('', true));
// Neue Keys in Datenbank speichern
$this->db->perform(
'INSERT INTO api_keys (id, nonce, opaque) VALUES (NULL, :nonce, :opaque)',
array('nonce' => $this->nonce, 'opaque' => $this->opaque)
);
}
/**
* This function returns the digest header
*
* @return string|false
*/
protected function getDigestRequestHeader()
{
$authHeader = $this->getAuthorizationRequestHeader();
if (stripos($authHeader, 'digest ') === 0) {
return substr_replace($authHeader, '', 0, 7);
}
return false;
}
/**
* Einzelnen Request-Header auslesen
*
* @param string $type z.B. "Authorization" oder "Content-Type"
*
* @return string|false
*/
protected function getRequestHeader($type)
{
if ($this->request->header->has($type)) {
return $this->request->header->get($type);
}
return false;
}
/**
* @return string|false
*/
protected function getAuthorizationRequestHeader()
{
return $this->getRequestHeader('Authorization');
}
/**
* Digest-Header in einzelne Bestandteile zerlegen, und prüfen ob alle benötigten Teile vorhanden sind.
*
* @param string $digest
*
* @return array|false Einzelne Bestandteile als Array, oder false wenn Teile fehlen
*/
protected function parseDigest($digest)
{
$neededParts = array(
'nonce' => false,
'opaque' => false,
'nc' => false,
'cnonce' => false,
'qop' => false,
'username' => false,
'uri' => false,
'response' => false,
);
$data = array();
// Beispiel: username="Test", realm="API", nonce="5b308bec108f0", uri="/api/addresses", qop=auth, nc=00000029, ...
$parts = explode(',', $digest);
foreach ($parts as $part) {
$atoms = explode('=', $part, 2);
if (count($atoms) !== 2) {
continue;
}
$key = trim($atoms[0], ' ');
$val = trim($atoms[1], '"');
$data[$key] = $val;
unset($neededParts[$key]);
}
return empty($neededParts) ? $data : false;
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Api\Auth;
use Xentral\Components\Database\Database;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Exception\AuthorizationErrorException;
class PermissionGuard
{
/** @var Database */
private $database;
/** @var int */
private $apiAccountId;
/**
* PermissionGuard constructor.
*
* @param Database $database
* @param int $apiAccountId
*/
public function __construct(Database $database, int $apiAccountId)
{
$this->database = $database;
$this->apiAccountId = $apiAccountId;
}
/**
* @param string $neededPermission
*/
public function check(string $neededPermission): void
{
$permissions = $this->getApiAccountPermissions();
$hasPermission = in_array($neededPermission, $permissions);
if (!$hasPermission) {
throw new AuthorizationErrorException(
'Api account has not needed permissions',
ApiError::CODE_API_ACCOUNT_PERMISSION_MISSING
);
}
}
/**
* @param string $action
*
* @return void
*/
public function checkStandardApiAction(string $action): void
{
$neededPermission = 'standard_' . strtolower($action);
$this->check($neededPermission);
}
/**
* @return array
*/
private function getApiAccountPermissions(): array
{
$jsonEncodedPermissions = $this->database->fetchValue(
'SELECT `permissions` FROM `api_account` WHERE `id` = :api_account_id',
['api_account_id' => $this->apiAccountId]
);
if( $jsonEncodedPermissions === null ) {
return [];
}
$permissions = json_decode($jsonEncodedPermissions, true);
return is_array($permissions)
? $permissions
: [];
}
}
@@ -0,0 +1,74 @@
<?php
namespace Xentral\Modules\Api\Controller\Legacy;
use Xentral\Components\Http\Request;
use Xentral\Modules\Api\Exception\BadRequestException;
class DefaultController
{
/** @var Request $request */
protected $request;
/** @var \Api $legacyApi */
protected $legacyApi;
/** @var int $apiId */
protected $apiId;
/**
* @param \Api $legacyApi
* @param Request $request
* @param int $apiId
*/
public function __construct($legacyApi, $request, $apiId)
{
$this->request = $request;
$this->legacyApi = $legacyApi;
$this->apiId = $apiId;
}
public function postAction()
{
$action = $this->request->attributes->get('action');
$contentType = $this->request->getContentType();
$content = $this->request->getContent();
if ($contentType === 'xml') {
$this->legacyApi->app->Secure->POST['xml'] = '<xml>' . $content . '</xml>';
}
if ($contentType === 'json') {
$requestData = json_decode($content, true);
$contentPrepared = isset($requestData['data']) ? json_encode($requestData['data']) : $content;
$this->legacyApi->app->Secure->GET['json'] = true;
$this->legacyApi->app->Secure->POST['json'] = $contentPrepared;
}
// API-Methode aufrufen
$this->legacyApi->setApiId($this->apiId);
$this->legacyApi->app->Secure->GET['action'] = $action;
$apiMethod = 'Api' . $action;
$actionMapping = [
'AccountCreate' => 'ApiAdresseAccountCreate',
'AccountEdit' => 'ApiAdresseAccountEdit',
];
if (isset($actionMapping[$action])) {
$apiMethod = $actionMapping[$action];
}
$this->legacyApi->$apiMethod();
// API-Methode liefert normalerweise selbst das Ergebnis aus und beendet die Script-Ausführung.
// Falls aber eine nicht existierende API-Methode aufgerufen wird, läuft das Script in die Exception.
throw new BadRequestException();
}
public function readAction()
{
$this->legacyApi->setApiId($this->apiId);
$action = $this->request->attributes->get('action');
}
}
@@ -0,0 +1,42 @@
<?php
namespace Xentral\Modules\Api\Controller\Legacy;
use Xentral\Components\Http\Request;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
class GobNavConnectController
{
/** @var Request $request */
protected $request;
/** @var LegacyApplication */
protected $app;
/**
* @param LegacyApplication $app
* @param Request $request
*/
public function __construct(LegacyApplication $app, Request $request)
{
$this->request = $request;
$this->app = $app;
}
public function exampleAction()
{
$post = $this->request->getContent();
$id = (int)$this->app->DB->Select(
"SELECT id FROM uebertragungen_account WHERE aktiv = 1 AND xml_pdf = 'TransferGobNav' LIMIT 1"
);
if ($id > 0) {
/** @var \Uebertragungen $transferObject */
$transferObject = $this->app->loadModule('uebertragungen');
if (!empty($transferObject)) {
/** @var \TransferGobNav $transferGobnav */
$transferGobnav = $transferObject->LoadTransferModul('TransferGobNav', $id);
$transferGobnav->ParseRequest($post);
}
}
exit;
}
}
@@ -0,0 +1,746 @@
<?php
namespace Xentral\Modules\Api\Controller\Legacy;
use DateInterval;
use DateTime;
use DateTimeInterface;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Components\Http\Request;
use Xentral\Modules\Api\Controller\Version1\AbstractController;
use Xentral\Modules\Api\Converter\Converter;
use Xentral\Modules\Api\Dashboard\WidgetData;
use Xentral\Modules\Api\Dashboard\WidgetResult;
use Xentral\Modules\Api\Exception\BadRequestException;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
class MobileApiController extends AbstractController
{
/** @var LegacyApplication $app */
private $app;
/**
* @param LegacyApplication $app
* @param Converter $converter
* @param Database $database
* @param Request $request
*/
public function __construct(LegacyApplication $app, Converter $converter, Database $database, Request $request)
{
parent::__construct(null, $database, $converter, $request, null);
$this->app = $app;
}
/**
* controller for dashboard api call
*
* uses optional GET request parameter 'date'
*
* @throws Exception
*/
public function dashboardAction()
{
$today = new DateTime('now');
$interval = (int)$this->request->get->get('interval');
$mode = $this->request->get->get('mode');
if (!in_array($mode, ['month', 'week', 'year'])) {
$mode = 'day';
}
if ($interval <= 0) {
switch ($mode) {
case 'year':
$interval = 10;
break;
case 'month':
$interval = 12;
break;
case 'week':
default:
$interval = 14;
break;
}
}
$requestDate = $this->request->get->get('date');
if ($requestDate !== null && !$this->isDate($requestDate)) {
throw new BadRequestException('Bad request: parameter \'date\' expected format YYYY-mm-dd');
}
if ($this->isDate($requestDate)) {
$today = new DateTime($requestDate);
}
$yesterday = new DateTime($today->format('Y-m-d'));
$yesterday = $yesterday->sub(new DateInterval('P1D'));
$lastYear = new DateTime($today->format('Y'));
$lastYear = $lastYear->sub(new DateInterval('P1Y'));
$result = new WidgetResult([]);
//Dashboard mainpage
$result->addData($this->getOrdersCountWidget($today, $yesterday));
$result->addData($this->getTurnoverWidget($today, $yesterday));
$result->addData($this->getDispatchWidget($today, $yesterday));
$result->addData($this->getOrderValueWidget($today, $yesterday));
$result->addData($this->getTwoWeeksTurnoverWidget($today, $interval, $mode));
$result->addData($this->getNewCustomerWidget($today, $yesterday));
$result->addData($this->getOpenTicketsWidget());
//financial data page
$result->addData(
new WidgetData(
'turnover_current',
WidgetData::WIDGET_TYPE_CONTRAST_BIG,
'Umsatz aktueller Monat (netto)',
['current' => $this->getTurnoverThisMonth(), 'previous' => $this->getTurnoverLastMonth()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'turnover_lastmonth',
WidgetData::WIDGET_TYPE_CONTRAST_BIG,
'Umsatz letzter Monat (netto)',
['current' => $this->getTurnoverLastMonth(), 'previous' => $this->getTurnoverBeforeLastMonth()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'turnover_beforelastmonth',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Umsatz vorletzter Monat (netto)',
['value' => $this->getTurnoverBeforeLastMonth()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'liability_open',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Offene Verbindlichkeiten (brutto)',
['value' => $this->getOpenLiabilies()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'orders_open',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Offene Aufträge (netto)',
['value' => $this->getOpenOrders()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'dunning_current',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Mahnwesen (brutto)',
['value' => $this->getDunning()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'timetrack_current',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Zeit Gebucht',
['value' => $this->getTimeTracking()],
'customer',
'',
WidgetData::FORMAT_HOURS
)
);
$result->addData(
new WidgetData(
'subscription_nextmonth',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Abolauf nächsten Monat (brutto)',
['value' => $this->getSubscriptionRun()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'accounts_total_current',
WidgetData::WIDGET_TYPE_SIMPLE_BIG,
'Bankkonten Gesamt',
['value' => $this->getAccountsTotal()],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
$result->addData(
new WidgetData(
'turnover_year_current',
WidgetData::WIDGET_TYPE_CONTRAST_BIG,
'Gesamtumsatz laufendes Jahr (netto)',
['current' => $this->getTurnoverByYear($today), 'previous' => $this->getTurnoverByYear($lastYear)],
'cashflow',
'€',
WidgetData::FORMAT_CURRENCY
)
);
return $this->sendResult($result);
}
/**
* returns chart data for number of orders
*
* @param DateTimeInterface $currentDay today
* @param DateTimeInterface $previousDay yesterday
*
* @return WidgetData chart data
*/
protected function getOrdersCountWidget(DateTimeInterface $currentDay, DateTimeInterface $previousDay)
{
$currentNumber = (int)$this->getOrdersCountByDay($currentDay);
$previousNumber = (int)$this->getOrdersCountByDay($previousDay);
$widget = new WidgetData(
'order_count',
WidgetData::WIDGET_TYPE_CONTRAST,
'Aufträge',
['current' => $currentNumber, 'previous' => $previousNumber],
'basket'
);
return $widget;
}
protected function getTurnoverWidget(DateTimeInterface $currentDay, DateTimeInterface $previousDay)
{
return new WidgetData(
'turnover_day',
WidgetData::WIDGET_TYPE_CONTRAST,
'Umsatz (heute)',
['current' => $this->getTurnoverByDay($currentDay), 'previous' => $this->getTurnoverByDay($previousDay)],
'euro',
'€',
WidgetData::FORMAT_CURRENCY
);
}
/**
* returns chart data for new customers
*
* @param DateTimeInterface $currentDay today
* @param DateTimeInterface $previousDay yesterday
*
* @return WidgetData chart data
*/
protected function getNewCustomerWidget(DateTimeInterface $currentDay, DateTimeInterface $previousDay)
{
$currentNumber = (int)$this->getNewCustomersByDay($currentDay);
$previousNumber = (int)$this->getNewCustomersByDay($previousDay);
$widget = new WidgetData(
'customer_new',
WidgetData::WIDGET_TYPE_CONTRAST,
'Neukunden',
['current' => $currentNumber, 'previous' => $previousNumber],
'customer'
);
return $widget;
}
protected function getOpenTicketsWidget()
{
return new WidgetData(
'tickets',
WidgetData::WIDGET_TYPE_SIMPLE,
'Offene Tickets',
['value' => $this->getOpenTicketCount()],
'ticket'
);
}
/**
* returns chart data for dispatched packages
*
* @param DateTimeInterface $currentDay today
* @param DateTimeInterface $previousDay yesterday
*
* @return WidgetData chart data
*/
protected function getDispatchWidget(DateTimeInterface $currentDay, DateTimeInterface $previousDay)
{
$currentNumber = (int)$this->getDispatchCountByDay($currentDay);
$previousNumber = (int)$this->getDispatchCountByDay($previousDay);
$widget = new WidgetData(
'dispatch_package',
WidgetData::WIDGET_TYPE_CONTRAST,
'Pakete',
['current' => $currentNumber, 'previous' => $previousNumber],
'packages'
);
return $widget;
}
protected function getOrderValueWidget(DateTimeInterface $today, DateTimeInterface $yesterday)
{
return new WidgetData(
'order_value',
WidgetData::WIDGET_TYPE_CONTRAST,
'Aufträge Heute',
['current' => $this->getOrderValueByDay($today), 'previous' => $this->getOrderValueByDay($yesterday)],
'euro',
'€',
WidgetData::FORMAT_CURRENCY
);
}
/**
* returns chart data for 14 days turnover
*
* @param DateTimeInterface $currentDay
* @param int $interval
* @param string $mode
*
* @throws Exception
* @return WidgetData chart data
*/
protected function getTwoWeeksTurnoverWidget(DateTimeInterface $currentDay, $interval = 0, $mode = 'day')
{
$dateString = $currentDay->format('Y-m-d');
switch ($mode) {
case 'year':
$modeName = 'Jahre';
if ($interval <= 0) {
$interval = 10;
}
$dayTo = new DateTime((new DateTime($dateString))->format('Y-12-31'));
$dayFrom = new DateTime((new DateTime($dateString))->format('Y-01-01'));
$dayFrom = $dayFrom->sub(new DateInterval(sprintf('P%dY', $interval - 1)));
break;
case 'month':
$modeName = 'Monate';
if ($interval <= 0) {
$interval = 12;
}
$dayTo = (new DateTime((new DateTime($dateString))
->format('Y-m-01')))
->add(new DateInterval('P1M'))
->sub(new DateInterval('P1D'));
$dayFrom = new DateTime((new DateTime($dateString))->format('Y-m-01'));
$dayFrom = $dayFrom->sub(new DateInterval(sprintf('P%dM', $interval - 1)));
break;
case 'week':
$modeName = 'Wochen';
if ($interval <= 0) {
$interval = 14;
}
$dayTo = new DateTime($dateString);
$weekDay = $dayTo->format('N');
if ($weekDay < 7) {
$dayTo->add(new DateInterval((sprintf('P%dD', 7 - $weekDay))));
}
$dayFrom = new DateTime($dayTo->format('Y-m-d'));
$dayFrom = $dayFrom->sub(new DateInterval(sprintf('P%dD', $interval * 7 - 1)));
break;
default:
$modeName = 'Tage';
if ($interval <= 0) {
$interval = 14;
}
$dayTo = new DateTime($dateString);
$dayFrom = new DateTime($dateString);
$dayFrom = $dayFrom->sub(new DateInterval(sprintf('P%dD', $interval - 1)));
break;
}
$data = $this->getTrunoverByDays($dayFrom, $dayTo, $mode);
$widget = new WidgetData(
'turnover_period',
WidgetData::WIDGET_TYPE_BARCHART,
sprintf('Umsatz (%d %s)', $interval, $modeName),
$data,
'euro',
'€',
WidgetData::FORMAT_CURRENCY
);
return $widget;
}
/**
* Returns number of orders created on specific date
*
* @param DateTimeInterface $date
*
* @return integer
*/
protected function getOrdersCountByDay(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y-m-d');
if (!$this->isDate($dateFormatted)) {
throw new InvalidArgumentException('Invalid date format.');
}
$sql = 'SELECT COUNT(a.id) AS anzahl FROM auftrag AS a WHERE a.datum = :dateFormatted';
$values = ['dateFormatted' => $dateFormatted];
$result = $this->db->fetchRow($sql, $values);
return (int)$result['anzahl'];
}
/**
* Returns total revenue of specific day
*
* @param DateTimeInterface $date
*
* @return double
*/
protected function getOrderValueByDay(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y-m-d');
$sql = "SELECT SUM(a.gesamtsumme) AS `ordervalue`
FROM auftrag AS a
WHERE a.datum = :dateFormatted AND a.status!='angelegt'";
$values = ['dateFormatted' => $dateFormatted];
$result = $this->db->fetchRow($sql, $values);
return (float)$result['ordervalue'];
}
/**
* Returns number of customers who placed their first order on specific date
*
* @param DateTimeInterface $date
*
* @return integer
*/
protected function getNewCustomersByDay(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y-m-d');
if (!$this->isDate($dateFormatted)) {
throw new InvalidArgumentException('Invalid date format.');
}
$sql = "SELECT Count(DISTINCT adr.name) AS neukunden
FROM adresse AS adr JOIN auftrag AS auf
ON adr.id = auf.adresse
WHERE adr.id NOT IN
(SELECT DISTINCT a.id
FROM adresse AS a RIGHT JOIN auftrag AS au
ON a.id = au.adresse
WHERE au.status<>'angelegt' AND au.datum <> :dateFormatted AND au.id IS NOT NULL
);";
$values = ['dateFormatted' => $dateFormatted];
$result = $this->db->fetchRow($sql, $values);
return (int)$result['neukunden'];
}
/**
* Returns number of packeges dispatched on specific date
*
* @param DateTimeInterface $date
*
* @return integer
*/
protected function getDispatchCountByDay(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y-m-d');
if (!$this->isDate($dateFormatted)) {
throw new InvalidArgumentException('Invalid date format.');
}
$sql = 'SELECT COUNT(v.id) AS anzahlpakete FROM versand AS v WHERE v.versendet_am = :dateFormatted';
$values = ['dateFormatted' => $dateFormatted];
$result = $this->db->fetchRow($sql, $values);
return (int)$result['anzahlpakete'];
}
protected function getOpenTicketCount()
{
return (float)$this->app->erp->AnzahlOffeneTickets(false);
}
/**
* Returns true if specific string represents a date.
*
* Accepted date format 'Y-m-d'
*
* @example isDate('2019-08-23') -> true
*
* @param string $dateString
*
* @return bool true=string represents a date
*/
protected function isDate($dateString)
{
$date = (string)$dateString;
if (preg_match('/^[1-9]\d{3}-\d{2}-\d{2}$/', $date)) {
return true;
}
return false;
}
/**
* @return float
*/
protected function getCashValues($key)
{
$obj = $this->app->loadModule('managementboard');
if (empty($obj)) {
return null;
}
$value = $obj->getCashValues($key);
return $value;
}
/**
* @param DateTimeInterface $dateFrom
* @param DateTimeInterface $dateTo
* @param string $mode
*
* @throws Exception
* @return array
*/
private function getTrunoverByDays(DateTimeInterface $dateFrom, DateTimeInterface $dateTo, $mode = 'day')
{
if ($dateFrom > $dateTo) {
throw new BadRequestException('Bad request: parameter \'dateFrom\' is later than parameter \'dateTo\'');
}
switch ($mode) {
case 'year':
$formatDb = '%Y';
$formatPhp = 'Y';
break;
case 'month':
$formatDb = '%m/%Y';
$formatPhp = 'm/Y';
break;
case 'week':
$formatDb = '%v/%x';
$formatPhp = 'W/o';
break;
default:
$formatDb = '%Y-%m-%d';
$formatPhp = 'Y-m-d';
break;
}
$dateFormattedFrom = $dateFrom->format('Y-m-d');
$dateFormattedTo = $dateTo->format('Y-m-d');
$values = [
'dateFormattedFrom' => $dateFormattedFrom,
'dateFormattedTo' => $dateFormattedTo,
];
$sqlInvoices = sprintf(
"SELECT DATE_FORMAT(r.datum,'%s') AS `date`, sum(r.umsatz_netto) AS `commitment`
FROM rechnung AS r
WHERE DATE_FORMAT(r.datum,'%%Y-%%m-%%d') >= :dateFormattedFrom
AND DATE_FORMAT(r.datum,'%%Y-%%m-%%d') <= :dateFormattedTo
AND r.status!='angelegt'
GROUP BY DATE_FORMAT(r.datum,'%s')",
$formatDb, $formatDb
);
$resultInvoices = $this->db->fetchPairs($sqlInvoices, $values);
$sqlReturnOrders = sprintf(
"SELECT DATE_FORMAT(g.datum,'%s') AS `date`, sum(g.umsatz_netto) AS `credit`
FROM gutschrift AS g
WHERE DATE_FORMAT(g.datum,'%%Y-%%m-%%d') >= :dateFormattedFrom
AND DATE_FORMAT(g.datum,'%%Y-%%m-%%d') <= :dateFormattedTo
AND g.status!='angelegt'
GROUP BY DATE_FORMAT(g.datum,'%s')",
$formatDb, $formatDb
);
$resultReturnOrders = $this->db->fetchPairs($sqlReturnOrders, $values);
$day = new DateTime($dateFormattedFrom);
$return = [];
while ($day <= $dateTo) {
$dayFormated = $day->format($formatPhp);
$return[$dayFormated] =
(empty($resultInvoices[$dayFormated]) ? 0.0 : $resultInvoices[$dayFormated])
- (empty($resultReturnOrders[$dayFormated]) ? 0.0 : $resultReturnOrders[$dayFormated]);
switch ($mode) {
case 'year':
$day = $day->add(new DateInterval('P1Y'));
break;
case 'month':
$day = $day->add(new DateInterval('P1M'));
break;
case 'week':
$day = $day->add(new DateInterval('P7D'));
break;
default:
$day = $day->add(new DateInterval('P1D'));
break;
}
}
return $return;
}
/**
* @param DateTimeInterface $date
*
* @return float
*/
private function getTurnoverByDay(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y-m-d');
$values = ['dateFormatted' => $dateFormatted];
$sql = "SELECT sum(r.umsatz_netto) AS `commitment`
FROM rechnung AS r
WHERE DATE_FORMAT(r.datum,'%Y-%m-%d')=:dateFormatted AND r.status!='angelegt'";
$result = $this->db->fetchRow($sql, $values);
if (empty($result)) {
return 0.0;
}
$commitment = (float)$result['commitment'];
$sql = "SELECT sum(g.umsatz_netto) AS `credit`
FROM gutschrift AS g
WHERE DATE_FORMAT(g.datum,'%Y-%m-%d')=:dateFormatted AND g.status!='angelegt'";
$result = $this->db->fetchRow($sql, $values);
if (empty($result)) {
return 0.0;
}
$credit = (float)$result['credit'];
return $commitment - $credit;
}
/**
* @param DateTimeInterface $date
*
* @return float
*/
private function getTurnoverByYear(DateTimeInterface $date)
{
$dateFormatted = $date->format('Y');
$values = ['dateFormatted' => $dateFormatted];
$sql = "SELECT sum(r.umsatz_netto) AS `commitment`
FROM rechnung AS r
WHERE DATE_FORMAT(r.datum,'%Y')=:dateFormatted AND r.status!='angelegt'";
$result = $this->db->fetchRow($sql, $values);
if (empty($result)) {
return 0.0;
}
$commitment = (float)$result['commitment'];
$sql = "SELECT sum(g.umsatz_netto) AS `credit`
FROM gutschrift AS g
WHERE DATE_FORMAT(g.datum,'%Y')=:dateFormatted AND g.status!='angelegt'";
$result = $this->db->fetchRow($sql, $values);
if (empty($result)) {
return 0.0;
}
$credit = (float)$result['credit'];
return $commitment - $credit;
}
/**
* @return float
*/
private function getTurnoverThisMonth()
{
return (float)$this->getCashValues('13.1') - (float)$this->getCashValues('13.2');
}
/**
* @return float
*/
private function getTurnoverLastMonth()
{
return (float)$this->getCashValues('17.1') - (float)$this->getCashValues('17.2');
}
/**
* @return float
*/
private function getTurnoverBeforeLastMonth()
{
return (float)$this->getCashValues('21.1') - (float)$this->getCashValues('21.2');
}
/**
* @return float
*/
private function getOpenLiabilies()
{
return (float)$this->getCashValues(9);
}
/**
* @return float
*/
private function getOpenOrders()
{
return (float)$this->getCashValues(10);
}
/**
* @return float
*/
private function getDunning()
{
return (float)$this->getCashValues(11);
}
/**
* @return float
*/
private function getTimeTracking()
{
return (float)$this->app->DB->Select(
"SELECT sum(TIMESTAMPDIFF(HOUR,von,bis))
FROM zeiterfassung
WHERE DATE_FORMAT(von,'%m-%Y') = DATE_FORMAT(NOW(),'%m-%Y')"
);
}
/**
* @return float
*/
private function getSubscriptionRun()
{
$obj = $this->app->erp->LoadModul('rechnungslauf');
$value = 0.0;
if ($obj) {
$value = (float)$obj->RechnungslaufRechnungslauf(true);
}
return $value;
}
/**
* @return float
*/
private function getAccountsTotal()
{
return (float)$this->getCashValues(25);
}
}
@@ -0,0 +1,411 @@
<?php
namespace Xentral\Modules\Api\Controller\Legacy;
use TransferOpentrans;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Converter\OpenTransConverter;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
class OpenTransConnectController
{
/** @var Request $request */
protected $request;
/** @var LegacyApplication $app */
protected $app;
/** @var int $accountId */
protected $accountId;
/**
* @param LegacyApplication $app
* @param Request $request
*/
public function __construct(LegacyApplication $app, OpenTransConverter $converter, Request $request, $accountId)
{
$this->request = $request;
$this->converter = $converter;
$this->app = $app;
$this->accountId = $accountId;
}
/**
* @return Response
*/
public function deleteOrder()
{
$orderId = $this->getDoctypeIdByRequestAttributes('order');
$transferOpenTrans = $this->getTransferObject();
list($result, $statusCode, $rootNode) = $transferOpenTrans->deleteOrder($orderId);
if(is_array($result)) {
$result = $this->converter->arrayToXml($result, $rootNode);
}
if(empty($result)) {
throw new ResourceNotFoundException('Auftrag konnte nicht gelöscht werden');
}
return $this->sendResponse($result,$statusCode);
}
/**
* @param string $doctype
*
* @return int
*/
protected function getDoctypeIdByRequestAttributes($doctype = 'deliverynote')
{
$id = (int)$this->request->attributes->get('id');
$orderId = $this->request->attributes->get('orderid');
$ordernumber = $orderId > 0?'':$this->request->attributes->get('ordernumber');
$extOrder = $orderId > 0 || !empty($ordernumber)?'':$this->request->attributes->get('extorder');
if($id > 0) {
return $id;
}
if(!empty($ordernumber)) {
$orderId = $this->app->DB->Select(
sprintf(
"SELECT id FROM auftrag WHERE belegnr = '%s' AND belegnr <> '' LIMIT 1",
$this->app->DB->real_escape_string($ordernumber)
)
);
if(empty($orderId)) {
throw new ResourceNotFoundException(sprintf('Auftrag mit Belegnr \'%s\' nicht gefunden', $ordernumber));
}
}
if(!empty($extOrder)) {
$orderId = $this->app->DB->Select(
sprintf(
"SELECT id FROM auftrag WHERE internet = '%s' AND internet <> '' LIMIT 1",
$this->app->DB->real_escape_string($extOrder)
)
);
if(empty($orderId)) {
throw new ResourceNotFoundException(sprintf('Auftrag mit Externer Belegnr \'%s\' nicht gefunden', $extOrder));
}
}
if(!empty($orderId)) {
switch($doctype) {
case 'order':
return $orderId;
break;
case 'invoice':
$id = $this->app->DB->Select(
sprintf(
"SELECT id FROM rechnung WHERE auftragid = %d ORDER BY status = 'storniert' LIMIT 1",
$orderId
)
);
if(!empty($id)) {
return $id;
}
throw new ResourceNotFoundException(
sprintf('Rechnung mit Order-ID \'%s\' nicht gefunden',
$orderId
)
);
break;
case 'deliverynote':
default:
$id = $this->app->DB->Select(
sprintf(
"SELECT id FROM lieferschein WHERE auftragid = %d ORDER BY status = 'storniert' LIMIT 1",
$orderId
)
);
if(!empty($id)) {
return $id;
}
throw new ResourceNotFoundException(
sprintf('Lieferschein mit Order-ID \'%s\' nicht gefunden',
$orderId
)
);
break;
}
}
return $id;
}
/**
* @return Response
*/
public function readDispatchnotification()
{
$deliveryNoteId = $this->getDoctypeIdByRequestAttributes('deliverynote');
$transferOpenTrans = $this->getTransferObject();
list($result, $statusCode, $rootNode) = $transferOpenTrans->getDispatchnotification($deliveryNoteId);
if(is_array($result)) {
$result = $this->converter->arrayToXml($result, $rootNode);
}
if(empty($result)) {
throw new ResourceNotFoundException(
sprintf('Lieferschein mit ID \'%s\' nicht gefunden',
$deliveryNoteId
)
);
}
return $this->sendResponse($result,$statusCode);
}
/**
* @param int $apiId
* @param string $request
* @param string $type
* @param bool $isIncoming
* @param string $doctype
* @param string $status
* @param int $doctypeId
*
* @return int
*/
protected function insertApiRequestLog(
$apiId, $request, $type, $isIncoming, $doctype, $status = '', $doctypeId = 0
)
{
$this->app->DB->Insert(
sprintf(
"INSERT INTO `api_request_response_log`
(api_id, raw_request, raw_response, type, status, doctype, doctype_id, is_incomming, created_at)
VALUES (%d, '%s', '%s', '%s', '%s','%s',%d,%d,NOW()) ",
$apiId,
($isIncoming?$this->app->DB->real_escape_string($request):''),
(!$isIncoming?$this->app->DB->real_escape_string($request):''),
$this->app->DB->real_escape_string($type),
$this->app->DB->real_escape_string($status),
$this->app->DB->real_escape_string($doctype),
$doctypeId,
$isIncoming
)
);
return (int)$this->app->DB->GetInsertID();
}
/**
* @param int $logId
* @param string $status
*/
protected function setLogStatus($logId, $status)
{
$this->app->DB->Update(
sprintf(
"UPDATE `api_request_response_log` SET `status` = '%s' WHERE `id` = %d",
$this->app->DB->real_escape_string($status),
$logId
)
);
}
/**
* @param int $logId
* @param int $doctypeId
*/
protected function setLogDoctypeId($logId, $doctypeId)
{
$this->app->DB->Update(
sprintf(
'UPDATE `api_request_response_log` SET `doctype_id` = %d WHERE `id` = %d',
$doctypeId,
$logId
)
);
}
/**
* @return Response
*/
public function createOrder()
{
$transferOpenTrans = $this->getTransferObject();
if(!empty($this->accountId)) {
$transferOpenTrans->setApiId($this->accountId);
}
$post = $this->request->getContent();
if(empty($post)) {
throw new ResourceNotFoundException('Data is empty');
}
$logId = $this->insertApiRequestLog($this->accountId, $post, 'create_order',true,'auftrag');
$xml = $this->converter->getXmlFromString($post);
if(empty($xml)) {
$this->setLogStatus($logId, 'error');
throw new ResourceNotFoundException('Data is no valid Xml');
}
list($result, $statusCode, $rootNode, $orderId) = $transferOpenTrans->createOrder($xml);
if(!empty($orderId)) {
$this->setLogDoctypeId($logId, $orderId);
}
if(is_array($result)) {
$result = $this->converter->arrayToXml($result, $rootNode);
}
if(empty($result)) {
$this->setLogStatus($logId, 'error');
throw new ResourceNotFoundException('Auftrag konnte nicht erstellt werden');
}
if($statusCode === Response::HTTP_CREATED) {
$this->setLogStatus($logId, 'ok');
}
else {
$this->setLogStatus($logId, 'error');
}
return $this->sendResponse($result,$statusCode);
}
/**
* @return Response
*/
public function updateDispatchnotification()
{
$deliveryNoteId = $this->getDoctypeIdByRequestAttributes('deliverynote');
$post = $this->request->getContent();
if(empty($post)) {
throw new ResourceNotFoundException('Data is empty');
}
$logId = $this->insertApiRequestLog(
$this->accountId, $post, 'update_dispatchnotification',true,'lieferschein','', $deliveryNoteId
);
$xml = $this->converter->getXmlFromString($post);
if(empty($xml)) {
$this->setLogStatus($logId, 'error');
throw new ResourceNotFoundException('Data is no valid Xml');
}
$transferOpenTrans = $this->getTransferObject();
list($result, $statusCode, $rootNode) = $transferOpenTrans->updateDispatchnotification($deliveryNoteId, $xml);
if(is_array($result)) {
$result = $this->converter->arrayToXml(
$result,
$rootNode
);
}
if($statusCode === Response::HTTP_OK) {
$this->setLogStatus($logId, 'ok');
}
return $this->sendResponse($result,$statusCode);
}
/**
* @return Response
*/
public function readInvoice()
{
$invoiceId = $this->getDoctypeIdByRequestAttributes('invoice');
$transferOpenTrans = $this->getTransferObject();
list($result, $statusCode, $rootNode) = $transferOpenTrans->getInvoice($invoiceId);
if(is_array($result)) {
$result = $this->converter->arrayToXml(
$result,
$rootNode
);
}
if(empty($result)) {
throw new ResourceNotFoundException(sprintf('Rechnung mit ID \'%s\' nicht gefunden', $invoiceId));
}
return $this->sendResponse($result,$statusCode);
}
/**
* @return Response
*/
public function readOrder()
{
$orderId = $this->getDoctypeIdByRequestAttributes('order');
$transferOpenTrans = $this->getTransferObject();
list($result,$statusCode, $rootNode) = $transferOpenTrans->getOrder($orderId);
if(is_array($result)) {
$result = $this->converter->arrayToXml
(
$result,
$rootNode
);
}
if(empty($result)) {
throw new ResourceNotFoundException(sprintf('Auftrag mit ID \'%s\' nicht gefunden', $orderId));
}
return $this->sendResponse($result,$statusCode);
}
public function updateOrder()
{
$orderId = $this->getDoctypeIdByRequestAttributes('order');
$transferOpenTrans = $this->getTransferObject();
$order = $transferOpenTrans->getOrderArr($orderId);
if(empty($order)) {
throw new ResourceNotFoundException(sprintf('Auftrag mit ID \'%s\' nicht gefunden', $orderId));
}
$post = $this->request->getContent();
$logId = $this->insertApiRequestLog(
$this->accountId, $post, 'update_order',true,'auftrag','', $orderId
);
$arr = $this->converter->toArray($post);
if(empty($arr)) {
$this->setLogStatus($logId, 'error');
throw new ResourceNotFoundException('XML konnte nicht geparsed werden');
}
}
/**
* @param string $data
* @param string $contentType [xml|json]
* @param int $statusCode HTTP-Statuscode
*
* @return Response
*/
protected function sendResponse($data, $contentType, $statusCode = Response::HTTP_OK)
{
return new Response(
$data,
$statusCode,
['Content-Type' => 'application/xml; charset=UTF-8']
);
}
/**
* @return TransferOpentrans
*/
private function getTransferObject()
{
$id = (int)$this->app->DB->Select(
"SELECT id FROM uebertragungen_account WHERE aktiv = 1 AND xml_pdf = 'TransferOpenTrans' AND id = %d LIMIT 1",
$this->accountId
);
if($id < 0) {
throw new ResourceNotFoundException('TransferOpenTrans Module not found');
}
/** @var \Uebertragungen $transferObject */
$transferObject = $this->app->loadModule('uebertragungen');
if(empty($transferObject)) {
throw new ResourceNotFoundException('TransferOpenTrans Module not found');
}
return $transferObject->LoadTransferModul('TransferOpentrans', $id);
}
}
@@ -0,0 +1,483 @@
<?php
namespace Xentral\Modules\Api\Controller\Legacy;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
class ShopimportController
{
/** @var Request $request */
protected $request;
/** @var LegacyApplication $app */
protected $app;
/** @var int $accountId */
protected $accountId;
/**
* @param LegacyApplication $app
* @param Request $request
*/
public function __construct(LegacyApplication $app, Request $request, $accountId)
{
$this->request = $request;
$this->app = $app;
$this->accountId = $accountId;
}
/**
* @param bool $onlyActive
*
* @return array
*/
private function getShopFromApi($onlyActive = true)
{
$shop = $this->app->DB->SelectRow(
sprintf(
'SELECT * FROM `shopexport` WHERE `api_account_id` = %d LIMIT 1',
$this->accountId
)
);
if (empty($shop)) {
throw new ResourceNotFoundException('Shop not found');
}
if($onlyActive && empty($shop['aktiv'])) {
throw new ResourceNotFoundException('Shop not connected');
}
return $shop;
}
/**
* @return Response
*/
public function auth()
{
$shop = $this->getShopFromApi();
$pageContents = $this->app->remote->RemoteConnection($shop['id'], true);
if (strpos($pageContents, 'success') !== 0) {
throw new ResourceNotFoundException('Auth Error ' . $pageContents);
}
/*$this->app->DB->Update(
sprintf(
"UPDATE `shopexport` SET `api_account_token` = '' WHERE `id` = %d",
$shop['id']
)
);*/
return $this->sendResponse(json_encode(['success' => true]), Response::HTTP_OK);
}
/**
* @return string
*/
public function getOrderByRequest()
{
$orderNumber = $this->request->attributes->get('ordernumber');
$orderNumber = base64_decode($orderNumber);
if (empty($orderNumber)) {
throw new ResourceNotFoundException(
'Ordernumber is empty'
);
}
return $orderNumber;
}
/**
* @param int $shopId
* @param bool $withDbCheck
*/
public function getArticleByRequest($shopId, $withDbCheck = true)
{
$articlenumber = $this->request->attributes->get('articlenumber');
$articlenumber = base64_decode($articlenumber);
if (empty($articlenumber)) {
throw new ResourceNotFoundException(
'Articlenumber is empty'
);
}
$article = $this->app->DB->SelectRow(
sprintf(
"SELECT art.id, art.projekt FROM `artikel` AS art
LEFT JOIN `artikelnummer_fremdnummern` AS af on art.id = af.artikel AND af.aktiv = 1 AND af.shopid = %d
WHERE (art.nummer = '%s' OR af.nummer = '%s') AND (art.geloescht = 0 OR art.geloescht IS NULL)
ORDER BY af.id DESC
LIMIT 1",
$shopId,
$this->app->DB->real_escape_string($articlenumber),
$this->app->DB->real_escape_string($articlenumber)
)
);
if (empty($article)) {
if($withDbCheck) {
throw new ResourceNotFoundException(
sprintf('Articlenumber %s not found', $articlenumber)
);
}
$article = [];
}
$article['number'] = $articlenumber;
return $article;
}
/**
* @return Response
*/
public function putArticleToShop()
{
$this->auth();
$shop = $this->getShopFromApi();
$article = $this->getArticleByRequest($shop['id']);
$ret = $this->app->remote->RemoteSendArticleList($shop['id'],[$article['id']], $article['number'], false);
if (empty($ret) || !is_array($ret) || isset($ret['error'])) {
return $this->sendResponse(
json_encode(['success' => false]),
Response::HTTP_BAD_REQUEST
);
}
return $this->sendResponse(json_encode(['success' => true]), Response::HTTP_OK);
}
/**
* @return Response
*/
public function getStatus()
{
$shop = $this->getShopFromApi(false);
$status = !empty($shop['aktiv']);
if($status) {
$this->auth();
}
return $this->sendResponse(json_encode(['success' => true, 'connected' => $status]), Response::HTTP_OK);
}
/**
* @return Response
*/
public function postDisconnect()
{
$shop = $this->getShopFromApi(false);
$status = !empty($shop['aktiv']);
if(!$status) {
return $this->sendResponse(
json_encode(
['success' => false,'error'=>'shop allready disconnected']
),
Response::HTTP_BAD_REQUEST
);
}
$this->app->DB->Update(sprintf("UPDATE `shopexport` SET `aktiv` = 0 WHERE `id` = %d", $shop['id']));
return $this->sendResponse(
json_encode(
['success' => true,'message'=>'shop disconnected']
),
Response::HTTP_OK
);
}
/**
* @return Response
*/
public function postReconnect()
{
$shop = $this->getShopFromApi(false);
$status = !empty($shop['aktiv']);
if($status) {
return $this->sendResponse(
json_encode(
['success' => false,'error'=>'shop allready connected']
),
Response::HTTP_BAD_REQUEST
);
}
$this->app->DB->Update(sprintf("UPDATE `shopexport` SET `aktiv` = 1 WHERE `id` = %d", $shop['id']));
return $this->sendResponse(
json_encode(
['success' => true,'message'=>'shop reconnected']
),
Response::HTTP_OK
);
}
/**
* @return Response
*/
public function putOrderToXentral()
{
$this->auth();
$shop = $this->getShopFromApi();
$orderNumber = $this->getOrderByRequest();
/** @var \Shopimport $shopimport */
$shopimport = $this->app->loadModule('shopimport');
$res = $shopimport->importSingleOrder(
$shop['id'], $orderNumber, empty($shop['demomodus']), $shop['projekt'], true
);
if(empty($res['status'])) {
return $this->sendResponse(
json_encode(
['success' => false,'error'=>$res['error']]
),
Response::HTTP_BAD_REQUEST
);
}
if($shop['auftraegeaufspaeter']) {
return $this->sendResponse(
json_encode(
[
'success' => true,
'message'=>$res['info'],
]
),
Response::HTTP_OK
);
}
$cart = $this->app->DB->SelectRow(
sprintf('SELECT * FROM `shopimport_auftraege` WHERE `id` = %d', $res['id'])
);
[$customerNumber, $customerNumberImported] = $shopimport->getCustomerNumberFromShopCart($cart);
$res = $shopimport->importShopOrder(
$res['id'], $shop['utf8codierung'],
$customerNumber, $customerNumberImported,
$unknownPaymentTypes
);
return $this->sendResponse(
json_encode(
[
'success' => true,
'message'=>$res['info'],
]
),
Response::HTTP_OK
);
}
/**
* @return Response
*/
public function putArticleToXentral()
{
$this->auth();
$shop = $this->getShopFromApi();
$article = $this->getArticleByRequest($shop['id'], false);
$ret = $this->app->remote->RemoteGetArticle($shop['id'], $article['number'], true);
if (empty($ret) || !is_array($ret) || isset($ret['error'])) {
return $this->sendResponse(
json_encode(['success' => false]),
Response::HTTP_BAD_REQUEST
);
}
if(empty($article['id'])) {
$article = $this->getArticleByRequest($shop['id'], false);
}
if(!empty($article['id'])) {
/** @var \Artikel $articleObj */
$articleObj = $this->app->loadModule('artikel');
$articleObj->updateShopArticle($article['id'], $ret);
}
return $this->sendResponse(json_encode(['success' => true]), Response::HTTP_OK);
}
/**
* @return Response
*/
public function syncStorage()
{
//$this->auth();
$shop = $this->getShopFromApi();
$article = $this->getArticleByRequest($shop['id']);
$ret = $this->app->remote->RemoteSendArticleList($shop['id'], [$article['id']],$article['number'], true);
if (empty($ret) || (!is_array($ret) && $ret !== 1) || isset($ret['error'])) {
return $this->sendResponse(
json_encode(['success' => false]),
Response::HTTP_BAD_REQUEST
);
}
return $this->sendResponse(json_encode(['success' => true]), Response::HTTP_OK);
}
/**
* @return Response
*/
public function getArticleSyncState()
{
$shop = $this->getShopFromApi();
$count = $this->app->DB->Select(
sprintf(
'SELECT COUNT(`ao`.`id`)
FROM `artikel_onlineshops` AS `ao`
INNER JOIN `artikel` AS `art` ON `ao`.artikel = `art`.`id` AND `art`.geloescht = 0
WHERE `ao`.shop = %d AND `ao`.`aktiv` = 1',
$shop['id']
)
);
return $this->sendResponse(json_encode(['success' => true, 'count' => $count]), Response::HTTP_OK);
}
public function postDistconnect()
{
//postReconnect
}
/**
* @return Response
*/
public function getModulelinks()
{
$shop = $this->getShopFromApi();
$shopId = $shop['id'];
/** @var \Onlineshops $onlineShop */
$onlineShop = $this->app->loadModule('onlineshops');
$moduleList = $onlineShop->getModulelinks($shopId);
return $this->sendResponse(
json_encode(
['success' => true, 'modulelist' => $moduleList]
),
Response::HTTP_OK
);
}
/**
* @return Response
*/
public function getStatistics()
{
$shop = $this->getShopFromApi();
$shopId = $shop['id'];
$stats = [];
/** @var \Verkaufszahlen $verkaufszahlen */
$verkaufszahlen = $this->app->loadModule('verkaufszahlen');
[$stats['orders_in_shipment'], $stats['orders_open']] = $verkaufszahlen->getVersandStats(
sprintf(' AND a.shop = %d ', $shopId)
);
$stats['packages_yesterday'] = $verkaufszahlen->getPackages(
" v.versendet_am=DATE_FORMAT(DATE_SUB(NOW(),INTERVAL 1 day),'%Y-%m-%d') '",
sprintf('INNER JOIN `auftrag` AS `a` ON l.auftragid = a.id AND a.shop = %d', $shopId)
);
$stats['packages_today'] = $verkaufszahlen->getPackages(
" v.versendet_am=DATE_FORMAT(NOW(),'%Y-%m-%d') '",
sprintf('INNER JOIN `auftrag` AS `a` ON l.auftragid = a.id AND a.shop = %d', $shopId)
);
[
$stats['order_income_yesterday'],
$stats['contribution_margin_yesterday'],
$stats['contribution_margin_perc_yesterday']
] =
$verkaufszahlen->getOrderStats(
sprintf(
" AND `datum` = DATE_FORMAT(DATE_SUB(NOW(),INTERVAL 1 day),'%%Y-%%m-%%d') AND `shop` = %d ",
$shopId
)
);
[
$stats['order_income_today'],
$stats['contribution_margin_today'],
$stats['contribution_margin_perc_today']
] =
$verkaufszahlen->getOrderStats(
sprintf(
" AND `datum` = DATE_FORMAT(NOW(),'%%Y-%%m-%%d') AND `shop` = %d ",
$shopId
)
);
return $this->sendResponse(json_encode(['success' => true, 'stats' => $stats]), Response::HTTP_OK);
}
/**
* @return Response
*/
public function postRefund()
{
$shop = $this->getShopFromApi();
$shopId = $shop['id'];
$post = $this->request->getContent();
if(empty($post)) {
throw new ResourceNotFoundException('Data is empty');
}
$contentType = $this->request->getContentType();
$data = null;
if ($contentType === 'json' || $contentType === null) {
$data = json_decode($post);
}
if ($data === null && ($contentType === 'xml' || $contentType === null)) {
$data = simplexml_load_string($post);
}
if(empty($post)) {
throw new ResourceNotFoundException('could not parse Data');
}
/** @var \Shopimport $shopimport */
$shopimport = $this->app->loadModule('shopimport');
if($shopimport === null || !method_exists($shopimport, 'Refund')) {
return $this->sendResponse(
json_encode(
[
'success' => false,
'error'=>'not implemented'
]
),
Response::HTTP_BAD_REQUEST
);
}
try {
$ret = $shopimport->Refund($shopId, $data);
}
catch(\Exception $e) {
return $this->sendResponse(
json_encode(
[
'success' => false,
'error' => $e->getMessage(),
]
),
Response::HTTP_BAD_REQUEST
);
}
return $this->sendResponse(json_encode(['success' => true,'creditnote_id' => $ret]), Response::HTTP_OK);
}
/**
* @param string $data
* @param string $contentType [xml|json]
* @param int $statusCode HTTP-Statuscode
*
* @return Response
*/
protected function sendResponse($data, $contentType, $statusCode = Response::HTTP_OK)
{
return new Response(
$data,
$statusCode,
['Content-Type' => 'application/json; charset=UTF-8']
);
}
}
@@ -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;
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace Xentral\Modules\Api\Converter;
class Converter
{
const CONVERTER_TYPE_JSON = 'json';
const CONVERTER_TYPE_XML = 'xml';
/**
* @var array
*/
protected static $validTypes = array(
self::CONVERTER_TYPE_JSON,
self::CONVERTER_TYPE_XML
);
/** @var XmlConverter $xml */
protected $xml;
/** @var JsonConverter $json */
protected $json;
/**
* @param XmlConverter $xml
* @param JsonConverter $json
*/
public function __construct(XmlConverter $xml, JsonConverter $json)
{
$this->xml = $xml;
$this->json = $json;
}
/**
* @param array $array
*
* @return string
*/
public function arrayToJson(array $array)
{
return $this->json->fromArray($array);
}
/**
* @param $jsonString
*
* @return array
*/
public function jsonToArray($jsonString)
{
return $this->json->toArray($jsonString);
}
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function arrayToXml(array $array, $rootNode = 'xml')
{
return $this->xml->convertArrayToXmlString($array, $rootNode);
}
/**
* @param string $xmlString
* @param bool $wrap
*
* @return array
*/
public function xmlToArray($xmlString, $wrap = false)
{
return $this->xml->convertXmlStringToArray($xmlString, $wrap);
}
/**
* @param string $type
* @param array $data
*
* @return string
*/
public function arrayTo($type, array $data)
{
$type = strtolower($type);
if (!in_array($type, $this->getSupportedTypes(), true)) {
throw new \RuntimeException(sprintf(
'Converter type "%s" is not supported.', $type
));
}
return $this->{$type}->fromArray($data);
}
/**
* @param string $type
* @param string $content
*
* @return array
*/
public function toArray($type, $content)
{
$type = strtolower($type);
if (!in_array($type, $this->getSupportedTypes(), true)) {
throw new \RuntimeException(sprintf(
'Converter type "%s" is not supported.', $type
));
}
return $this->{$type}->toArray($content);
}
/**
* @return array
*/
public function getSupportedTypes()
{
return self::$validTypes;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Xentral\Modules\Api\Converter;
interface ConverterInterface
{
/**
* Wandle Array in Converter-Format (XML oder JSON)
*
* @param array $array
*
* @return string
*/
public function fromArray($array);
/**
* Wandle Converter-Format (XML oder JSON) zu Array
*
* @param string $data
*
* @return array
*/
public function toArray($data);
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Api\Converter\Exception;
class ConvertionException extends \RuntimeException
{
protected $message = 'Convertion failed.';
}
@@ -0,0 +1,44 @@
<?php
namespace Xentral\Modules\Api\Converter;
use Xentral\Modules\Api\Converter\Exception\ConvertionException;
class JsonConverter implements ConverterInterface
{
/**
* Array zu JSON
*
* @param array $array
*
* @return string
*/
public function fromArray($array)
{
$data = json_encode($array);
if ($data === false || json_last_error() !== JSON_ERROR_NONE) {
throw new ConvertionException('JSON could not be encoded.');
}
return $data;
}
/**
* JSON zu Array
*
* @param string $json
*
* @return array
*/
public function toArray($json)
{
$data = json_decode($json, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
throw new ConvertionException('JSON could not be decoded.');
}
return $data;
}
}
@@ -0,0 +1,368 @@
<?php
namespace Xentral\Modules\Api\Converter;
use SimpleXMLElement;
use Xentral\Modules\Api\Converter\Exception\ConvertionException;
class OpenTransConverter implements ConverterInterface
{
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function fromArray($array, $rootNode = 'xml')
{
return $this->convertArrayToXmlString($array, $rootNode);
}
/**
* @param string $data
*
* @return array
*/
public function toArray($data)
{
return $this->convertXmlStringToArray($data);
}
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function arrayToXml(array $array, $rootNode = 'xml')
{
return $this->convertArrayToXmlString($array, $rootNode);
}
/**
* @param string $xml
*
* @return SimpleXMLElement
*/
public function getXmlFromString($xml)
{
return simplexml_load_string($xml, null, LIBXML_NOCDATA);
}
/**
* Kovertiert einen XML-String in ein Array
*
* @param string $xml
* @param bool $wrap
*
* @return array
*
* @throws \RuntimeException
*/
public function convertXmlStringToArray($xml)
{
$namespaces = [];
$simplexml = simplexml_load_string($xml, null, LIBXML_NOCDATA);
if(is_object($simplexml)) {
$namespaces = $simplexml->getNamespaces();
}
if ($simplexml === false) {
throw new ConvertionException('XML could not be decoded.');
}
return $this->convertSimpleXmlToArray($simplexml, $namespaces);
}
/**
* @param array|SimpleXMLElement $attributes
*
* @return string
*/
protected function attributeKey($attributes) {
$ret = '';
if(empty($attributes)) {
return $ret;
}
foreach($attributes as $key => $attribute) {
if((is_array($attribute) || is_object($attribute)) && count($attribute) === 1) {
$ret .= ' '.$key.'="'.reset($attribute).'"';
continue;
}
$ret .= ' '.$key.'="'.$attribute.'"';
}
return $ret;
}
/**
* @param SimpleXMLElement $object
* @param array|null $namespaces
*
* @return array|string
*/
public function convertSimpleXmlToArray($object, $namespaces)
{
$array = [];
$isObject = is_object($object);
$cobject = $isObject?count($object):0;
if($isObject && $cobject === 0) {
$name = $object->getName();
$attributes = $object->attributes();
$attributeKey = $this->attributeKey($attributes);
$array[$name.$attributeKey] = (string)$object;
return $array;
}
$arr = (array)$object;
if(isset($arr['@attributes'])) {
unset($arr['@attributes']);
}
$keys = array_keys($arr);
$count = count($keys);
if($isObject && !empty($arr)) {
foreach($object as $key => $value) {
if($key === '@attributes') {
continue;
}
if($key === 0 && $count === 1) {
return $value;
}
$valueArr = (array)$value;
if(isset($valueArr['@attributes'])) {
unset($valueArr['@attributes']);
}
if(is_object($value) && !empty($valueArr)) {
$cValue = count($value);
$cValueArr = count($valueArr);
$attributes = $value->attributes();
$attributeKey = $this->attributeKey($attributes);
if(isset($array[$key.$attributeKey])) {
if(!isset($array[$key.$attributeKey][0])) {
$array[$key.$attributeKey] = [$array[$key.$attributeKey]];
}
if($cValue === 0 || ($cValue <= 1 && $cValueArr === 1)) {
$valueReset = reset($valueArr);
if(!is_object($valueReset) && !is_array($valueReset)) {
$array[$key.$attributeKey][] = $valueReset;
continue;
}
}
$array[$key.$attributeKey][] = $this->convertSimpleXmlToArray($value, $namespaces);
continue;
}
if($cValue === 0 || ($cValue <= 1 && $cValueArr === 1)) {
$valueReset = reset($valueArr);
if (!is_object($valueReset) && !is_array($valueReset)) {
$array[$key.$attributeKey] = $valueReset;
continue;
}
}
$array[$key.$attributeKey] = $this->convertSimpleXmlToArray($value, $namespaces);
}
else {
$array[$key] = (string)$value;
}
}
return $array;
}
return (string)$object;
}
/**
* Wandelt ein SimpleXml-Objekt in ein Array
*
* @param SimpleXMLElement $object
*
* @return array|string
*/
public function convertSimpleXmlToArray_old($object, $namespaces)
{
if(is_object($object)) {
$attributes = (array)$object->attributes();
$namespace = $object->getNamespaces();
if(!empty($attributes) || !empty($namespace)) {
if($attributes) {
}
}
}
$array = (array)$object;
if (empty($array)) {
return '';
}
foreach ($array as $key => $value) {
$isObject = is_object($value);
if ($isObject || is_array($value)) {
$attributes = null;
if($key === '@attributes') {
if($value) {
}
}
if($key !== '@attributes' && $isObject) {
$attributes = (array)$value->attributes();
if(!empty($attributes)) {
foo($attributes);
}
}
$array[$key] = $this->convertSimpleXmlToArray($value);
}
}
return $array;
}
/**
* @param array $array
* @param string $rootNode Name des Root-Elements
*
* @return SimpleXMLElement
*/
public function convertArrayToSimpleXml($array, $rootNode = 'xml')
{
$rootNodeCloser = explode(' ', $rootNode);
$rootNodeCloser = reset($rootNodeCloser);
$xml = new SimpleXMLElement(
sprintf('<?xml version="1.0" encoding="UTF-8"?><%s></%s>', $rootNode, $rootNodeCloser)
);
$nameSpaces = $this->getNameSpacesByNode($rootNode);
$this->arrayToXmlHelper($xml, $array, $nameSpaces);
return $xml;
}
/**
* @param string $node
*
* @return array
*/
protected function getNameSpacesByNode($node)
{
$nameSpaces = [];
$nodeArr = explode(' ', $node);
unset($nodeArr[0]);
foreach($nodeArr as $nodeVal) {
$nodeVal = trim($nodeVal);
if(empty($nodeVal)) {
continue;
}
if(preg_match_all('/xmlns(:{0,1})([^=]*)="([^"]+)"/', $nodeVal, $matches)) {
$nameSpaces[$matches[2][0]] = $matches[3][0];
}
}
return $nameSpaces;
}
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function convertArrayToXmlString($array, $rootNode = 'xml')
{
$simpleXml = $this->convertArrayToSimpleXml($array, $rootNode);
return $simpleXml->asXML();
}
/**
* @see convertArrayToSimpleXml
*
* @param SimpleXMLElement $xmlObj
* @param array $array
* @param array $nameSpaces
* @param string $parentTag
* @param array $attributesFromParent
*/
protected function arrayToXmlHelper(&$xmlObj, $array, $nameSpaces = [], $parentTag = '', $attributesFromParent = [])
{
foreach ($array as $key => $value) {
// Wenn kein Knotenname ermittelt werden konnte > den Knoten 'item' nennen
$subNodeName = is_int($key) ? 'item' : $key;
if(!empty($parentTag) && is_int($key)) {
$subNodeName = $parentTag;
}
list($subNodeName, $attributes, $nameSpace) = $this->getAttributesFromKey($subNodeName, $nameSpaces);
if (is_array($value)) {
$useParentTag = !empty($key);
foreach ($value as $key2 => $value2) {
if(!is_int($key2) || !$useParentTag) {
$useParentTag = false;
break;
}
}
if($useParentTag) {
$this->arrayToXmlHelper($xmlObj, $value, $nameSpaces, $subNodeName, $attributes);
}
else {
$subNode = $xmlObj->addChild((string)$subNodeName, null, $nameSpace);
if (!empty($attributes)) {
foreach ($attributes as $attribute) {
$subNode->addAttribute((string)$attribute[0],
empty($attribute[1]) ? '' : (string)$attribute[1]);
}
}
elseif(!empty($attributesFromParent)) {
foreach ($attributesFromParent as $attribute) {
$subNode->addAttribute((string)$attribute[0],
empty($attribute[1]) ? '' : (string)$attribute[1]);
}
}
$this->arrayToXmlHelper($subNode, $value, $nameSpaces,$subNodeName);
}
} else {
$subNode = $xmlObj->addChild((string)$subNodeName, htmlspecialchars($value, ENT_QUOTES), $nameSpace);
if(!empty($attributes)) {
foreach($attributes as $attribute) {
$subNode->addAttribute((string)$attribute[0], empty($attribute[1])?'':(string)$attribute[1]);
}
}
elseif(!empty($attributesFromParent)) {
foreach($attributesFromParent as $attribute) {
$subNode->addAttribute((string)$attribute[0], empty($attribute[1])?'':(string)$attribute[1]);
}
}
}
}
}
/**
* @param string $key
* @param array $nameSpaces
*
* @return array
*/
protected function getAttributesFromKey($key, $nameSpaces = [])
{
$keyArr = explode(' ', $key);
$nameSpace = null;
$node = $keyArr[0];
if(strpos($node, ':') !== false) {
list($nameSpaceShort, $node) = explode(':', $node, 2);
if($nameSpaceShort !== '' && isset($nameSpaces[$nameSpaceShort])) {
$nameSpace = $nameSpaces[$nameSpaceShort];
}
}
unset($keyArr[0]);
$attributes = [];
foreach($keyArr as $attr) {
if(empty($attr)) {
continue;
}
$attrA = explode('=', $attr,2);
if(!empty($attrA[1])) {
$attrA[1] = trim($attrA[1],'"');
}
$attributes[] = $attrA;
}
return [$node, $attributes, $nameSpace];
}
}
@@ -0,0 +1,133 @@
<?php
namespace Xentral\Modules\Api\Converter;
use Xentral\Modules\Api\Converter\Exception\ConvertionException;
/**
* @todo Tests
*/
class XmlConverter implements ConverterInterface
{
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function fromArray($array, $rootNode = 'xml')
{
return $this->convertArrayToXmlString($array, $rootNode);
}
/**
* @param string $data
* @param bool $wrap
*
* @return array
*/
public function toArray($data, $wrap = false)
{
return $this->convertXmlStringToArray($data, $wrap);
}
/**
* Kovertiert einen XML-String in ein Array
*
* @param string $xml
* @param bool $wrap
*
* @return array
*
* @throws \RuntimeException
*/
public function convertXmlStringToArray($xml, $wrap = false)
{
if ($wrap) {
$xml = "<data>{$xml}</data>";
}
$simplexml = simplexml_load_string($xml, null, LIBXML_NOCDATA);
if ($simplexml === false) {
throw new ConvertionException('XML could not be decoded.');
}
$array = $this->convertSimpleXmlToArray($simplexml);
return $array;
}
/**
* Wandelt ein SimpleXml-Objekt in ein Array
*
* @param \SimpleXMLElement $object
*
* @return array|string
*/
public function convertSimpleXmlToArray($object)
{
$array = (array)$object;
if (empty($array)) {
return '';
}
foreach ($array as $key => $value) {
if (is_object($value) || is_array($value)) {
$array[$key] = $this->convertSimpleXmlToArray($value);
}
}
return $array;
}
/**
* @param array $array
* @param string $rootNode Name des Root-Elements
*
* @return \SimpleXMLElement
*/
public function convertArrayToSimpleXml($array, $rootNode = 'xml')
{
$xml = new \SimpleXMLElement(
sprintf('<?xml version="1.0" encoding="UTF-8"?><%s></%s>', $rootNode, $rootNode)
);
$this->arrayToXmlHelper($xml, $array);
return $xml;
}
/**
* @param array $array
* @param string $rootNode
*
* @return string
*/
public function convertArrayToXmlString($array, $rootNode = 'xml')
{
$simpleXml = $this->convertArrayToSimpleXml($array, $rootNode);
return $simpleXml->asXML();
}
/**
* @see convertArrayToSimpleXml
*
* @param \SimpleXMLElement $xmlObj
* @param array $array
*/
protected function arrayToXmlHelper(&$xmlObj, $array)
{
foreach ($array as $key => $value) {
// Wenn kein Knotenname ermittelt werden konnte > den Knoten 'item' nennen
$subNodeName = is_int($key) ? 'item' : $key;
if (is_array($value)) {
$subNode = $xmlObj->addChild((string)$subNodeName);
$this->arrayToXmlHelper($subNode, $value);
} else {
$xmlObj->addChild((string)$subNodeName, htmlspecialchars($value, ENT_QUOTES));
}
}
}
}
@@ -0,0 +1,221 @@
<?php
namespace Xentral\Modules\Api\Dashboard;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
class WidgetData
{
/** @var string WIDGET_TYPE_SIMPLE */
const WIDGET_TYPE_SIMPLE = 'simple';
/** @var string WIDGET_TYPE_SIMPLE_BIG */
const WIDGET_TYPE_SIMPLE_BIG = 'simple_big';
/** @var string WIDGET_TYPE_CONTRAST */
const WIDGET_TYPE_CONTRAST = 'contrast';
/** @var string WIDGET_CONTRAST_BIG */
const WIDGET_TYPE_CONTRAST_BIG = 'contrast_big';
/** @var string WIDGET_TYPE_BARCHART */
const WIDGET_TYPE_BARCHART = 'barchart';
/** @var string WIDGET_TREND_RISE */
const WIDGET_TREND_RISE = 'rise';
/** @var string WIDGET_TREND_FALL */
const WIDGET_TREND_FALL = 'fall';
/** @var string WIDGET_TREND_EQUAL */
const WIDGET_TREND_EQUAL = 'equal';
/** @var string WIDGET_TREND_NONE */
const WIDGET_TREND_NONE = 'none';
/** @var string FORMAT_TEXT */
const FORMAT_TEXT = 'text';
/** @var string FORMAT_CURRENCY */
const FORMAT_CURRENCY = 'currency';
/** @var string FORMAT_DECIMAL */
const FORMAT_DECIMAL = 'decimal';
/** @var string FORMAT_HOURS */
const FORMAT_HOURS = 'hours';
/** @var array $formats */
private static $formats = [self::FORMAT_TEXT, self::FORMAT_CURRENCY, self::FORMAT_DECIMAL, self::FORMAT_HOURS];
/** @var string $name */
protected $name;
/** @var string $type */
protected $type;
/** @var string $label */
protected $label;
/** @var array $value */
protected $value;
/** @var string $context */
protected $context;
/** @var array $format */
protected $format;
/**@var string $valueUnit */
private $valueUnit;
/**
* WidgetData constructor.
*
* @param string $name
* @param string $type
* @param string $label
* @param array $value
* @param string $context
* @param string $valueUnit
* @param string $format
*/
public function __construct($name, $type, $label, $value, $context, $valueUnit = '', $format = self::FORMAT_TEXT)
{
$this->name = $name;
$this->type = $type;
$this->label = $label;
$this->value = $value;
$this->context = $context;
$this->valueUnit = $valueUnit;
$this->setFormat($format);
}
/**
* @param string $name
*
* @return WidgetData
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* @param string $label
*
* @return WidgetData
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* @param string $format
*
* @return void
*
* @throws InvalidArgumentException
*/
public function setFormat($format)
{
if (!in_array($format, self::$formats, true)) {
throw new InvalidArgumentException(sprintf('Unknown format "%s".', $format));
}
$this->format = $format;
}
/**
* @return array formatted Value(s)
*/
public function getFormattedValue()
{
if (empty($this->value)) {
return [];
}
$result = $this->value;
switch ($this->format) {
case self::FORMAT_TEXT:
foreach ($result as $key => &$val) {
if(is_array($val)) {
$val = implode(',', $val);
} else {
$val = (string)$val;
}
}
unset($val);
break;
case self::FORMAT_CURRENCY:
foreach ($result as $key => &$val) {
if (is_numeric($val)) {
$val = number_format($val, 2, ',', '.');
} else {
$val = (string)$val;
}
}
unset($val);
break;
case self::FORMAT_DECIMAL:
foreach ($result as $key => &$val) {
if (is_numeric($val)) {
$val = number_format($val, 2, ',', '');
} else {
$val = (string)$val;
}
}
unset($val);
break;
case self::FORMAT_HOURS:
foreach ($result as $key => &$val) {
if (is_numeric($val)) {
$min = $val * 60;
$hours = floor($min / 60);
$min %= 60;
$val = sprintf('%02dh %02dm', $hours, $min);
} else {
$val = (string)$val;
}
}
unset($val);
break;
default:
$result = [];
}
return $result;
}
/**
* @return array
*/
public function toArray()
{
if ($this->type === self::WIDGET_TYPE_CONTRAST || $this->type === self::WIDGET_TYPE_CONTRAST_BIG) {
$trend = $this->getContrastTrend();
$this->value['trend'] = $trend;
}
return [
'name' => $this->name,
'type' => $this->type,
'label' => $this->label,
'value' => $this->value,
'formattedValue' => $this->getFormattedValue(),
'valueUnit' => $this->valueUnit,
'format' => $this->format,
'context' => $this->context,
];
}
/**
* @return string
*/
private function getContrastTrend()
{
if (!isset($this->value['current'], $this->value['previous'])) {
return self::WIDGET_TREND_NONE;
}
if ($this->value['current'] > $this->value['previous']) {
return self::WIDGET_TREND_RISE;
}
if ($this->value['current'] < $this->value['previous']) {
return self::WIDGET_TREND_FALL;
}
return self::WIDGET_TREND_EQUAL;
}
}
@@ -0,0 +1,39 @@
<?php
namespace Xentral\Modules\Api\Dashboard;
use Xentral\Modules\Api\Resource\Result\AbstractResult;
final class WidgetResult extends AbstractResult
{
/**
* @param array $data
* @param array $pagination
*/
public function __construct(array $data, array $pagination = null)
{
$this->data = $data;
}
/**
* @return array
*/
public function getData()
{
$data = [];
foreach ($this->data as $item) {
/** @var WidgetData $item */
$data[] = $item->toArray();
}
return $data;
}
/**
* @param WidgetData $widgetData
*/
public function addData(WidgetData $widgetData)
{
$this->data[] = $widgetData;
}
}
@@ -0,0 +1,824 @@
<?php
namespace Xentral\Modules\Api\Engine;
use Xentral\Components\Http\Collection\ReadonlyParameterCollection;
use Xentral\Components\Http\Request;
use Xentral\Components\Http\Response;
use Xentral\Modules\Api\Auth\DigestAuth;
use Xentral\Modules\Api\Auth\PermissionGuard;
use Xentral\Modules\Api\Controller\Legacy\DefaultController;
use Xentral\Modules\Api\Controller\Legacy\GobNavConnectController;
use Xentral\Modules\Api\Controller\Legacy\MobileApiController;
use Xentral\Modules\Api\Controller\Legacy\OpenTransConnectController;
use Xentral\Modules\Api\Controller\Legacy\ShopimportController;
use Xentral\Modules\Api\Controller\Version1\AbstractController;
use Xentral\Modules\Api\Controller\Version1\ReportsController;
use Xentral\Modules\Api\Converter\Converter;
use Xentral\Modules\Api\Http\Exception\HttpException as ApiHttpException;
use Xentral\Modules\Api\Http\PathInfoDetector;
use Xentral\Modules\Api\Router\Router;
use Xentral\Modules\Api\Router\RouterResult;
class ApiApplication
{
/** @var ApiContainer $container */
protected $container;
/** @var Converter $converter */
protected $converter;
/** @var Request $request */
protected $request;
/** @var Response $response */
protected $response;
/** @var DigestAuth $auth */
protected $auth;
/** @var RouterResult|null $routerResult */
protected $routerResult;
/**
* @param ApiContainer $container
*/
public function __construct(ApiContainer $container)
{
$this->converter = $container->get('Converter');
$this->container = $container;
}
/**
* @param Request|null $request
*
* @return Response
*/
public function handle(Request $request = null)
{
$this->request = $request ?: Request::createFromGlobals();
$this->container->add('Request', $this->request);
$method = $this->request->getMethod();
$uri = $this->request->getPathInfo();
/**
* Failsafe; falls Webserver-Konfiguration Probleme bereitet.
* Dann kann der Pfad zur Ressource im Parameter "path" übergeben werden.
*
* @example /api/index.php?path=/v1/artikelkategorien&sort=bezeichnung
*/
if ($uri === '' && $this->request->get->has('path')) {
$uri = $this->request->get->get('path');
$queryParams = $this->request->get->all();
unset($queryParams['path']);
$this->request->get = new ReadonlyParameterCollection($queryParams);
}
try {
$this->auth = $this->get('DigestAuth');
$this->auth->checkLogin();
$this->response = $this->handleApiRequest($method, $uri);
} catch (ApiHttpException $e) {
$this->response = $this->createErrorResponse($e);
}
return $this->response;
}
/**
* @param string $serviceName
*
* @return object
*/
protected function get($serviceName)
{
return $this->container->get($serviceName);
}
/**
* @param string $method
* @param string $uri
*
* @return Response
*/
protected function handleApiRequest($method, $uri)
{
/** @var Router $apiRouter */
/** @var RouterResult $routeInfo */
$apiRouter = $this->get('ApiRouter');
/*
* Routen zusammenstellen
*/
$collection = $apiRouter->createCollection();
$collection->addRoute(['GET', 'POST', 'PUT', 'DELETE'], '/', ['Version1', null, 'Start', 'indexAction']);
$collection->addRoute(['GET', 'POST', 'PUT', 'DELETE'], '/v1', ['Version1', null, 'Start', 'indexAction']);
/*
* Dokumentation
*
* Routen greifen nur wenn Webserver falsch konfiguriert ist. Webserver sollte existierende Dateien direkt ausliefern.
* Zugriff auf Dokumentation erfordert API-Authentifizierung wenn Routen greifen.
*/
$collection->addRoute(['GET', 'POST', 'PUT', 'DELETE'], '/docs.html', ['Version1', null, 'Start', 'docsAction']);
$collection->addRoute(['GET', 'POST', 'PUT', 'DELETE'], '/assets/{assetfile}', ['Version1', null, 'Start', 'docsAssetsAction', 'handle_assets']);
/**
* Legacy-API
*
* @example POST /www/api/legacy/AdresseGet
*/
$collection->addRoute('POST', '/v1/gobnavconnect', ['Legacy', null, 'GobNavConnect', 'exampleAction', 'handle_navision']);
$collection->addRoute('POST', '/v1/gobnavconnect/', ['Legacy', null, 'GobNavConnect', 'exampleAction', 'handle_navision']);
$collection->addRoute('POST', '/{action}', ['Legacy', null, 'Default', 'postAction']);
$collection->addRoute('GET', '/{action}', ['Legacy', null, 'Default', 'postAction']);
$collection->addRoute('GET', '/v1/mobileapi/dashboard', ['Legacy', null, 'MobileApi', 'dashboardAction', 'mobile_app_communication']);
$collection->addRoute('GET','/opentrans/dispatchnotification/{id:\d+}',
['Legacy',null,'OpenTransConnect','readDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('GET','/opentrans/dispatchnotification/orderid/{orderid:\d+}',
['Legacy',null,'OpenTransConnect','readDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('GET','/opentrans/dispatchnotification/ordernumber/{ordernumber:\w+}',
['Legacy',null,'OpenTransConnect','readDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('GET','/opentrans/dispatchnotification/extorder/{extorder:\w+}',
['Legacy',null,'OpenTransConnect','readDispatchnotification', 'handle_opentrans']
);
/*$collection->addRoute('POST', '/opentrans/dispatchnotification',
['Legacy', null, 'OpenTransConnect', 'createDispatchnotification']
);*/
$collection->addRoute('PUT', '/opentrans/dispatchnotification/{id:\d+}',
['Legacy', null, 'OpenTransConnect', 'updateDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('PUT', '/opentrans/dispatchnotification/orderid/{orderid:\d+}',
['Legacy', null, 'OpenTransConnect', 'updateDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('PUT', '/opentrans/dispatchnotification/ordernumber/{ordernumber:\w+}',
['Legacy', null, 'OpenTransConnect', 'updateDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('PUT', '/opentrans/dispatchnotification/extorder/{extorder:\w+}',
['Legacy', null, 'OpenTransConnect', 'updateDispatchnotification', 'handle_opentrans']
);
$collection->addRoute('GET','/opentrans/order/{id:\d+}',['Legacy',null,'OpenTransConnect','readOrder', 'handle_opentrans']);
$collection->addRoute('GET','/opentrans/order/ordernumber/{ordernumber:\w+}',['Legacy',null,'OpenTransConnect','readOrder', 'handle_opentrans']);
$collection->addRoute('GET','/opentrans/order/extorder/{extorder:\w+}',['Legacy',null,'OpenTransConnect','readOrder', 'handle_opentrans']);
$collection->addRoute('POST', '/opentrans/order',
['Legacy', null, 'OpenTransConnect', 'createOrder', 'handle_opentrans']
);
$collection->addRoute('DELETE','/opentrans/order/{id:\d+}',['Legacy',null,'OpenTransConnect','deleteOrder', 'handle_opentrans']);
$collection->addRoute('DELETE','/opentrans/order/ordernumber/{ordernumber:\w+}',['Legacy',null,'OpenTransConnect','deleteOrder', 'handle_opentrans']);
$collection->addRoute('DELETE','/opentrans/order/extorder/{extorder:\w+}',['Legacy',null,'OpenTransConnect','deleteOrder', 'handle_opentrans']);
/*$collection->addRoute('PUT', '/opentrans/order/{id:\d+}',
['Legacy', null, 'OpenTransConnect', 'updateOrder']
);*/
$collection->addRoute('GET','/opentrans/invoice/{id:\d+}',['Legacy',null,'OpenTransConnect','readInvoice', 'handle_opentrans']);
$collection->addRoute('GET','/opentrans/invoice/orderid/{orderid:\d+}',['Legacy',null,'OpenTransConnect','readInvoice', 'handle_opentrans']);
$collection->addRoute('GET','/opentrans/invoice/ordernumber/{ordernumber:\w+}',['Legacy',null,'OpenTransConnect','readInvoice', 'handle_opentrans']);
$collection->addRoute('GET','/opentrans/invoice/extorder/{extorder:\w+}',['Legacy',null,'OpenTransConnect','readInvoice', 'handle_opentrans']);
$collection->addRoute('POST', '/shopimport/auth',
['Legacy', null, 'Shopimport', 'auth', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/syncstorage/{articlenumber:.+}',
['Legacy', null, 'Shopimport', 'syncStorage', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/articletoxentral/{articlenumber:.+}',
['Legacy', null, 'Shopimport', 'putArticleToXentral', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/articletoshop/{articlenumber:.+}',
['Legacy', null, 'Shopimport', 'putArticleToShop', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/ordertoxentral/{ordernumber:.+}',
['Legacy', null, 'Shopimport', 'putOrderToXentral', 'communicate_with_shop']
);
$collection->addRoute('GET', '/shopimport/articlesyncstate',
['Legacy', null, 'Shopimport', 'getArticleSyncState', 'communicate_with_shop']
);
$collection->addRoute('GET', '/shopimport/statistics',
['Legacy', null, 'Shopimport', 'getStatistics', 'communicate_with_shop']
);
$collection->addRoute('GET', '/shopimport/modulelinks',
['Legacy', null, 'Shopimport', 'getModulelinks', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/disconnect',
['Legacy', null, 'Shopimport', 'postDisconnect', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/reconnect',
['Legacy', null, 'Shopimport', 'postReconnect', 'communicate_with_shop']
);
$collection->addRoute('GET', '/shopimport/status',
['Legacy', null, 'Shopimport', 'getStatus', 'communicate_with_shop']
);
$collection->addRoute('POST', '/shopimport/refund',
['Legacy', null, 'Shopimport', 'postRefund', 'communicate_with_shop']
);
/**
* REST-API (v1)
*
* @example GET /www/api/v1/adressen
*/
// Abo-Artikel
$collection->addRoute('POST', '/v1/aboartikel',
['Version1', 'ArticleSubscription', 'ArticleSubscription', 'createAction', 'create_subscription'] // Achtung: Eigener Controller
);
$collection->addRoute('GET', '/v1/aboartikel',
['Version1', 'ArticleSubscription', 'Generic', 'listAction', 'list_subscriptions']
);
$collection->addRoute('GET', '/v1/aboartikel/{id:\d+}',
['Version1', 'ArticleSubscription', 'Generic', 'readAction', 'view_subscription']
);
$collection->addRoute('PUT', '/v1/aboartikel/{id:\d+}',
['Version1', 'ArticleSubscription', 'ArticleSubscription', 'updateAction', 'edit_subscription'] // Achtung: Eigener Controller
);
$collection->addRoute('DELETE', '/v1/aboartikel/{id:\d+}',
['Version1', 'ArticleSubscription', 'Generic', 'deleteAction', 'delete_subscription']
);
// Abo-Artikel-Gruppen
$collection->addRoute('POST', '/v1/abogruppen',
['Version1', 'ArticleSubscriptionGroup', 'Generic', 'createAction', 'create_subscription_group']
);
$collection->addRoute('GET', '/v1/abogruppen',
['Version1', 'ArticleSubscriptionGroup', 'Generic', 'listAction', 'list_subscription_groups']
);
$collection->addRoute('GET', '/v1/abogruppen/{id:\d+}',
['Version1', 'ArticleSubscriptionGroup', 'Generic', 'readAction', 'view_subscription_group']
);
$collection->addRoute('PUT', '/v1/abogruppen/{id:\d+}',
['Version1', 'ArticleSubscriptionGroup', 'Generic', 'updateAction', 'edit_subscription_group']
);
// Adressen
/** @see AddressController::createAction */
$collection->addRoute('POST', '/v1/adressen', ['Version1', null, 'Address', 'createAction', 'create_address']);
/** @see AddressController::listAction */
$collection->addRoute('GET', '/v1/adressen', ['Version1', null, 'Address', 'listAction', 'list_addresses']);
/** @see AddressController::readAction */
$collection->addRoute('GET', '/v1/adressen/{id:\d+}', ['Version1', null, 'Address', 'readAction', 'view_address']);
/** @see AddressController::updateAction */
$collection->addRoute('PUT', '/v1/adressen/{id:\d+}', ['Version1', null, 'Address', 'updateAction', 'edit_address']);
// Addressen
/*$collection->addRoute('POST', '/v2/adressen',
array('Version1', 'Address', 'Generic', 'createAction')
);*/
$collection->addRoute('GET', '/v2/adressen',
['Version1', 'Address', 'Generic', 'listAction','list_addresses']
);
$collection->addRoute('GET', '/v2/adressen/{id:\d+}',
['Version1', 'Address', 'Generic', 'readAction','view_address']
);
/*$collection->addRoute('PUT', '/v2/adressen/{id:\d+}',
array('Version1', 'Address', 'Generic', 'updateAction')
);*/
// Addressen-Typ (herr, frau, firma)
$collection->addRoute('POST', '/v1/adresstyp',
['Version1', 'AddressType', 'Generic', 'createAction', 'create_address_type']
);
$collection->addRoute('GET', '/v1/adresstyp',
['Version1', 'AddressType', 'Generic', 'listAction', 'list_address_types']
);
$collection->addRoute('GET', '/v1/adresstyp/{id:\d+}',
['Version1', 'AddressType', 'Generic', 'readAction', 'view_address_type']
);
$collection->addRoute('PUT', '/v1/adresstyp/{id:\d+}',
['Version1', 'AddressType', 'Generic', 'updateAction', 'edit_address_type']
);
// Artikel
/*$collection->addRoute('POST', '/v1/artikel',
array('Version1', 'Article', 'Generic', 'createAction')
);*/
$collection->addRoute('GET', '/v1/artikel',
['Version1', 'Article', 'Generic', 'listAction', 'list_articles']
);
$collection->addRoute('GET', '/v1/artikel/{id:\d+}',
['Version1', 'Article', 'Generic', 'readAction', 'view_article']
);
/*$collection->addRoute('PUT', '/v1/artikel/{id:\d+}',
array('Version1', 'Article', 'Generic', 'updateAction')
);*/
// Eigenschaften
$collection->addRoute('GET', '/v1/eigenschaften',
['Version1', 'Property', 'Generic', 'listAction', 'list_property']
);
$collection->addRoute('GET', '/v1/eigenschaften/{id:\d+}',
['Version1', 'Property', 'Generic', 'readAction', 'view_property']
);
$collection->addRoute('DELETE', '/v1/eigenschaften/{id:\d+}',
['Version1', 'Property', 'Generic', 'deleteAction', 'delete_property']
);
$collection->addRoute('PUT', '/v1/eigenschaften/{id:\d+}',
['Version1', 'Property', 'Generic', 'updateAction', 'edit_property']
);
$collection->addRoute('POST', '/v1/eigenschaften',
['Version1', 'Property', 'Generic', 'createAction', 'create_property']
);
// Eigenschaftenwerte
$collection->addRoute('GET', '/v1/eigenschaftenwerte',
['Version1', 'PropertyValue', 'Generic', 'listAction', 'list_property_value']
);
$collection->addRoute('GET', '/v1/eigenschaftenwerte/{id:\d+}',
['Version1', 'PropertyValue', 'Generic', 'readAction', 'view_property_value']
);
$collection->addRoute('DELETE', '/v1/eigenschaftenwerte/{id:\d+}',
['Version1', 'PropertyValue', 'Generic', 'deleteAction', 'delete_property_value']
);
$collection->addRoute('PUT', '/v1/eigenschaftenwerte/{id:\d+}',
['Version1', 'PropertyValue', 'Generic', 'updateAction', 'edit_property_value']
);
$collection->addRoute('POST', '/v1/eigenschaftenwerte',
['Version1', 'PropertyValue', 'Generic', 'createAction', 'create_property_value']
);
//
// BELEGE
//
// /v1/belege => Nothing here
$collection->addRoute('GET', '/v1/belege', ['Version1', null, 'Start', 'indexAction']);
// Angebote
$collection->addRoute('GET', '/v1/belege/angebote',
['Version1', 'DocumentOffer', 'Generic', 'listAction', 'list_quotes']
);
$collection->addRoute('GET', '/v1/belege/angebote/{id:\d+}',
['Version1', 'DocumentOffer', 'Generic', 'readAction', 'view_quote']
);
// Aufträge
$collection->addRoute('GET', '/v1/belege/auftraege',
['Version1', 'DocumentSalesOrder', 'Generic', 'listAction', 'list_orders']
);
$collection->addRoute('GET', '/v1/belege/auftraege/{id:\d+}',
['Version1', 'DocumentSalesOrder', 'Generic', 'readAction', 'view_order']
);
// Lieferscheine
$collection->addRoute('GET', '/v1/belege/lieferscheine',
['Version1', 'DocumentDeliveryNote', 'Generic', 'listAction', 'list_delivery_notes']
);
$collection->addRoute('GET', '/v1/belege/lieferscheine/{id:\d+}',
['Version1', 'DocumentDeliveryNote', 'Generic', 'readAction', 'view_delivery_note']
);
// Rechnungen
$collection->addRoute('GET', '/v1/belege/rechnungen',
['Version1', 'DocumentInvoice', 'Generic', 'listAction', 'list_invoices']
);
$collection->addRoute('GET', '/v1/belege/rechnungen/{id:\d+}',
['Version1', 'DocumentInvoice', 'Generic', 'readAction', 'view_invoice']
);
$collection->addRoute('DELETE', '/v1/belege/rechnungen/{id:\d+}',
['Version1', 'DocumentInvoice', 'Generic', 'deleteAction', 'delete_invoice']
);
// Gutschriften/Stornorechnungen
$collection->addRoute('GET', '/v1/belege/gutschriften',
['Version1', 'DocumentCreditNote', 'Generic', 'listAction', 'list_credit_memos']
);
$collection->addRoute('GET', '/v1/belege/gutschriften/{id:\d+}',
['Version1', 'DocumentCreditNote', 'Generic', 'readAction', 'view_credit_memo']
);
//
// ENDE: BELEGE
//
$collection->addRoute('GET', '/v1/reports/{id:\d+}/download',
['Version1', null, 'Reports', 'downloadAction', 'view_report']
);
// Dateien
$collection->addRoute('POST', '/v1/dateien',
['Version1', 'File', 'File', 'createAction', 'create_file']
);
$collection->addRoute('GET', '/v1/dateien',
['Version1', 'File', 'File', 'listAction', 'list_files']
);
$collection->addRoute('GET', '/v1/dateien/{id:\d+}',
['Version1', 'File', 'File', 'readAction', 'view_file']
);
$collection->addRoute('GET', '/v1/dateien/{id:\d+}/download',
['Version1', 'File', 'File', 'downloadAction', 'view_file']
);
$collection->addRoute('GET', '/v1/dateien/{id:\d+}/base64',
['Version1', 'File', 'File', 'base64Action', 'view_file']
);
/*$collection->addRoute('PUT', '/v1/dateien/{id:\d+}',
array('Version1', 'File', 'File', 'updateAction')
);*/
// Dokumenten-Scanner (DocScan)
$collection->addRoute('POST', '/v1/docscan',
['Version1', 'DocumentScanner', 'DocumentScanner', 'createAction', 'create_scanned_document']
);
$collection->addRoute('GET', '/v1/docscan',
['Version1', 'DocumentScanner', 'DocumentScanner', 'listAction', 'list_scanned_documents']
);
$collection->addRoute('GET', '/v1/docscan/{id:\d+}',
['Version1', 'DocumentScanner', 'DocumentScanner', 'readAction', 'view_scanned_document']
);
// Artikelkategorien
$collection->addRoute('POST', '/v1/artikelkategorien',
['Version1', 'ArticleCategory', 'Generic', 'createAction', 'create_article_category']
);
$collection->addRoute('GET', '/v1/artikelkategorien',
['Version1', 'ArticleCategory', 'Generic', 'listAction', 'list_article_categories']
);
$collection->addRoute('GET', '/v1/artikelkategorien/{id:\d+}',
['Version1', 'ArticleCategory', 'Generic', 'readAction', 'view_article_category']
);
$collection->addRoute('PUT', '/v1/artikelkategorien/{id:\d+}',
['Version1', 'ArticleCategory', 'Generic', 'updateAction', 'edit_article_category']
);
// Gruppen
$collection->addRoute('POST', '/v1/gruppen',
['Version1', 'Group', 'Generic', 'createAction', 'create_group']
);
$collection->addRoute('GET', '/v1/gruppen',
['Version1', 'Group', 'Generic', 'listAction', 'list_groups']
);
$collection->addRoute('GET', '/v1/gruppen/{id:\d+}',
['Version1', 'Group', 'Generic', 'readAction', 'view_group']
);
$collection->addRoute('PUT', '/v1/gruppen/{id:\d+}',
['Version1', 'Group', 'Generic', 'updateAction', 'edit_group']
);
//CrmDokumente
$collection->addRoute('POST', '/v1/crmdokumente',
['Version1', 'CrmDocument', 'Generic', 'createAction', 'create_crm_document']
);
$collection->addRoute('GET', '/v1/crmdokumente',
['Version1', 'CrmDocument', 'Generic', 'listAction', 'list_crm_documents']
);
$collection->addRoute('GET', '/v1/crmdokumente/{id:\d+}',
['Version1', 'CrmDocument', 'Generic', 'readAction', 'view_crm_document']
);
$collection->addRoute('PUT', '/v1/crmdokumente/{id:\d+}',
['Version1', 'CrmDocument', 'Generic', 'updateAction', 'edit_crm_document']
);
$collection->addRoute('DELETE', '/v1/crmdokumente/{id:\d+}',
['Version1', 'CrmDocument', 'Generic', 'deleteAction', 'delete_crm_document']
);
// Länder
$collection->addRoute('POST', '/v1/laender',
['Version1', 'Country', 'Generic', 'createAction', 'create_country']
);
$collection->addRoute('GET', '/v1/laender',
['Version1', 'Country', 'Generic', 'listAction', 'list_countries']
);
$collection->addRoute('GET', '/v1/laender/{id:\d+}',
['Version1', 'Country', 'Generic', 'readAction', 'view_country']
);
$collection->addRoute('PUT', '/v1/laender/{id:\d+}',
['Version1', 'Country', 'Generic', 'updateAction', 'edit_country']
);
// Lager-Charge
$collection->addRoute('GET', '/v1/lagercharge',
['Version1', 'StorageBatch', 'Generic', 'listAction', 'view_storage_batch']
);
// Lager-Mindesthaltbarkeitsdatum (MHD)
$collection->addRoute('GET', '/v1/lagermhd',
['Version1', 'StorageBestBeforeDate', 'Generic', 'listAction', 'view_storage_best_before']
);
// Lieferadressen
$collection->addRoute('POST', '/v1/lieferadressen',
['Version1', 'DeliveryAddress', 'Generic', 'createAction', 'create_delivery_address']
);
$collection->addRoute('GET', '/v1/lieferadressen',
['Version1', 'DeliveryAddress', 'Generic', 'listAction', 'list_delivery_addresses']
);
$collection->addRoute('GET', '/v1/lieferadressen/{id:\d+}',
['Version1', 'DeliveryAddress', 'Generic', 'readAction', 'view_delivery_address']
);
$collection->addRoute('PUT', '/v1/lieferadressen/{id:\d+}',
['Version1', 'DeliveryAddress', 'Generic', 'updateAction', 'edit_delivery_address']
);
$collection->addRoute('DELETE', '/v1/lieferadressen/{id:\d+}',
['Version1', 'DeliveryAddress', 'Generic', 'deleteAction', 'delete_delivery_address']
);
// Steuersätze
$collection->addRoute('POST', '/v1/steuersaetze',
['Version1', 'TaxRate', 'Generic', 'createAction', 'create_tax_rate']
);
$collection->addRoute('GET', '/v1/steuersaetze',
['Version1', 'TaxRate', 'Generic', 'listAction', 'list_tax_rates']
);
$collection->addRoute('GET', '/v1/steuersaetze/{id:\d+}',
['Version1', 'TaxRate', 'Generic', 'readAction', 'view_tax_rate']
);
$collection->addRoute('PUT', '/v1/steuersaetze/{id:\d+}',
['Version1', 'TaxRate', 'Generic', 'updateAction', 'edit_tax_rate']
);
// Versandarten
$collection->addRoute('POST', '/v1/versandarten',
['Version1', 'ShippingMethod', 'Generic', 'createAction', 'create_shipping_method']
);
$collection->addRoute('GET', '/v1/versandarten',
['Version1', 'ShippingMethod', 'Generic', 'listAction', 'list_shipping_methods']
);
$collection->addRoute('GET', '/v1/versandarten/{id:\d+}',
['Version1', 'ShippingMethod', 'Generic', 'readAction', 'view_shipping_method']
);
$collection->addRoute('PUT', '/v1/versandarten/{id:\d+}',
['Version1', 'ShippingMethod', 'Generic', 'updateAction', 'edit_shipping_method']
);
// Wiedervorlagen
$collection->addRoute('POST', '/v1/wiedervorlagen',
['Version1', 'Resubmission', 'Generic', 'createAction', 'create_resubmission']
);
$collection->addRoute('GET', '/v1/wiedervorlagen',
['Version1', 'Resubmission', 'Generic', 'listAction', 'list_resubmissions']
);
$collection->addRoute('GET', '/v1/wiedervorlagen/{id:\d+}',
['Version1', 'Resubmission', 'Generic', 'readAction', 'view_resubmission']
);
$collection->addRoute('PUT', '/v1/wiedervorlagen/{id:\d+}',
['Version1', 'Resubmission', 'Generic', 'updateAction', 'edit_resubmission']
);
// Zahlungsweisen
$collection->addRoute('POST', '/v1/zahlungsweisen',
['Version1', 'PaymentMethod', 'Generic', 'createAction', 'create_payment_method']
);
$collection->addRoute('GET', '/v1/zahlungsweisen',
['Version1', 'PaymentMethod', 'Generic', 'listAction', 'list_payment_methods']
);
$collection->addRoute('GET', '/v1/zahlungsweisen/{id:\d+}',
['Version1', 'PaymentMethod', 'Generic', 'readAction', 'view_payment_method']
);
$collection->addRoute('PUT', '/v1/zahlungsweisen/{id:\d+}',
['Version1', 'PaymentMethod', 'Generic', 'updateAction', 'edit_payment_method']
);
// Trackingnummern
$collection->addRoute('POST', '/v1/trackingnummern',
['Version1', 'TrackingNumber', 'TrackingNumber', 'createAction', 'create_tracking_number'] // Achtung: Eigener Controller
);
$collection->addRoute('GET', '/v1/trackingnummern',
['Version1', 'TrackingNumber', 'Generic', 'listAction', 'list_tracking_numbers']
);
$collection->addRoute('GET', '/v1/trackingnummern/{id:\d+}',
['Version1', 'TrackingNumber', 'Generic', 'readAction', 'view_tracking_number']
);
$collection->addRoute('PUT', '/v1/trackingnummern/{id:\d+}',
['Version1', 'TrackingNumber', 'TrackingNumber', 'updateAction', 'edit_tracking_number'] // Achtung: Eigener Controller
);
// @todo Aufträge
//$collection->addRoute('GET', '/v1/auftraege', array('Version1', 'Order', 'GetAllOrders'));
//$collection->addRoute('GET', '/v1/auftraege/{id:\d+}', array('Version1', 'Order', 'GetOrderById'));
//$collection->addRoute('POST', '/v1/auftraege', array('Version1', 'Order', 'CreateOrder'));
/*
* Route ermitteln
*/
$apiRouter->setCollection($collection);
$routeInfo = $apiRouter->dispatch($method, $uri);
$this->routerResult = $routeInfo;
/*
* Check permission
*/
if($routeInfo->getPermission() !== null){
$guard = New PermissionGuard($this->container->get('Database'), $this->auth->getApiAccountId());
$guard->check($routeInfo->getPermission());
}
/*
* Controller dispatchen
*/
$this->request->attributes->add($routeInfo->getRouterParams());
// Legacy-API-Controller
if ($routeInfo->getControllerClass() === DefaultController::class) {
$controller = new DefaultController(
$this->container->get('LegacyApi'),
$this->container->get('Request'),
$this->container->get('DigestAuth')->getApiAccountId()
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
if ($routeInfo->getControllerClass() === GobNavConnectController::class) {
$controller = new GobNavConnectController(
$this->container->get('LegacyApplication'),
$this->container->get('Request')
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
if ($routeInfo->getControllerClass() === OpenTransConnectController::class) {
$controller = new OpenTransConnectController(
$this->container->get('LegacyApplication'),
$this->container->get('OpenTransConverter'),
$this->container->get('Request'),
$this->container->get('DigestAuth')->getApiAccountId()
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
if ($routeInfo->getControllerClass() === ShopimportController::class) {
$controller = new ShopimportController(
$this->container->get('LegacyApplication'),
$this->container->get('Request'),
$this->container->get('DigestAuth')->getApiAccountId()
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
if ($routeInfo->getControllerClass() === MobileApiController::class) {
$controller = new MobileApiController(
$this->container->get('LegacyApplication'),
$this->container->get('Converter'),
$this->container->get('Database'),
$this->container->get('Request')
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
if ($routeInfo->getControllerClass() === ReportsController::class) {
$controller = new ReportsController(
$this->container->get('LegacyApplication'),
$this->container->get('Request'),
$this->container->get('DigestAuth')->getApiAccountId()
);
$action = $routeInfo->getControllerAction();
return $controller->$action();
}
/** @var AbstractController $controller */
$controller = $this->container->getApiController(
$routeInfo->getControllerClass()
);
$controller->setResourceClass($routeInfo->getResourceClass());
return $controller->dispatch($routeInfo->getControllerAction());
}
/**
* @param int $errorCode
*
* @return string
*/
private function buildErrorLink($errorCode)
{
$pathInfo = $this->request->getPathInfo();
$fullUrl = $this->request->getFullUrl();
$apiUrl = $fullUrl;
if ($pos = strrpos($fullUrl, $pathInfo)) {
$apiUrl = substr($fullUrl, 0, $pos);
}
if ($pos = strrpos($apiUrl, '/index.php')) {
$apiUrl = substr($apiUrl, 0, $pos);
}
return $apiUrl . '/docs.html#error-' . $errorCode;
}
/**
* @param ApiHttpException $e
*
* @return Response
*/
private function createErrorResponse($e)
{
// Fehler-Informationen zusammenbauen
$data = [
'error' => [
'code' => $e->getCode(),
'http_code' => $e->getStatusCode(),
'message' => $e->getMessage(),
'href' => $this->buildErrorLink($e->getCode()),
],
];
if ($e->hasErrors()) {
// Validierungsfehler anhängen
$data['error']['details'] = $e->getErrors();
}
if ($this->isDebugModeActive()) {
$data['debug'] = [];
// Router-Informationen anhängen
$data['debug']['router'] = $this->routerResult !== null ? $this->routerResult->toArray() : false;
// Request-Informationen anhängen
$pathInfoDetector = new PathInfoDetector($this->request);
$pathInfo = $pathInfoDetector->detect();
$data['debug']['request'] = [
'isFailsafe' => $this->request->isFailsafeUri(),
'pathInfo' => [
'actual' => (string)$this->request->server->get('PATH_INFO'),
'expected' => $pathInfo,
],
'info' => [
'method' => $this->request->getMethod(),
'requestUri' => $this->request->getRequestUri(),
'fullUri' => $this->request->getFullUri(true),
],
'serverParams' => $this->request->server->all(),
'header' => $this->request->header->all(),
'getParams' => $this->request->get->all(),
'postParams' => $this->request->post->all(),
'additionalParams' => $this->request->attributes->all(),
];
}
// XML oder JSON
if (in_array('text/html', $this->request->getAcceptableContentTypes(), true)) {
// Client ist vermutlich ein Browser > JSON ausliefern
$json = $this->converter->arrayToJson($data);
$response = new Response(
$json,
$e->getStatusCode(),
['Content-Type' => 'application/json; charset=UTF-8']
);
} else {
if (in_array('application/xml', $this->request->getAcceptableContentTypes(), true)) {
$xml = $this->converter->arrayToXml($data['error'], 'error');
$response = new Response(
$xml,
$e->getStatusCode(),
['Content-Type' => 'application/xml; charset=UTF-8']
);
} else {
$json = $this->converter->arrayToJson($data);
$response = new Response(
$json,
$e->getStatusCode(),
['Content-Type' => 'application/json; charset=UTF-8']
);
}
}
// Login-Header mitschicken
$response->setHeader('WWW-Authenticate', $this->auth->generateAuthenticationString());
return $response;
}
/**
* @return bool
*/
private function isDebugModeActive()
{
return defined('DEBUG_MODE') && (int)DEBUG_MODE === 1;
}
}
+301
View File
@@ -0,0 +1,301 @@
<?php
namespace Xentral\Modules\Api\Engine;
use ReflectionClass;
use Xentral\Components\Database\Database;
use Xentral\Components\Http\Request;
use Xentral\Modules\Api\Auth\DigestAuth;
use Xentral\Modules\Api\Controller\Version1\AbstractController;
use Xentral\Modules\Api\Converter\Converter;
use Xentral\Modules\Api\Converter\JsonConverter;
use Xentral\Modules\Api\Converter\OpenTransConverter;
use Xentral\Modules\Api\Converter\XmlConverter;
use Xentral\Modules\Api\LegacyBridge\LegacyApiLazyProxy;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
use Xentral\Modules\Api\Resource\AbstractResource as AbstractApiResource;
use Xentral\Modules\Api\Resource\ResourceManager;
use Xentral\Modules\Api\Router\Router as ApiRouter;
use Xentral\Modules\Api\Validator\Rule\BooleanRule;
use Xentral\Modules\Api\Validator\Rule\DbValueRule;
use Xentral\Modules\Api\Validator\Rule\DecimalRule;
use Xentral\Modules\Api\Validator\Rule\LengthRule;
use Xentral\Modules\Api\Validator\Rule\LowerRule;
use Xentral\Modules\Api\Validator\Rule\NotPresentRule;
use Xentral\Modules\Api\Validator\Rule\TimeRule;
use Xentral\Modules\Api\Validator\Rule\UniqueRule;
use Xentral\Modules\Api\Validator\Rule\UpperRule;
use Xentral\Modules\Api\Validator\Validator;
final class ApiContainer
{
/** @var array $services Speicher für Service-Instanzen */
private $services = array();
/**
* Service-Instanz von außen injizieren
*
* @param string $name
* @param object $instance
*/
public function add($name, $instance)
{
if (isset($this->services['name'])) {
throw new \RuntimeException(
sprintf('Service "%s" is already registered.', $name)
);
}
$this->services[$name] = $instance;
}
/**
* @param string $name Service-Name oder FQCN
*
* @return object
*/
public function get($name)
{
if ($this->has($name)) {
return $this->services[$name];
}
return $this->createService($name);
}
/**
* @param string $name
*
* @return bool
*/
public function has($name)
{
return isset($this->services[$name]);
}
/**
* @param string $name
*
* @return object
*/
private function createService($name)
{
$createServiceMethod = 'create' . $name . 'Service';
if (!method_exists($this, $createServiceMethod)) {
throw new \RuntimeException(
sprintf(
'Service "%s" could not be created. Container method "%s" is missing.',
$name, $createServiceMethod
)
);
}
$this->services[$name] = $this->$createServiceMethod();
return $this->services[$name];
}
/**
* @param string $contollerClass
* @param Request|null $request
*
* @return AbstractController
*/
public function getApiController($contollerClass, Request $request = null)
{
// @todo
/*$interfaces = class_implements($contollerClass, true);
if (!in_array('Xentral\Modules\Api\Version1\Controller\ControllerInterface', $interfaces, true)) {
throw new \CountryInvalidArgumentException(sprintf(
'"%s" must implement "%s"',
$contollerClass, 'Xentral\Modules\Api\Version1\Controller\ControllerInterface'
));
}*/
$parents = class_parents($contollerClass, true);
if (!in_array(AbstractController::class, $parents, true)) {
throw new \InvalidArgumentException(sprintf(
'"%s" must implement "%s"',
$contollerClass, AbstractController::class
));
}
// Controller nicht sharen!
// Resourcen können sich Controller teilen
return new $contollerClass(
$this->get('LegacyApi'),
$this->get('Database'),
$this->get('Converter'),
$request ?: $this->get('Request'),
$this->get('ResourceManager')
);
}
/**
* @param string $resourceClass
*
* @return AbstractApiResource
*
* @throws \ReflectionException
*/
public function getApiResource($resourceClass)
{
$resourceReflection = new ReflectionClass($resourceClass);
$resourceName = $resourceReflection->getShortName();
if ($this->has($resourceName)) {
return $this->services[$resourceName];
}
$parents = class_parents($resourceClass, true);
if (!in_array(AbstractApiResource::class, $parents, true)) {
throw new \InvalidArgumentException(sprintf(
'"%s" must extend "%s"',
$resourceClass, AbstractApiResource::class
));
}
// @todo
/*$interfaces = class_implements($resourceClass, false);
if (!in_array(ApiResourceInterface::class, $interfaces, true)) {
throw new \CountryInvalidArgumentException(sprintf(
'"%s" must implement "%s"',
$resourceClass, ApiResourceInterface::class
));
}*/
// Resource erzeugen
$resource = new $resourceClass(
$this->get('Database'),
$this->get('Validator')
);
// Resource sharen
$this->add($resourceName, $resource);
return $resource;
}
/**
* @return ResourceManager
*/
private function createResourceManagerService()
{
return new ResourceManager(
$this->get('Database'),
$this->get('Validator'),
$this->get('LegacyApi')
);
}
/**
* @return LegacyApiLazyProxy
*/
private function createLegacyApiService()
{
return new LegacyApiLazyProxy();
}
/**
* @return LegacyApplication
*/
private function createLegacyApplicationService()
{
return new LegacyApplication();
}
/**
* @return DigestAuth
*/
private function createDigestAuthService()
{
return new DigestAuth($this->get('Database'), $this->get('Request'));
}
/**
* @return Database
*/
private function createDatabaseService()
{
/** @var LegacyApplication $legacyApp */
$legacyApp = $this->get('LegacyApplication');
return $legacyApp->Container->get('Database');
}
/**
* @return Validator
*/
private function createValidatorService()
{
$validator = new Validator();
$validator->addValidator('db_value', new DbValueRule($this->get('Database')));
$validator->addValidator('boolean', new BooleanRule());
$validator->addValidator('decimal', new DecimalRule());
$validator->addValidator('length', new LengthRule());
$validator->addValidator('lower', new LowerRule());
$validator->addValidator('not_present', new NotPresentRule());
$validator->addValidator('time', new TimeRule());
$validator->addValidator('unique', new UniqueRule($this->get('Database')));
$validator->addValidator('upper', new UpperRule());
return $validator;
}
/**
* @return Request
*/
private function createRequestService()
{
/** @var LegacyApplication $legacyApp */
$legacyApp = $this->get('LegacyApplication');
return $legacyApp->Container->get('Request');
}
/**
* @return ApiRouter
*/
private function createApiRouterService()
{
return new ApiRouter();
}
/**
* @return Converter
*/
private function createConverterService()
{
return new Converter($this->get('XmlConverter'), $this->get('JsonConverter'));
}
/**
* @return OpenTransConverter
*/
private function createOpenTransConverterService()
{
return new OpenTransConverter();
}
/**
* @return XmlConverter
*/
private function createXmlConverterService()
{
return new XmlConverter();
}
/**
* @return JsonConverter
*/
private function createJsonConverterService()
{
return new JsonConverter();
}
public function __clone()
{
}
public function __wakeup()
{
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Api\Engine;
use Xentral\Components\Http\Request;
use Xentral\Components\Util\StringUtil;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
final class ApiUrlGenerator
{
/** @var Request $request */
private $request;
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* @param string $endpointUrl Beispiel: /v1/adressen
* @param array $queryParams Query-Parameter (GET-Parameter)
*
* @throws InvalidArgumentException
*
* @return string
*/
public function generate(string $endpointUrl, array $queryParams = []): string
{
if (empty($endpointUrl)) {
throw new InvalidArgumentException('Endpoint URL can not be empty.');
}
if (!StringUtil::startsWith($endpointUrl, '/')) {
throw new InvalidArgumentException('Endpoint URL must start with a slash character.');
}
if (isset($queryParams['path'])) {
throw new InvalidArgumentException('Parameter "path" is reserved.');
}
// 1. Normal: http://locahost/xentral-20.3/www/api/v1/docscan?foo=bar
// 2. Alternative: http://locahost/xentral-20.3/www/api/index.php/v1/docscan?foo=bar
// 3. Failsafe: http://locahost/xentral-20.3/www/api/index.php?path=/v1/docscan&foo=bar
// => Base-URI in allen Fällen: http://locahost/xentral-20.3/www/api/
$baseUrl = $this->request->getUrlForPath('/');
$baseUrl = substr($baseUrl, 0, -1); // Remove last slash
// Query-Parameter zusammenbauen
$queryString = http_build_query($queryParams, '', '&');
if ($this->isFailsafeMode()) {
$fullUrl = $baseUrl . '/index.php?path=' . $endpointUrl;
if (!empty($queryParams)) {
$fullUrl .= '&' . $queryString;
}
return $fullUrl;
}
if ($this->isAlternateMode()) {
$fullUrl = $baseUrl . '/index.php' . $endpointUrl;
} else {
$fullUrl = $baseUrl . $endpointUrl;
}
if (!empty($queryParams)) {
$fullUrl .= '?' . $queryString;
}
return $fullUrl;
}
/**
* Failsafe URL: /www/api/index.php?path=/v1/adressen&foo=bar
*
* @return bool
*/
private function isFailsafeMode(): bool
{
$pathInfo = $this->request->getPathInfo();
if (!empty($pathInfo)) {
return false;
}
$queryString = $this->request->getServer('QUERY_STRING');
parse_str($queryString, $queryParts);
return isset($queryParts['path']);
}
/**
* Alternative URL-Variante: /www/api/index.php/v1/adressen?foo=bar
*
* @return bool
*/
private function isAlternateMode(): bool
{
$pathInfo = $this->request->getPathInfo();
if (empty($pathInfo)) {
return false;
}
$requestUri = $this->request->getRequestUri();
$apiRootPos = strpos($requestUri, 'api/index.php');
return is_int($apiRootPos) && $apiRootPos > 0;
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace Xentral\Modules\Api\Error;
class ApiError
{
/*
* Auth-Fehler
*/
const CODE_UNAUTHORIZED = 7411; // (Erster) Besuch ohne Authorization-Header
const CODE_DIGEST_HEADER_INCOMPLETE = 7412; // Digest-Header unvollständig; benötigte Teile fehlen
const CODE_API_ACCOUNT_MISSING = 7413; // Es ist überhaupt kein API-Account angelegt oder aktiv
const CODE_API_ACCOUNT_INVALID = 7414; // Verwendeter API-Account ist nicht (mehr?) gültig oder aktiv
//const CODE_DIGEST_VALIDDATION_FAILED = 7415; // Prüfung ist fehlgeschlagen // Momentan nicht möglich da es mehrere Accounts mit dem gleichen Benutzernamen geben kann.
const CODE_DIGEST_NONCE_INVALID = 7416; // Serverkey ist nicht vorhanden, oder schon länger abgelaufen (daher gelöscht)
const CODE_DIGEST_NONCE_EXPIRED = 7417; // Serverkey ist abgelaufen
const CODE_AUTH_USERNAME_EMPTY = 7418; // Benutzername wurde leer übergeben
const CODE_AUTH_TYPE_NOT_ALLOWED = 7419; // Authorization-Header vorhanden, aber kein Digest
const CODE_DIGEST_NC_NOT_MATCHING = 7420; // NonceCount (nc) passt nicht
const CODE_API_ACCOUNT_PERMISSION_MISSING = 7421; // Api account has not the correct permissions
/*
* Routing-Fehler
*/
const CODE_ROUTE_NOT_FOUND = 7431;
const CODE_METHOD_NOT_ALLOWED = 7432;
const CODE_API_METHOD_NOT_FOUND = 7433;
/*
* Endpoint-Fehler
*/
const CODE_BAD_REQUEST = 7451; // API-Benutzer hat beim Request einen Fehler gemacht; Diesen Fehler nur verwenden
// wenns nicht anders geht. Besser einen konkreteren Code verwenden bzw. anlegen. Benutzer kann mit diesem Fehler
// nichts anfangen.
const CODE_RESOURCE_NOT_FOUND = 7452; // API-Resource wurde nicht gefunden; zb wenn gesuchte ID nicht existiert
const CODE_VALIDATION_ERROR = 7453; // Fehler bei der Validierung von Eingabedaten (nur bei PUT oder POST)
const CODE_INVALID_ARGUMENT = 7454; // Argument (z.B. Suchparameter) enthält ungültige Werte
const CODE_MALFORMED_REQUEST_BODY = 7455; // JSON oder XML konnte nicht dekodiert werden
const CODE_CONTENT_TYPE_NOT_SUPPORTED = 7456; // Request-Body wurde mit unbekanntem Content-Type abgeschickt
/*
* Webserver falsch konfiguriert (Vermutlich Nginx oder FastCGI falsch konfiguriert)
* @see https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/
*/
const CODE_WEBSERVER_MISCONFIGURED = 7481; // Fehlkonfiguration im Webserver (nicht genauer beschrieben). Diesen
// Fehler-Code nicht verwenden! Besser einen konkreteren Fehlercode verwenden bzw. hinzufügen.
const CODE_WEBSERVER_PATHINFO_INVALID = 7482; // $_SERVER['PATH_INFO'] ist nicht vorhanden oder leer, obwohl der
// Request darauf hindeutet dass PATH_INFO gefüllt sein sollte.
// Nginx bzw. FastCGI sehr wahrscheinlich falsch konfiguriert.
/*
* Sonstige Fehler
*/
const CODE_UNEXPECTED_ERROR = 7499; // Schwerer Fehler; z.B. ungefangene Exception oder Fatal Error (unsere Schuld)
}
+200
View File
@@ -0,0 +1,200 @@
<?php
namespace Xentral\Modules\Api\Error;
use Exception;
use PDOException;
use Xentral\Core\LegacyConfig\Exception\LegacyConfigExceptionInterface;
/**
* @see /www/api/index.php
*/
class ErrorHandler
{
/** @var array Error types that halts execution */
const THROWABLE_ERROR_TYPES = [
E_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_error.php */
E_PARSE, /** @see http://www.bbminfo.com/Tutor/php_error_e_parse.php */
E_CORE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_core_error.php */
E_COMPILE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_compile_error.php */
E_USER_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_user_error.php */
E_RECOVERABLE_ERROR, /** @see http://www.bbminfo.com/Tutor/php_error_e_recoverable_error.php */
];
/** @var array $errorTypeTranslations */
private $errorTypeTranslations = [
E_ERROR => 'Fatal Error',
E_PARSE => 'Parse Error',
E_CORE_ERROR => 'Core Error',
E_COMPILE_ERROR => 'Compile Error',
E_USER_ERROR => 'Fatal User Error',
E_RECOVERABLE_ERROR => 'Recoverable Error',
];
/**
* @return void
*/
public function register()
{
register_shutdown_function([$this, 'onShutdown']);
// Use own error output function
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
}
/**
* @return void
*/
public function onShutdown()
{
$error = error_get_last();
if ($error === null) {
return;
}
if ($this->isErrorTypeHaltingExecution((int)$error['type'])) {
// Try to free memory; in case of exhausted memory limit
@gc_enable();
@gc_collect_cycles();
$this->handleError((int)$error['type'], $error['message'], $error['file'], $error['line']);
}
}
/**
* @param int $code
* @param string $message
* @param string $file
* @param int $line
*
* @return bool
*/
public function handleError($code, $message, $file, $line)
{
if ($this->isErrorTypeHaltingExecution($code)) {
$content = [
'error' => [
'code' => ApiError::CODE_UNEXPECTED_ERROR,
'message' => 'Unexpected error',
'http_code' => 500,
],
];
if ($this->isDebugModeActive()) {
$errorType = $this->translateErrorType($code);
$content['debug'] = [
'error' => [
'message' => $errorType . ': ' . $message,
'file' => $file,
'line' => $line,
'code' => $code,
],
];
}
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($content);
exit; // Necessary for E_RECOVERABLE_ERROR
}
return true; // Don't execute PHP internal error handler
}
/**
* @param Exception $exception
*/
public function handleException($exception)
{
$errors = [];
if ($exception instanceof PDOException) {
if ($exception->getCode() === 'HY000') {
// "HY000: General error: 1364 Field 'xxxxx' doesn't have a default value"
if (strpos($exception->getMessage(), 'SQLSTATE[HY000]: General error: 1364') !== false) {
$errors[] = str_replace('SQLSTATE[HY000]: General error: 1364 ', '', $exception->getMessage());
}
}
// 42S22: Column not found
if ($exception->getCode() === '42S22') {
$errors[] = str_replace('SQLSTATE[42S22]: Column not found: 1054 ', '', $exception->getMessage());
}
// 1049: Unknown database
if ($exception->getCode() === 1049) {
$errors[] = str_replace('SQLSTATE[HY000] [1049] ', 'DatabaseException: ', $exception->getMessage());
}
}
if ($exception instanceof LegacyConfigExceptionInterface) {
$errors[] = $exception->getMessage();
}
$content = [
'error' => [
'code' => ApiError::CODE_UNEXPECTED_ERROR,
'message' => 'Unexpected error',
'http_code' => 500,
'errors' => $errors,
],
];
if ($this->isDebugModeActive()) {
$content['debug'] = [
'error' => [
'message' => 'Unhandled exception: ' . $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'code' => $exception->getCode(),
'trace' => $exception->getTrace(),
],
];
}
header('HTTP/1.1 500 Internal Server Error');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($content);
exit;
}
/**
* @see https://secure.php.net/manual/en/errorfunc.constants.php
*
* @param int $type
*
* @return string|null
*/
private function translateErrorType($type)
{
$type = (int)$type;
if (!isset($this->errorTypeTranslations[$type])) {
return 'Unknown Error';
}
return $this->errorTypeTranslations[$type];
}
/**
* @param int $type
*
* @return bool
*/
private function isErrorTypeHaltingExecution($type)
{
return in_array((int)$type, self::THROWABLE_ERROR_TYPES, true);
}
/**
* @return bool
*/
private function isDebugModeActive()
{
return defined('DEBUG_MODE') && (int)DEBUG_MODE === 1;
}
}
@@ -0,0 +1,14 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
class AuthorizationErrorException extends HttpException
{
public function __construct($message = 'Authorization error', $code = 0, Throwable $previous = null)
{
parent::__construct(401, $message, $code, $previous);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class BadRequestException extends HttpException
{
public function __construct(
$message = 'Bad request',
$code = ApiError::CODE_BAD_REQUEST,
Throwable $previous = null,
array $errors = array()
) {
parent::__construct(400, $message, $code, $previous, $errors);
}
}
@@ -0,0 +1,18 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class InvalidArgumentException extends HttpException
{
public function __construct(
$message = 'Invalid argument',
$code = ApiError::CODE_INVALID_ARGUMENT,
Throwable $previous = null
) {
parent::__construct(400, $message, $code, $previous);
}
}
@@ -0,0 +1,21 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class MethodNotAllowedException extends HttpException
{
public function __construct(
array $allowedMethods,
$message = 'Method not allowed',
$code = ApiError::CODE_METHOD_NOT_ALLOWED,
Throwable $previous = null
) {
$message = sprintf('Method is not allowed. Allowed: %s', implode(', ', $allowedMethods));
parent::__construct(405, $message, $code, $previous);
}
}
@@ -0,0 +1,18 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class ResourceNotFoundException extends HttpException
{
public function __construct(
$message = 'Resource not found',
$code = ApiError::CODE_RESOURCE_NOT_FOUND,
Throwable $previous = null
) {
parent::__construct(404, $message, $code, $previous);
}
}
@@ -0,0 +1,18 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class RouteNotFoundException extends HttpException
{
public function __construct(
$message = 'Route not found',
$code = ApiError::CODE_ROUTE_NOT_FOUND,
Throwable $previous = null
) {
parent::__construct(404, $message, $code, $previous);
}
}
@@ -0,0 +1,18 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class ServerErrorException extends HttpException
{
public function __construct(
$message = 'Unknown server error',
$code = ApiError::CODE_UNEXPECTED_ERROR,
Throwable $previous = null
) {
parent::__construct(500, $message, $code, $previous);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Http\Exception\HttpException;
use Xentral\Modules\Api\Error\ApiError;
class ValidationErrorException extends HttpException
{
public function __construct(
array $errors,
$message = 'Validation error',
$code = ApiError::CODE_VALIDATION_ERROR,
Throwable $previous = null
) {
parent::__construct(400, $message, $code, $previous, $errors);
}
}
@@ -0,0 +1,23 @@
<?php
namespace Xentral\Modules\Api\Exception;
use Throwable;
use Xentral\Modules\Api\Error\ApiError;
use Xentral\Modules\Api\Http\Exception\HttpException;
class WebserverMisconfigurationException extends HttpException
{
/**
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct(
$message = 'Webserver configuration incorrect',
$code = ApiError::CODE_WEBSERVER_MISCONFIGURED,
Throwable $previous = null
) {
parent::__construct(500, $message, $code, $previous);
}
}
@@ -0,0 +1,59 @@
<?php
namespace Xentral\Modules\Api\Http\Exception;
use RuntimeException;
use Throwable;
class HttpException extends RuntimeException
{
/** @var int $statusCode */
protected $statusCode = 500;
/** @var array $errors */
protected $errors;
/**
* @param int $statusCode
* @param string $message
* @param int $code
* @param array $errors
* @param Throwable|null $previous
*/
public function __construct(
$statusCode = 500,
$message = "",
$code = 0,
Throwable $previous = null,
array $errors = array()
) {
parent::__construct($message, $code, $previous);
$this->statusCode = $statusCode;
$this->errors = $errors;
}
/**
* @return int HTTP-Statuscode
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* @return bool
*/
public function hasErrors()
{
return sizeof($this->errors) > 0;
}
/**
* @return array
*/
public function getErrors()
{
return $this->errors;
}
}
@@ -0,0 +1,19 @@
<?php
namespace Xentral\Modules\Api\Http\Exception;
use Throwable;
class MethodNotAllowedException extends HttpException
{
public function __construct(
array $allowedMethods,
$message = null,
$code = 0,
Throwable $previous = null
) {
$message = sprintf('Method is not allowed. Allowed: %s', implode(', ', $allowedMethods));
parent::__construct(405, $message, $code, $previous);
}
}
@@ -0,0 +1,141 @@
<?php
namespace Xentral\Modules\Api\Http;
/**
* @deprecated Use Xentral\Components\Http instead
*/
class ParameterCollection
{
/** @var array $params */
protected $params;
/**
* @param array $params
*/
public function __construct(array $params)
{
$this->params = $params;
}
/**
* @return array
*/
public function all()
{
return $this->params;
}
/**
* @param string $name
*
* @return bool
*/
public function has($name)
{
return array_key_exists($name, $this->params);
}
/**
* @param string $name
*
* @return mixed
*/
public function get($name)
{
return isset($this->params[$name]) ? $this->params[$name] : null;
}
/**
* @param string $name
* @param mixed $value
*/
public function set($name, $value)
{
$this->params[$name] = $value;
}
/**
* @param array $values
*/
public function add(array $values)
{
$this->params = array_merge($this->params, $values);
}
/**
* @param string $name
*/
public function remove($name)
{
unset($this->params[$name]);
}
/**
* @param string $name
* @param int $filter
* @param array $options
*
* @return mixed
*/
public function filter($name, $filter = FILTER_DEFAULT, $options = [])
{
$value = $this->get($name);
if (!is_array($options) && !empty($options)) {
$options = array('flags' => $options);
}
return filter_var($value, $filter, $options);
}
/**
* @param $name
*
* @return bool Gibt true zurück für "1", "true", "on" und "yes"; sonst false
*/
public function getBool($name)
{
return $this->filter($this->get($name), FILTER_VALIDATE_BOOLEAN);
}
/**
* @param string $name
*
* @return int
*/
public function getInt($name)
{
return (int)$this->get($name);
}
/**
* @param string $name
*
* @return string
*/
public function getAlpha($name)
{
return (string)preg_replace('#[^A-Za-z]#', '', $this->get($name));
}
/**
* @param string $name
*
* @return string
*/
public function getAlphaNum($name)
{
return (string)preg_replace('#[^A-Za-z0-9]#', '', $this->get($name));
}
/**
* @param string $name
*
* @return string
*/
public function getDigits($name)
{
return (string)preg_replace('#[^0-9]#', '', $this->get($name));
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\Api\Http;
use Xentral\Components\Http\Request;
final class PathInfoDetector
{
/** @var Request $request */
private $request;
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Gibt den berechneten PathInfo-Teil der URL zurück; ohne $_SERVER['PATH_INFO'] zu verwenden
*
* Wird benötigt um Fehler in der Server-Konfiguration zu erkennen
*
* @return string|null false wenn PathInfo nicht rekonstruiert werden kann
*/
public function detect(): ?string
{
$scriptName = $this->getSafeScriptName();
if (empty($scriptName)) {
return null; // Fehlerhafte Webserver-Konfiguration
}
// PathInfo aus $_SERVER['DOCUMENT_URI'] ermitteln
// Bei Apache nicht gesetzt! Nur bei Nginx und PHP-FPM gesetzt; abhängig von Konfiguration!
$docUri = $this->request->server->get('DOCUMENT_URI');
if (!empty($docUri) && strpos($docUri, $scriptName) === 0) {
return substr($docUri, strlen($scriptName));
}
// PathInfo aus $_SERVER['PHP_SELF'] ermitteln
$phpSelf = $this->request->server->get('PHP_SELF');
if (strpos($phpSelf, $scriptName) === 0) {
return substr($phpSelf, strlen($scriptName));
}
// PathInfo aus $_SERVER['REQUEST_URI'] ermitteln; ohne URL-Rewriting
// Request-URI kann Query-Parameter enthalten!
$reqUri = $this->request->server->get('REQUEST_URI');
if (!empty($reqUri) && strpos($reqUri, $scriptName) === 0) {
$pathInfoWithQueryParams = substr($reqUri, strlen($scriptName));
return $this->trimQueryParams($pathInfoWithQueryParams);
}
// Komplexeres URL-Rewriting, oder fehlerhafte Webserver-Konfiguration
// => PathInfo kann nicht rekonstruiert werden
return null;
}
/**
* Ermittelt $_SERVER['SCRIPT_NAME'] ohne PathInfo
*
* Unter Nginx + PHP-FPM kann(!) der $_SERVER['SCRIPT_NAME'] auch den PathInfo enthalten.
*
* @return string
*/
private function getSafeScriptName(): string
{
$scriptFilename = $this->request->server->get('SCRIPT_FILENAME');
$documentRoot = $this->request->server->get('DOCUMENT_ROOT');
if (strpos($scriptFilename, $documentRoot) === 0) {
return substr($scriptFilename, strlen($documentRoot));
}
return $this->request->server->get('SCRIPT_NAME');
}
/**
* @param string $url
*
* @return string URL ohne Query-Parameter
*/
private function trimQueryParams(string $url): string
{
$queryParamsOffset = strpos($url, '?');
if ($queryParamsOffset === false) {
return $url; // Keine Query-Parameter vorhanden
}
return substr($url, 0, $queryParamsOffset);
}
}
+432
View File
@@ -0,0 +1,432 @@
<?php
namespace Xentral\Modules\Api\Http;
use Xentral\Modules\Api\Http\Exception\MethodNotAllowedException;
/**
* @deprecated Use Xentral\Components\Http instead
*/
class Request
{
/** @var array $supportedMethods */
protected static $supportedMethods = [
'GET', 'POST', 'PUT', 'DELETE',
];
/** @var array $attributes */
public $attributes;
/** @var array $query $_GET-Parameter */
public $query;
/** @var array $request $_POST-Parameter */
public $request;
/** @var array $server $_SERVER-Parameter */
public $server;
/** @var array $headers */
public $headers;
/** @var string $method */
protected $method;
/** @var string $pathInfo */
protected $pathInfo;
/** @var string $requestUri */
protected $requestUri;
/** @var string $content */
protected $content;
/** @var array $acceptableContentTypes */
protected $acceptableContentTypes;
/**
* @param array $query
* @param array $request
* @param array $server
* @param array $files
* @param array $cookies
* @param string $content
*/
public function __construct(
array $query = [],
array $request = [],
array $server = [],
array $files = [],
array $cookies = [],
$content = null
) {
$this->query = new ParameterCollection(!empty($query) ? $query : $_GET);
$this->request = new ParameterCollection(!empty($request) ? $request : $_POST);
$this->server = new ServerParameter(!empty($server) ? $server : $_SERVER);
// $this->files = $_FILES; // @todo
// $this->cookies = $_COOKIE; // @todo
$this->attributes = new ParameterCollection([]);
$this->headers = new ParameterCollection($this->server->getHeaders());
$this->method = $this->getMethod();
$this->requestUri = $this->getRequestUri();
$this->pathInfo = $this->getPathInfo();
$this->content = $content;
}
/**
* @return Request
*/
public static function createFromGlobals()
{
return new static($_GET, $_POST, $_SERVER, [], []);
}
/**
* @deprecated Use Xentral\Tests\Http\RequestFactory instead
*
* @param string $uri
* @param string $method
* @param array $params $_GET oder $_POST-Parameter
* @param array $server
* @param string $content
*
* @return Request
*/
public static function create($uri, $method = 'GET', $params = [], $server = [], $content = null)
{
// Default-Settings
$serverDefault = [
'HTTP_HOST' => 'localhost',
'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'PATH_INFO' => '',
'REMOTE_ADDRESS' => '127.0.0.1',
'REQUEST_METHOD' => 'GET',
'REQUEST_SCHEME' => 'http',
'REQUEST_TIME' => time(),
'SCRIPT_NAME' => '',
'SCRIPT_FILENAME' => '',
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => '80',
'SERVER_PROTOCOL' => 'HTTP/1.1'
];
$server = array_merge($serverDefault, $server);
if ($method !== 'GET' && in_array($method, self::$supportedMethods, true)) {
$server['REQUEST_METHOD'] = strtoupper($method);
}
$queryParams = [];
$requestParams = [];
if ($method === 'GET') {
$queryParams = $params;
} elseif (in_array($method, ['POST', 'PUT'])) {
$requestParams = $params;
}
$uriParts = parse_url($uri);
if (!empty($uriParts['scheme'])) {
$server['REQUEST_SCHEME'] = $uriParts['scheme'];
}
if (!empty($uriParts['host'])) {
$server['HTTP_HOST'] = $uriParts['host'];
$server['SERVER_NAME'] = $uriParts['host'];
}
if (!empty($uriParts['port'])) {
$server['SERVER_PORT'] = (string)$uriParts['port'];
$server['HTTP_HOST'] .= ':' . $uriParts['port'];
}
if (!isset($uriParts['path'])) {
$uriParts['path'] = '/';
}
$server['REQUEST_URI'] = $uriParts['path'];
$queryString = '';
if (!empty($uriParts['query'])) {
$queryString = $uriParts['query'];
// @todo URL-Parameter und $queryParams zusammenführen
} else {
if (!empty($queryParams)) {
$queryString = http_build_query($queryParams, '', '&');
}
}
$server['QUERY_STRING'] = $queryString;
if (!empty($queryString)) {
$server['REQUEST_URI'] .= '?' . $queryString;
}
return new static($queryParams, $requestParams, $server, [], [], $content);
}
/**
* @param string $name
* @param string $value
*/
public function setHeader($name, $value)
{
$this->headers[$name] = $value;
}
/**
* @param string $name
*
* @return string
*/
public function getHeader($name)
{
return $this->headers[$name];
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @param string $method
*/
public function setMethod($method)
{
$this->method = $method;
}
/**
* @return string
*/
public function getMethod()
{
if (null === $this->method) {
$method = strtoupper($this->server->get('REQUEST_METHOD') ?: 'GET');
if (!in_array($method, self::$supportedMethods, true)) {
throw new MethodNotAllowedException(self::$supportedMethods);
}
$this->method = $method;
}
return $this->method;
}
/**
* @return string
*/
public function getRequestUri()
{
if (null === $this->requestUri) {
$this->requestUri = $this->server->get('REQUEST_URI');
}
return $this->requestUri;
}
/**
* @return string
*/
public function getPathInfo()
{
if (null === $this->pathInfo) {
$this->pathInfo = !empty($this->server->get('PATH_INFO')) ? $this->server->get('PATH_INFO') : '/';
}
return $this->pathInfo;
}
/**
* @deprecated Use PathInfoDetector instead
*
* Gibt den berechneten PathInfo-Teil der URL zurück; ohne $_SERVER['PATH_INFO'] zu verwenden
*
* Wird benötigt um Fehler in der Server-Konfiguration zu erkennen
*
* @return string|false false wenn PathInfo nicht rekonstruiert werden kann
*/
public function getDetectedPathInfo()
{
$scriptName = $this->getSafeScriptName();
if (empty($scriptName)) {
return false; // Fehlerhafte Webserver-Konfiguration
}
// PathInfo aus $_SERVER['DOCUMENT_URI'] ermitteln
// Bei Apache nicht gesetzt! Nur bei Nginx und PHP-FPM gesetzt; abhängig von Konfiguration!
$docUri = $this->server->get('DOCUMENT_URI');
if (!empty($docUri) && strpos($docUri, $scriptName) === 0) {
return substr($docUri, strlen($scriptName));
}
// PathInfo aus $_SERVER['PHP_SELF'] ermitteln
$phpSelf = $this->server->get('PHP_SELF');
if (strpos($phpSelf, $scriptName) === 0) {
return substr($phpSelf, strlen($scriptName));
}
// PathInfo aus $_SERVER['REQUEST_URI'] ermitteln; ohne URL-Rewriting
// Request-URI kann Query-Parameter enthalten!
$reqUri = $this->server->get('REQUEST_URI');
if (!empty($reqUri) && strpos($reqUri, $scriptName) === 0) {
$pathInfoWithQueryParams = substr($reqUri, strlen($scriptName));
return $this->trimQueryParams($pathInfoWithQueryParams);
}
// Komplexeres URL-Rewriting, oder fehlerhafte Webserver-Konfiguration
// => PathInfo kann nicht rekonstruiert werden
return false;
}
/**
* Ermittelt $_SERVER['SCRIPT_NAME'] ohne PathInfo
*
* Unter Nginx + PHP-FPM kann(!) der $_SERVER['SCRIPT_NAME'] auch den PathInfo enthalten.
*
* @return string
*/
private function getSafeScriptName()
{
$scriptFilename = $this->server->get('SCRIPT_FILENAME');
$documentRoot = $this->server->get('DOCUMENT_ROOT');
if (strpos($scriptFilename, $documentRoot) === 0) {
return substr($scriptFilename, strlen($documentRoot));
}
return $this->server->get('SCRIPT_NAME');
}
/**
* @param bool $withQueryParams GET-Parameter mitliefern?
*
* @return string
*/
public function getFullUri($withQueryParams = true)
{
$scheme = $this->server->get('REQUEST_SCHEME');
$hostAndPort = $this->server->get('HTTP_HOST');
$requestUri = $this->server->get('REQUEST_URI');
$fullUriWithQueryParams = sprintf('%s://%s%s', $scheme, $hostAndPort, $requestUri);
if ($withQueryParams === true) {
return $fullUriWithQueryParams;
}
/*
* Nachfolgend werden die GET-Parameter aus der Uri entfernt
*/
$offset = strpos($fullUriWithQueryParams, '?');
$fullUriWithoutQueryParams = $offset !== false
? substr_replace($fullUriWithQueryParams, '', $offset)
: $fullUriWithQueryParams;
// Query-String zerlegen
$queryString = $this->server->get('QUERY_STRING');
parse_str($queryString, $queryParts);
/** @see /www/api/docs.html#failsafe */
if (isset($queryParts['path'])) {
return $fullUriWithoutQueryParams . '?path=' . $queryParts['path'];
}
return $fullUriWithoutQueryParams;
}
/**
* Beispiel-Failsafe-Uri: /api/index.php?path=/v1/adressen
*
* @see /www/api/docs.html#failsafe
*
* @return bool
*/
public function isFailsafeUri()
{
$queryString = $this->server->get('QUERY_STRING');
parse_str($queryString, $queryParts);
return isset($queryParts['path']);
}
/**
* @return string|null [json|xml|html|...] oder null wenn nicht gesetzt
*/
public function getContentType()
{
$contentTypeRaw = $this->headers->get('Content-Type');
if (null === $contentTypeRaw) {
return null;
}
$typeParts = explode('/', strtolower($contentTypeRaw));
return $typeParts[1];
}
/**
* @return string
*/
public function getContent()
{
if (null === $this->content) {
$this->content = file_get_contents('php://input');
}
return !empty($this->content) ? $this->content : '';
}
/**
* @param string $content
*/
public function setContent($content)
{
$this->content = (string)$content;
}
/**
* @return array
*/
public function getAcceptableContentTypes()
{
if (null === $this->acceptableContentTypes) {
$acceptHeaderRaw = $this->headers->get('Accept');
$acceptParts = explode(',', $acceptHeaderRaw);
$acceptable = [];
foreach ($acceptParts as $acceptPart) {
if ($pos = strpos($acceptPart, ';')) {
// Priorität abschneiden
$acceptPart = substr($acceptPart, 0, $pos);
}
$acceptable[] = $acceptPart;
}
$this->acceptableContentTypes = $acceptable;
}
return $this->acceptableContentTypes;
}
/**
* @param string $url
*
* @return string URL ohne Query-Parameter
*/
protected function trimQueryParams($url)
{
$queryParamsOffset = strpos($url, '?');
if ($queryParamsOffset === false) {
return $url; // Keine Query-Parameter vorhanden
}
return substr($url, 0, $queryParamsOffset);
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace Xentral\Modules\Api\Http;
use RuntimeException;
/**
* @deprecated Use Xentral\Components\Http instead
*/
class Response
{
const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_BAD_REQUEST = 400;
const HTTP_UNAUTHORIZED = 401;
const HTTP_FORBIDDEN = 403;
const HTTP_NOT_FOUND = 404;
const HTTP_METHOD_NOT_ALLOWED = 405;
const HTTP_INTERNAL_SERVER_ERROR = 500;
/** @var array $statusMessages */
protected $statusMessages = [
self::HTTP_OK => 'OK',
self::HTTP_CREATED => 'Created',
self::HTTP_BAD_REQUEST => 'Bad Request',
self::HTTP_UNAUTHORIZED => 'Unauthorized',
self::HTTP_FORBIDDEN => 'Forbidden',
self::HTTP_NOT_FOUND => 'Not Found',
self::HTTP_METHOD_NOT_ALLOWED => 'Method Not Allowed',
self::HTTP_INTERNAL_SERVER_ERROR => 'Internal Server Error',
];
/** @var array $headers */
protected $headers = [];
/** @var string $content Response-Content */
protected $content;
/** @var int $statusCode HTTP-Statuscode */
protected $statusCode;
/** @var string $statusText HTTP-Statustext */
protected $statusText;
/** @var string $protocolVersion */
protected $protocolVersion = '1.1';
/**
* @param string $content
* @param int $statusCode
* @param array $headers
*/
public function __construct($content, $statusCode, array $headers = [])
{
$this->content = $content;
$this->headers = $headers;
$this->statusCode = $statusCode;
}
/**
* Response an Client senden
*/
public function send()
{
header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->statusCode, $this->statusText));
foreach ($this->headers as $name => $value) {
header(sprintf('%s: %s', $name, $value), false, $this->statusCode);
}
echo $this->content;
}
/**
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @param string $content
*/
public function setContent($content)
{
$this->content = (string)$content;
}
/**
* @return int
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* @param int $statusCode
*/
public function setStatusCode($statusCode)
{
if (!array_key_exists($statusCode, $this->statusMessages)) {
throw new RuntimeException(sprintf('Status Code %s is not supported', $statusCode));
}
$this->statusCode = $statusCode;
}
/**
* @return string HTTP-Statustext
*/
public function getStatusText()
{
if ($this->statusText === null) {
$this->statusText = $this->statusMessages[$this->statusCode];
}
return $this->statusText;
}
/**
* @param string $name
* @param string $value
*/
public function setHeader($name, $value)
{
$this->headers[$name] = $value;
}
/**
* @param string $name
*
* @return string
*/
public function getHeader($name)
{
return $this->headers[$name];
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
}
@@ -0,0 +1,64 @@
<?php
namespace Xentral\Modules\Api\Http;
/**
* @deprecated Use Xentral\Components\Http instead
*/
class ServerParameter extends ParameterCollection
{
/**
* @return array
*/
public function getHeaders()
{
$header = [];
if (isset($this->params['CONTENT_TYPE'])) {
$header['Content-Type'] = $this->params['CONTENT_TYPE'];
}
foreach ($this->params as $name => $value) {
if (substr($name, 0, 4) === 'HTTP') {
$header[$this->transformHeaderName($name)] = $value;
}
}
// Auth-Header ist bereits gesetzt durch $_SERVER[HTTP_AUTHORIZATION]
if (!empty($header['Authorization'])) {
return $header;
}
// Basic-Auth
if (isset($this->params['PHP_AUTH_USER'])) {
$authString = base64_encode($this->params['PHP_AUTH_USER'] . ':' . $this->params['PHP_AUTH_PW']);
$header['Authorization'] = sprintf('Basic %s', $authString);
}
// Digest-Auth
if (isset($this->params['PHP_AUTH_DIGEST'])) {
$header['Authorization'] = sprintf('Digest %s', $this->params['PHP_AUTH_DIGEST']);
}
return $header;
}
/**
* Header-Bezeichnungen umwandeln
*
* @param string $name
*
* @return string
*
* @example Wandelt "HTTP_USER_AGENT" zu "User-Agent"
*/
private function transformHeaderName($name)
{
$name = substr($name, 5); // HTTP-Prefix entfernen
$name = str_replace('_', ' ', $name);
$name = strtolower($name);
$name = ucwords($name);
return str_replace(' ', '-', $name);
}
}
@@ -0,0 +1,67 @@
<?php
namespace Xentral\Modules\Api\LegacyBridge;
class LegacyApiLazyProxy
{
/** @var \Api $realLegacyApi */
private $realLegacyApi;
/** @var bool $isInitialized */
private $isInitialized = false;
/**
* Magischer Aufruf für Methoden
*
* @param string $action
* @param array $arguments
*
* @return mixed
*/
public function __call($action, $arguments)
{
if ($this->isInitialized === false) {
$this->lazyLoad();
}
return call_user_func_array(array($this->realLegacyApi, $action), $arguments);
}
/**
* Magischer Getter für Eigenschaften
*
* @param string $property
*
* @return mixed|null
*/
public function __get($property)
{
if ($this->isInitialized === false) {
$this->lazyLoad();
}
if (property_exists($this->realLegacyApi, $property)) {
return $this->realLegacyApi->{$property};
}
return null;
}
/**
* Legacy-API nachladen
*/
private function lazyLoad()
{
$app = new LegacyApplication();
$apiobj = $app->erp->LoadModul('api');
$apiobj->app = $app;
if (!$apiobj instanceof \Api) {
throw new \RuntimeException('Legacy-API could not be loaded');
}
$this->realLegacyApi = $apiobj;
$this->isInitialized = true;
}
}
@@ -0,0 +1,7 @@
<?php
namespace Xentral\Modules\Api\LegacyBridge;
class LegacyApplication extends \ApplicationCore
{
}
@@ -0,0 +1,421 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Exception;
use InvalidArgumentException;
use Xentral\Components\Database\Database;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Resource\Exception\EndpointNotAvailableException;
use Xentral\Modules\Api\Resource\Feature\FilterFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\IncludeFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\SortingFeatureTrait;
use Xentral\Modules\Api\Resource\Feature\ValidationFeatureTrait;
use Xentral\Modules\Api\Resource\Filter\Select\ComplexSearchFilter;
use Xentral\Modules\Api\Resource\Filter\Select\SelectFilterInterface;
use Xentral\Modules\Api\Resource\Filter\Select\SelectFilterTrait;
use Xentral\Modules\Api\Resource\Result\CollectionResult;
use Xentral\Modules\Api\Resource\Result\ItemResult;
use Xentral\Modules\Api\Validator\Validator;
abstract class AbstractResource
{
use SelectFilterTrait;
use FilterFeatureTrait;
use SortingFeatureTrait;
use IncludeFeatureTrait;
use ValidationFeatureTrait;
/** @var Database $db */
protected $db;
/** @var Validator $validator */
protected $validator;
/** @return SelectQuery|false */
abstract protected function selectAllQuery();
/** @return SelectQuery|false */
abstract protected function selectOneQuery();
/** @return SelectQuery|false */
abstract protected function selectIdsQuery();
/** @return InsertQuery|false */
abstract protected function insertQuery();
/** @return UpdateQuery|false */
abstract protected function updateQuery();
/** @return UpdateQuery|DeleteQuery|false */
abstract protected function deleteQuery();
/** @return void */
abstract protected function configure();
/**
* @param Database $database
* @param Validator $validator
*/
public function __construct(
Database $database,
Validator $validator
) {
$this->db = $database;
$this->validator = $validator;
$this->configure();
// Komplexe Suche immer aktivieren
$this->registerSelectFilter(new ComplexSearchFilter());
}
/**
* @param array $filter
* @param array $sorting
* @param array $columns
* @param array $includes
* @param int $page
* @param int $paging
*
* @return CollectionResult
*/
public function getList(
array $filter = [],
array $sorting = [],
array $columns = [],
array $includes = [],
$page = 1,
$paging = 20
) {
/** @var SelectQuery $selectAll */
$selectAll = $this->selectAllQuery();
if (!$selectAll) {
throw new EndpointNotAvailableException();
}
if (!$selectAll instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectAllQuery() must return an instance of %s', SelectQuery::class
));
}
// Suchfilter und Sortierung hinzufügen
$selectAll = $this->applySelectFilter($selectAll, [
SelectFilterInterface::TYPE_SEARCHING => $filter,
SelectFilterInterface::TYPE_SORTING => $sorting,
]);
// Filter hinzufügen
//$selectAll = $this->appendFilterQuery($filter, $selectAll);
//$bindValues = $this->appendFilterBindings($filter, $bindValues);
// Sortierung hinzufügen
//$selectAll = $this->appendSorting($sorting, $selectAll);
/*echo "<pre>";
echo $selectAll->getStatement();
var_dump($selectAll->getBindValues());
echo "</pre>";
exit;*/
// Ergebnisse ermitteln
$selectList = clone $selectAll;
if (!empty($columns)) {
$selectList->resetCols()->cols($columns);
}
$selectList->page($page)->setPaging($paging);
$items = $this->db->fetchAll(
$selectList->getStatement(),
$selectList->getBindValues()
);
if (count($items) === 0) {
throw new ResourceNotFoundException();
}
// Gesamtanzahl der Ergebnisse ermitteln
$selectCount = clone $selectAll;
$selectCount->resetOrderBy()->resetCols()->cols(['COUNT(*)']);
$total = (int)$this->db->fetchValue(
$selectCount->getStatement(),
$selectCount->getBindValues()
);
$pagination = $this->getPagination($total, count($items), $paging, $page);
// Includes in Ergebnis integrieren
$items = $this->integrateIncludes($includes, $items);
return new CollectionResult($items, $pagination);
}
/**
* @param array $ids
* @param array $columns Spalten überschreiben
*
* @return CollectionResult
*/
public function getIds(array $ids, array $columns = [])
{
/** @var SelectQuery $selectIds */
$selectIds = $this->selectIdsQuery();
if (!$selectIds) {
throw new EndpointNotAvailableException();
}
if (!$selectIds instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectIdsQuery() must return an instance of %s', SelectQuery::class
));
}
if (!empty($columns)) {
$selectIds->resetCols()->cols($columns);
}
$data = $this->db->fetchAssoc(
$selectIds->getStatement(),
['ids' => $ids]
);
if (!$data) {
throw new ResourceNotFoundException();
}
return new CollectionResult($data);
}
/**
* @param int $id
* @param array $includes
*
* @return ItemResult
*/
public function getOne($id, array $includes = [])
{
/** @var SelectQuery $selectOne */
$selectOne = $this->selectOneQuery();
if (!$selectOne) {
throw new EndpointNotAvailableException();
}
if (!$selectOne instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectOneQuery() must return an instance of %s', SelectQuery::class
));
}
$data = $this->db->fetchRow($selectOne->getStatement(), ['id' => $id]);
if (!$data) {
throw new ResourceNotFoundException();
}
// Includes in Ergebnis integrieren
$data = $this->integrateIncludes($includes, $data, false);
return new ItemResult($data);
}
/**
* Prüfen ob übergebene ID in Datenbank vorhanden ist
*
* @param int $id
* @param string|null $message Fehlermeldung wenn ID nicht vorhanden ist
*/
public function checkOrFail($id, $message = null)
{
/** @var SelectQuery $selectOne */
$select = $this->selectOneQuery();
if (!$select) {
throw new EndpointNotAvailableException();
}
if (!$select instanceof SelectQuery) {
throw new InvalidArgumentException(sprintf(
'selectOneQuery() must return an instance of %s', SelectQuery::class
));
}
$value = $this->db->fetchValue($select->getStatement(), ['id' => $id]);
if ((int)$value !== (int)$id) {
throw new ResourceNotFoundException($message === null ? 'Resource not found' : $message);
}
}
/**
* @param int $id
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return ItemResult
*/
public function edit($id, $inputVars, $inputMapping = null)
{
$updateQuery = $this->updateQuery();
if (!$updateQuery) {
throw new EndpointNotAvailableException();
}
if (!$updateQuery instanceof UpdateQuery) {
throw new InvalidArgumentException(sprintf(
'updateQuery() must return an instance of %s', UpdateQuery::class
));
}
// Eingabe validieren
$this->validateData($inputVars, $id);
$inputVars['id'] = $id;
// Eingabe- zu Datenbankfeld mappen
$inputVars = $this->mapInputData($inputVars, $inputMapping);
$bindValues = [];
foreach ($inputVars as $inputKey => $inputVal) {
$updateQuery->col($inputKey);
$bindValues[$inputKey] = $inputVal;
}
$this->db->perform($updateQuery->getStatement(), $bindValues);
// Bei Erfolg die geänderte Resource zurückliefern; mit Success-Flag
$result = $this->getOne($id);
$result->setSuccess(true);
return $result;
}
/**
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return ItemResult
*/
public function insert($inputVars, $inputMapping = null)
{
$insertQuery = $this->insertQuery();
if (!$insertQuery) {
throw new EndpointNotAvailableException();
}
if (!$insertQuery instanceof InsertQuery) {
throw new InvalidArgumentException(sprintf(
'insertQuery() must return an instance of %s', InsertQuery::class
));
}
// Eingabe validieren
$this->validateData($inputVars);
// Eingabe- zu Datenbankfeld mappen
$inputVars = $this->mapInputData($inputVars, $inputMapping);
$bindValues = [];
foreach ($inputVars as $inputKey => $inputVal) {
$insertQuery->col($inputKey);
$bindValues[$inputKey] = $inputVal;
}
$this->db->perform($insertQuery->getStatement(), $bindValues);
$id = $this->db->lastInsertId();
// Bei Erfolg die angelegte Resource zurückliefern; mit Success-Flag
$result = $this->getOne($id);
$result->setSuccess(true);
return $result;
}
/**
* @param int $id
*
* @return ItemResult
*/
public function delete($id)
{
$deleteQuery = $this->deleteQuery();
if (!$deleteQuery) {
throw new EndpointNotAvailableException();
}
if (!$deleteQuery instanceof DeleteQuery && !$deleteQuery instanceof UpdateQuery) {
throw new InvalidArgumentException(sprintf(
'deleteQuery() must return an instance of %s or %s', DeleteQuery::class, UpdateQuery::class
));
}
try {
$this->db->perform($deleteQuery->getStatement(), ['id' => $id]);
$success = true;
} catch (Exception $e) {
$success = false;
}
$result = new ItemResult(['id' => $id]);
$result->setSuccess($success);
return $result;
}
/**
* Eingabe- zu Datenbankfeld mappen
*
* @param array $inputVars
* @param array|null $inputMapping Assoc-Array ['Eingabefeld' => 'Datenbankfeld']
*
* @return array
*/
protected function mapInputData($inputVars, $inputMapping = null)
{
if (empty($inputMapping)) {
return $inputVars;
}
foreach ($inputMapping as $inputKey => $dbKey) {
if (empty($inputKey) || empty($dbKey)) {
continue;
}
if ($inputKey === $dbKey) {
continue;
}
if (array_key_exists($inputKey, $inputVars)) {
$inputVars[$dbKey] = $inputVars[$inputKey];
unset($inputVars[$inputKey]);
}
}
return $inputVars;
}
/**
* @param int $itemsTotal
* @param int $itemsCurrent
* @param int $itemsPerPage
* @param int $pageCurrent
*
* @return array
*/
protected function getPagination($itemsTotal, $itemsCurrent, $itemsPerPage, $pageCurrent)
{
return [
'items_per_page' => (int)$itemsPerPage,
'items_current' => (int)$itemsCurrent,
'items_total' => (int)$itemsTotal,
'page_current' => (int)$pageCurrent,
'page_last' => (int)ceil($itemsTotal / $itemsPerPage),
];
}
/**
* @param string $resourceClass
*
* @return AbstractResource
*/
protected function getResource($resourceClass)
{
return new $resourceClass(
$this->db,
$this->validator
);
}
}
@@ -0,0 +1,381 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class AddressResource extends AbstractResource
{
const TABLE_NAME = 'adresse';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'rolle' => 'ar.rolle %LIKE%',
'projekt' => 'a.projekt =',
'firma' => 'a.firma =',
'typ' => 'a.typ LIKE',
'sprache' => 'a.sprache LIKE',
'waehrung' => 'a.waehrung LIKE',
'land' => 'a.land LIKE',
'name' => 'a.name %LIKE%',
'name_equals' => 'a.name LIKE',
'name_startswith' => 'a.name LIKE%',
'name_endswith' => 'a.name %LIKE',
'kundennummer' => 'a.kundennummer %LIKE%',
'kundennummer_equals' => 'a.kundennummer LIKE',
'kundennummer_startswith' => 'a.kundennummer LIKE%',
'kundennummer_endswith' => 'a.kundennummer %LIKE',
'lieferantennummer' => 'a.lieferantennummer %LIKE%',
'lieferantennummer_equals' => 'a.lieferantennummer LIKE',
'lieferantennummer_startswith' => 'a.lieferantennummer LIKE%',
'lieferantennummer_endswith' => 'a.lieferantennummer %LIKE',
'mitarbeiternummer' => 'a.mitarbeiternummer %LIKE%',
'mitarbeiternummer_equals' => 'a.mitarbeiternummer LIKE',
'mitarbeiternummer_startswith' => 'a.mitarbeiternummer LIKE%',
'mitarbeiternummer_endswith' => 'a.mitarbeiternummer %LIKE',
'email' => 'a.email %LIKE%',
'email_equals' => 'a.email LIKE',
'email_startswith' => 'a.email LIKE%',
'email_endswith' => 'a.email %LIKE',
'freifeld1' => 'a.freifeld1 %LIKE%',
'freifeld2' => 'a.freifeld2 %LIKE%',
'freifeld3' => 'a.freifeld3 %LIKE%',
'freifeld4' => 'a.freifeld4 %LIKE%',
'freifeld5' => 'a.freifeld5 %LIKE%',
'freifeld6' => 'a.freifeld6 %LIKE%',
'freifeld7' => 'a.freifeld7 %LIKE%',
'freifeld8' => 'a.freifeld8 %LIKE%',
'freifeld9' => 'a.freifeld9 %LIKE%',
'freifeld10' => 'a.freifeld10 %LIKE%',
'freifeld1_equals' => 'a.freifeld1 LIKE',
'freifeld2_equals' => 'a.freifeld2 LIKE',
'freifeld3_equals' => 'a.freifeld3 LIKE',
'freifeld4_equals' => 'a.freifeld4 LIKE',
'freifeld5_equals' => 'a.freifeld5 LIKE',
'freifeld6_equals' => 'a.freifeld6 LIKE',
'freifeld7_equals' => 'a.freifeld7 LIKE',
'freifeld8_equals' => 'a.freifeld8 LIKE',
'freifeld9_equals' => 'a.freifeld9 LIKE',
'freifeld10_equals' => 'a.freifeld10 LIKE',
]);
$this->registerSortingParams([
'name' => 'a.name',
'kundennummer' => 'a.kundennummer',
'lieferantennummer' => 'a.lieferantennummer',
'mitarbeiternummer' => 'a.mitarbeiternummer',
]);
/*$this->registerValidationRules([
'id' => 'not_present',
'bezeichnung' => 'required',
'type' => 'required',
'projekt' => 'numeric',
'netto' => 'boolean',
'aktiv' => 'boolean',
]);*/
/*$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);*/
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'a.id',
'ar.rolle',
'a.typ',
'a.marketingsperre',
'a.trackingsperre',
'a.rechnungsadresse',
'a.sprache',
'a.name',
'a.abteilung',
'a.unterabteilung',
'a.ansprechpartner',
'a.land',
'a.strasse',
'a.ort',
'a.plz',
'a.telefon',
'a.telefax',
'a.mobil',
'a.email',
'a.ustid',
'a.ust_befreit',
'a.passwort_gesendet',
'a.sonstiges',
'a.adresszusatz',
'a.kundenfreigabe',
'a.steuer',
'a.logdatei',
'a.kundennummer',
'a.lieferantennummer',
'a.mitarbeiternummer',
'a.konto',
'a.blz',
'a.bank',
'a.inhaber',
'a.swift',
'a.iban',
'a.waehrung',
'a.paypal',
'a.paypalinhaber',
'a.paypalwaehrung',
'a.projekt',
'a.partner',
'a.zahlungsweise',
'a.zahlungszieltage',
'a.zahlungszieltageskonto',
'a.zahlungszielskonto',
'a.versandart',
'a.kundennummerlieferant',
'a.zahlungsweiselieferant',
'a.zahlungszieltagelieferant',
'a.zahlungszieltageskontolieferant',
'a.zahlungszielskontolieferant',
'a.versandartlieferant',
'a.geloescht',
'a.firma',
'a.webid',
'a.vorname',
'a.kennung',
'a.sachkonto',
'a.filiale',
'a.vertrieb',
'a.innendienst',
'a.verbandsnummer',
'a.abweichendeemailab',
'a.portofrei_aktiv',
'a.portofreiab',
'a.infoauftragserfassung',
'a.mandatsreferenz',
'a.mandatsreferenzdatum',
'a.mandatsreferenzaenderung',
'a.glaeubigeridentnr',
'a.kreditlimit',
'a.tour',
'a.zahlungskonditionen_festschreiben',
'a.rabatte_festschreiben',
'a.mlmaktiv',
'a.mlmvertragsbeginn',
'a.mlmlizenzgebuehrbis',
'a.mlmfestsetzenbis',
'a.mlmfestsetzen',
'a.mlmmindestpunkte',
'a.mlmwartekonto',
'a.abweichende_rechnungsadresse',
'a.rechnung_vorname',
'a.rechnung_name',
'a.rechnung_titel',
'a.rechnung_typ',
'a.rechnung_strasse',
'a.rechnung_ort',
'a.rechnung_plz',
'a.rechnung_ansprechpartner',
'a.rechnung_land',
'a.rechnung_abteilung',
'a.rechnung_unterabteilung',
'a.rechnung_adresszusatz',
'a.rechnung_telefon',
'a.rechnung_telefax',
'a.rechnung_anschreiben',
'a.rechnung_email',
'a.geburtstag',
'a.rolledatum',
'a.liefersperre',
'a.liefersperregrund',
'a.mlmpositionierung',
'a.steuernummer',
'a.steuerbefreit',
'a.mlmmitmwst',
'a.mlmabrechnung',
'a.mlmwaehrungauszahlung',
'a.mlmauszahlungprojekt',
'a.sponsor',
'a.geworbenvon',
'a.logfile',
'a.kalender_aufgaben',
'a.verrechnungskontoreisekosten',
'a.usereditid',
'a.useredittimestamp',
'a.rabatt',
'a.provision',
'a.rabattinformation',
'a.rabatt1',
'a.rabatt2',
'a.rabatt3',
'a.rabatt4',
'a.rabatt5',
'a.internetseite',
'a.bonus1',
'a.bonus1_ab',
'a.bonus2',
'a.bonus2_ab',
'a.bonus3',
'a.bonus3_ab',
'a.bonus4',
'a.bonus4_ab',
'a.bonus5',
'a.bonus5_ab',
'a.bonus6',
'a.bonus6_ab',
'a.bonus7',
'a.bonus7_ab',
'a.bonus8',
'a.bonus8_ab',
'a.bonus9',
'a.bonus9_ab',
'a.bonus10',
'a.bonus10_ab',
'a.rechnung_periode',
'a.rechnung_anzahlpapier',
'a.rechnung_permail',
'a.titel',
'a.anschreiben',
'a.nachname',
'a.arbeitszeitprowoche',
'a.folgebestaetigungsperre',
'a.lieferantennummerbeikunde',
'a.verein_mitglied_seit',
'a.verein_mitglied_bis',
'a.verein_mitglied_aktiv',
'a.verein_spendenbescheinigung',
'a.freifeld1',
'a.freifeld2',
'a.freifeld3',
'a.freifeld4',
'a.freifeld5',
'a.freifeld6',
'a.freifeld7',
'a.freifeld8',
'a.freifeld9',
'a.freifeld10',
'a.rechnung_papier',
'a.angebot_cc',
'a.auftrag_cc',
'a.rechnung_cc',
'a.gutschrift_cc',
'a.lieferschein_cc',
'a.bestellung_cc',
'a.angebot_fax_cc',
'a.auftrag_fax_cc',
'a.rechnung_fax_cc',
'a.gutschrift_fax_cc',
'a.lieferschein_fax_cc',
'a.bestellung_fax_cc',
'a.abperfax',
'a.abpermail',
'a.kassiereraktiv',
'a.kassierernummer',
'a.kassiererprojekt',
'a.portofreilieferant_aktiv',
'a.portofreiablieferant',
'a.mandatsreferenzart',
'a.mandatsreferenzwdhart',
'a.serienbrief',
'a.kundennummer_buchhaltung',
'a.lieferantennummer_buchhaltung',
'a.lead',
'a.zahlungsweiseabo',
'a.bundesland',
'a.mandatsreferenzhinweis',
'a.geburtstagkalender',
'a.geburtstagskarte',
'a.liefersperredatum',
'a.umsatzsteuer_lieferant',
'a.lat',
'a.lng',
'a.art',
'a.angebot_email',
'a.auftrag_email',
'a.rechnungs_email',
'a.gutschrift_email',
'a.lieferschein_email',
'a.bestellung_email',
'a.firmensepa',
'a.anzeigesteuerbelege',
'a.gln',
'a.rechnung_gln',
'a.keinealtersabfrage',
'a.lieferbedingung',
'a.mlmintranetgesamtestruktur',
'a.kommissionskonsignationslager',
'a.zollinformationen',
'a.bundesstaat',
'a.rechnung_bundesstaat',
'a.lieferschwellenichtanwenden',
])
->from(self::TABLE_NAME . ' AS a')
->joinSubSelect(
'LEFT',
'SELECT ar.adresse, GROUP_CONCAT(LOWER(ar.subjekt)) AS rolle ' .
'FROM adresse_rolle AS ar ' .
'WHERE (ar.bis = \'0000-00-00\' OR ar.bis >= CURDATE())' .
'AND (ar.von = \'0000-00-00\' OR ar.von <= CURDATE())' .
'AND (ar.subjekt = \'Kunde\' OR ar.subjekt = \'Lieferant\') ' .
'GROUP BY ar.adresse ',
'ar',
'a.id = ar.adresse'
)
->where('a.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('a.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('a.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,114 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class AddressTypeResource extends AbstractResource
{
const TABLE_NAME = 'adresse_typ';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung' => 't.bezeichnung %LIKE%',
'bezeichnung_exakt' => 't.bezeichnung LIKE',
'type' => 't.type LIKE',
'projekt' => 't.projekt =',
'netto' => 't.netto =',
'aktiv' => 't.aktiv =',
]);
$this->registerSortingParams([
'bezeichnung' => 't.bezeichnung',
'type' => 't.type',
'projekt' => 't.projekt',
'netto' => 't.netto',
'aktiv' => 't.aktiv',
]);
$this->registerValidationRules([
'id' => 'not_present',
'bezeichnung' => 'required',
'type' => 'required',
'projekt' => 'numeric',
'netto' => 'boolean',
'aktiv' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
't.id',
't.type',
't.bezeichnung',
't.projekt',
't.netto',
't.aktiv',
])->from(self::TABLE_NAME . ' AS t')
->where('t.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('t.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('t.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,110 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class ArticleCategoryResource extends AbstractResource
{
const TABLE_NAME = 'artikelkategorien';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung' => 'k.bezeichnung %LIKE%',
'bezeichnung_exakt' => 'k.bezeichnung LIKE',
'projekt' => 'k.projekt =',
'parent' => 'k.parent =',
]);
$this->registerSortingParams([
'bezeichnung' => 'k.bezeichnung',
'projekt' => 'k.projekt',
'parent' => 'k.parent',
]);
$this->registerValidationRules([
'id' => 'not_present',
'id_ext' => 'not_present', // @todo
'bezeichnung' => 'required|unique:artikelkategorien,bezeichnung',
'next_number' => 'numeric',
'projekt' => 'numeric',
'parent' => 'numeric',
'externenummer' => 'numeric',
'geloescht' => 'in:0,1',
//'id_ext' => 'numeric', @todo
// @todo Steuerfelder
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols(['k.*', 'am.id_ext'])->from(self::TABLE_NAME . ' AS k')->where('k.geloescht <> 1')
->leftJoin(
'api_mapping AS am',
'am.id_int = k.id AND am.tabelle = ' . $this->db->escapeString('artikelkategorien')
);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('k.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('k.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,131 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class ArticleFileResource extends AbstractResource
{
const TABLE_NAME = 'datei';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
/*$this->registerFilterParams([
'bezeichnung' => 'd.bezeichnung %LIKE%',
'bezeichnung_exakt' => 'd.bezeichnung LIKE',
'projekt' => 'd.projekt =',
'parent' => 'd.parent =',
]);*/
/*$this->registerSortingParams([
'bezeichnung' => 'd.bezeichnung',
'projekt' => 'd.projekt',
'parent' => 'd.parent',
]);*/
/*$this->registerValidationRules([
'id' => 'not_present',
'id_ext' => 'not_present', // @todo
'bezeichnung' => 'required|unique:artikelkategorien,bezeichnung',
'next_number' => 'numeric',
'projekt' => 'numeric',
'parent' => 'numeric',
'externenummer' => 'numeric',
'geloescht' => 'in:0,1',
//'id_ext' => 'numeric', @todo
// @todo Steuerfelder
]);*/
/*$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);*/
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'd.id',
'd.titel',
'd.beschreibung',
'ds.subjekt',
'ds.parameter AS artikel',
'd.nummer',
])
->from(self::TABLE_NAME . ' AS d')
->where('d.geloescht <> 1')
->innerJoin(
'datei_stichwoerter AS ds',
'd.id = ds.datei AND ds.objekt = ' . $this->db->escapeString('Artikel')
)
/*->innerJoin(
'datei_version AS dv',
'd.id = dv.datei'
)*/
/*->joinSubSelect(
'INNER',
'SELECT MAX(dv.version) AS max_version, dv.datei, dv.ersteller, dv.datum, dv.bemerkung '.
'FROM datei_version AS dv '.
'GROUP BY dv.datei, dv.ersteller, dv.datum, dv.bemerkung',
'dv',
'd.id = dv.datei'
)*/
;
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('d.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('d.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,447 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class ArticleResource extends AbstractResource
{
const TABLE_NAME = 'artikel';
/** @var \Api $legacyApi */
private $legacyApi;
/**
* @param \Api $api
*
* @return void
*/
public function setLegacyApi($api)
{
$this->legacyApi = $api;
}
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'typ' => 'a.typ LIKE',
'name_de' => 'a.name_de %LIKE%',
'name_de_exakt' => 'a.name_de LIKE',
'name_de_startswith' => 'a.name_de LIKE%',
'name_de_endswith' => 'a.name_de %LIKE',
'name_de_equals' => 'a.name_de LIKE',
'name_en' => 'a.name_en %LIKE%',
'name_en_exakt' => 'a.name_en LIKE',
'name_en_startswith' => 'a.name_en LIKE%',
'name_en_endswith' => 'a.name_en %LIKE',
'name_en_equals' => 'a.name_en LIKE',
'nummer' => 'a.nummer %LIKE%',
'nummer_exakt' => 'a.nummer LIKE',
'nummer_startswith' => 'a.nummer LIKE%',
'nummer_endswith' => 'a.nummer %LIKE',
'nummer_equals' => 'a.nummer LIKE',
'projekt' => 'a.projekt =',
'adresse' => 'a.adresse =',
'katalog' => 'a.katalog =',
'firma' => 'a.firma =',
'ausverkauft' => 'a.ausverkauft =',
'startseite' => 'a.startseite =',
'topseller' => 'a.topseller =',
]);
$this->registerSortingParams([
'name_de' => 'a.name_de',
'name_en' => 'a.name_en',
'nummer' => 'a.nummer',
'typ' => 'a.typ',
]);
$this->registerValidationRules([
'id' => 'not_present',
'a.shop' => 'not_present',
'a.shop2' => 'not_present',
'a.shop3' => 'not_present',
'a.usereditid' => 'not_present',
'a.useredittimestamp' => 'not_present',
'a.intern_gesperrtuser' => 'not_present',
'a.inbearbeitunguser' => 'not_present',
'nummer' => 'required|unique:artikel,nummer',
'projekt' => 'numeric',
'adresse' => 'numeric',
'katalog' => 'numeric',
'firma' => 'numeric',
'ausverkauft' => 'in:0,1',
'geloescht' => 'in:0,1',
// Keine Default-Values
/*'checksum' => 'present',
'kurztext_de' => 'present',
'kurztext_en' => 'present',
'beschreibung_de' => 'present',
'beschreibung_en' => 'present',
'uebersicht_de' => 'present',
'uebersicht_en' => 'present',
'links_de' => 'present',
'links_en' => 'present',
'startseite_de' => 'present',
'startseite_en' => 'present',*/
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
'verkaufspreise' => [
'key' => 'verkaufspreise',
'filter' => [
['property' => 'artikel', 'value' => ':id'],
],
'sort' => ['menge' => 'ASC'],
'resource' => SalesPriceResource::class,
],
'dateien' => [
'key' => 'dateien',
'filter' => [
['property' => 'artikel', 'value' => ':id'],
],
'resource' => ArticleFileResource::class,
],
'lagerbestand' => [
/**
* Sonderfall
*
* @see ArticleResource::integrateIncludes
*/
],
]);
}
/**
* @inheritdoc
*/
protected function integrateIncludes(array $includes, array &$items, $isCollection = true)
{
// Ausnahme für "lagerbestand"-Include
$lagerbestandIncludeKey = array_search('lagerbestand', $includes, true);
if ($lagerbestandIncludeKey !== false) {
// Mehrere Artikel
if ($isCollection) {
foreach ($items as &$item) {
$articleId = $item['id'];
$istLagerartikel = (int)$item['lagerartikel'] === 1;
$item['lagerbestand'] =
$istLagerartikel
? $this->legacyApi->app->erp->ArtikelAnzahlVerkaufbar($articleId, 0, 0, 0, 0, true)
: [];
}
unset($item);
}
// Einzelner Artikel
if (!$isCollection) {
$articleId = $items['id'];
$istLagerartikel = (int)$items['lagerartikel'] === 1;
$items['lagerbestand'] =
$istLagerartikel
? $this->legacyApi->app->erp->ArtikelAnzahlVerkaufbar($articleId, 0, 0, 0, 0, true)
: [];
}
unset($includes[$lagerbestandIncludeKey]);
}
// Andere Includes normal ausführen
return parent::integrateIncludes($includes, $items, $isCollection);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
//'a.*',
'a.id',
'a.typ',
'a.nummer',
'a.checksum',
'a.projekt',
'a.inaktiv',
'a.ausverkauft',
'a.warengruppe',
'a.name_de',
'a.name_en',
'a.kurztext_de',
'a.kurztext_en',
'a.beschreibung_de',
'a.beschreibung_en',
'a.uebersicht_de',
'a.uebersicht_en',
'a.links_de',
'a.links_en',
'a.startseite_de',
'a.startseite_en',
'a.standardbild',
'a.herstellerlink',
'a.hersteller',
'a.teilbar',
'a.nteile',
'a.seriennummern',
'a.lager_platz',
'a.lieferzeit',
'a.lieferzeitmanuell',
'a.sonstiges',
'a.gewicht',
'a.endmontage',
'a.funktionstest',
'a.artikelcheckliste',
'a.stueckliste',
'a.juststueckliste',
'a.barcode',
'a.hinzugefuegt',
'a.pcbdecal',
'a.lagerartikel',
'a.porto',
'a.chargenverwaltung',
'a.provisionsartikel',
'a.gesperrt',
'a.sperrgrund',
'a.geloescht',
'a.gueltigbis',
'a.umsatzsteuer',
'a.klasse',
'a.adresse',
'a.shopartikel',
'a.unishopartikel',
'a.journalshopartikel',
'a.katalog',
'a.katalogtext_de',
'a.katalogtext_en',
'a.katalogbezeichnung_de',
'a.katalogbezeichnung_en',
'a.neu',
'a.topseller',
'a.startseite',
'a.wichtig',
'a.mindestlager',
'a.mindestbestellung',
'a.partnerprogramm_sperre',
'a.internerkommentar',
'a.intern_gesperrt',
//'a.intern_gesperrtuser',
'a.intern_gesperrtgrund',
'a.inbearbeitung',
//'a.inbearbeitunguser',
'a.cache_lagerplatzinhaltmenge',
'a.internkommentar',
'a.firma',
'a.logdatei',
'a.anabregs_text',
'a.autobestellung',
'a.produktion',
'a.herstellernummer',
'a.restmenge',
'a.mlmdirektpraemie',
'a.keineeinzelartikelanzeigen',
'a.mindesthaltbarkeitsdatum',
'a.letzteseriennummer',
'a.individualartikel',
'a.keinrabatterlaubt',
'a.rabatt',
'a.rabatt_prozent',
'a.geraet',
'a.serviceartikel',
'a.autoabgleicherlaubt',
'a.pseudopreis',
'a.freigabenotwendig',
'a.freigaberegel',
'a.nachbestellt',
'a.ean',
'a.mlmpunkte',
'a.mlmbonuspunkte',
'a.mlmkeinepunkteeigenkauf',
//'a.shop', // Altlasten; wird zukünftig über artikel_shop gemacht
//'a.shop2',
//'a.shop3',
//'a.usereditid',
//'a.useredittimestamp',
'a.einheit',
'a.webid',
'a.lieferzeitmanuell_en',
'a.variante',
'a.variante_von',
'a.produktioninfo',
'a.sonderaktion',
'a.sonderaktion_en',
'a.autolagerlampe',
'a.leerfeld',
'a.zolltarifnummer',
'a.herkunftsland',
'a.laenge',
'a.breite',
'a.hoehe',
'a.gebuehr',
'a.pseudolager',
'a.downloadartikel',
'a.matrixprodukt',
'a.steuer_erloese_inland_normal',
'a.steuer_aufwendung_inland_normal',
'a.steuer_erloese_inland_ermaessigt',
'a.steuer_aufwendung_inland_ermaessigt',
'a.steuer_erloese_inland_steuerfrei',
'a.steuer_aufwendung_inland_steuerfrei',
'a.steuer_erloese_inland_innergemeinschaftlich',
'a.steuer_aufwendung_inland_innergemeinschaftlich',
'a.steuer_erloese_inland_eunormal',
'a.steuer_erloese_inland_nichtsteuerbar',
'a.steuer_erloese_inland_euermaessigt',
'a.steuer_aufwendung_inland_nichtsteuerbar',
'a.steuer_aufwendung_inland_eunormal',
'a.steuer_aufwendung_inland_euermaessigt',
'a.steuer_erloese_inland_export',
'a.steuer_aufwendung_inland_import',
'a.steuer_art_produkt',
'a.steuer_art_produkt_download',
'a.metadescription_de',
'a.metadescription_en',
'a.metakeywords_de',
'a.metakeywords_en',
'a.anabregs_text_en',
'a.externeproduktion',
'a.bildvorschau',
'a.inventursperre',
'a.variante_kopie',
'a.unikat',
'a.generierenummerbeioption',
'a.allelieferanten',
'a.tagespreise',
'a.rohstoffe',
'a.ohnepreisimpdf',
'a.provisionssperre',
'a.dienstleistung',
'a.inventurekaktiv',
'a.inventurek',
'a.hinweis_einfuegen',
'a.etikettautodruck',
'a.lagerkorrekturwert',
'a.autodrucketikett',
'a.steuertext_innergemeinschaftlich',
'a.steuertext_export',
'a.formelmenge',
'a.formelpreis',
'a.ursprungsregion',
'a.bestandalternativartikel',
'a.metatitle_de',
'a.metatitle_en',
'a.vkmeldungunterdruecken',
'a.altersfreigabe',
'a.unikatbeikopie',
'a.steuergruppe',
'a.keinskonto',
'a.berechneterek',
'a.verwendeberechneterek',
'a.berechneterekwaehrung',
'a.artikelautokalkulation',
'a.artikelabschliessenkalkulation',
'a.artikelfifokalkulation',
'a.freifeld1',
'a.freifeld2',
'a.freifeld3',
'a.freifeld4',
'a.freifeld5',
'a.freifeld6',
'a.freifeld7',
'a.freifeld8',
'a.freifeld9',
'a.freifeld10',
'a.freifeld11',
'a.freifeld12',
'a.freifeld13',
'a.freifeld14',
'a.freifeld15',
'a.freifeld16',
'a.freifeld17',
'a.freifeld18',
'a.freifeld19',
'a.freifeld20',
'a.freifeld21',
'a.freifeld22',
'a.freifeld23',
'a.freifeld24',
'a.freifeld25',
'a.freifeld26',
'a.freifeld27',
'a.freifeld28',
'a.freifeld29',
'a.freifeld30',
'a.freifeld31',
'a.freifeld32',
'a.freifeld33',
'a.freifeld34',
'a.freifeld35',
'a.freifeld36',
'a.freifeld37',
'a.freifeld38',
'a.freifeld39',
'a.freifeld40',
])
->from(self::TABLE_NAME . ' AS a')
->where('a.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('a.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('a.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,157 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class ArticleSubscriptionGroupResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'abrechnungsartikel_gruppe';
/** @var array $inputMapping */
protected $inputMapping = [
'beschreibung' => 'beschreibung2',
'bezeichnung' => 'beschreibung',
'rabatt' => 'rabatt',
'gruppensumme' => 'gruppensumme',
'projekt' => 'projekt',
'reihenfolge' => 'sort',
];
/**
* @param array $inputValues
* @param array|null $inputMapping
*
* @return Result\ItemResult
*/
public function insert($inputValues, $inputMapping = null)
{
$inputValues['extrarechnung'] = 0;
return parent::insert($inputValues, $this->inputMapping);
}
/**
* @param int $id
* @param array $inputVars
* @param array|null $inputMapping
*
* @return Result\ItemResult
*/
public function edit($id, $inputVars, $inputMapping = null)
{
return parent::edit($id, $inputVars, $this->inputMapping);
}
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung' => 'g.beschreibung %LIKE%',
'bezeichnung_equals' => 'g.beschreibung LIKE',
'bezeichnung_startswith' => 'g.beschreibung LIKE%',
'bezeichnung_endswith' => 'g.beschreibung %LIKE',
'gruppensumme' => 'g.gruppensumme =',
'rabatt' => 'g.rabatt =',
'rabatt_gt' => 'g.rabatt >',
'rabatt_gte' => 'g.rabatt >=',
'rabatt_lt' => 'g.rabatt <',
'rabatt_lte' => 'g.rabatt <=',
]);
$this->registerSortingParams([
'bezeichnung' => 'g.beschreibung',
'reihenfolge' => 'g.sort',
'rabatt' => 'g.rabatt',
]);
$this->registerValidationRules([
'id' => 'not_present',
'bezeichnung' => 'required',
'rabatt' => 'decimal',
'reihenfolge' => 'numeric',
'projekt' => 'numeric',
'gruppensumme' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'g.id',
'g.beschreibung AS bezeichnung',
'g.beschreibung2 AS beschreibung',
'g.rabatt',
'g.gruppensumme',
'g.projekt',
'g.sort AS reihenfolge',
])
->from(self::TABLE_NAME . ' AS g')
->where('g.extrarechnung = 0'); // 0 = Gemeinsame Rechnung; 1 = Eigene Rechnung; 2 = Sammelrechnung
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('g.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('g.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,249 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class ArticleSubscriptionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'abrechnungsartikel';
/** @var array $inputMapping */
protected $inputMapping = [
'reihenfolge' => 'sort',
'beschreibung_ersetzen' => 'beschreibungersetzten',
'abgerechnet_bis' => 'abgerechnetbis',
'dokumenttyp' => 'dokument',
];
/**
* @param array $inputValues
* @param array|null $inputMapping
*
* @return Result\ItemResult
*/
public function insert($inputValues, $inputMapping = null)
{
return parent::insert($inputValues, $this->inputMapping);
}
/**
* @param int $id
* @param array $inputVars
* @param array|null $inputMapping
*
* @return Result\ItemResult
*/
public function edit($id, $inputVars, $inputMapping = null)
{
return parent::edit($id, $inputVars, $this->inputMapping);
}
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'waehrung' => 'aa.waehrung =',
'preisart' => 'aa.preisart =',
'dokumenttyp' => 'aa.dokument =',
'gruppe' => 'aa.gruppe =',
'artikel' => 'aa.artikel =',
'adresse' => 'aa.adresse =',
'kundennummer' => 'ad.kundennummer =',
'projekt' => 'aa.projekt =',
'bezeichnung' => 'aa.beschreibung %LIKE%',
'bezeichnung_equals' => 'aa.beschreibung LIKE',
'bezeichnung_startswith' => 'aa.beschreibung LIKE%',
'bezeichnung_endswith' => 'aa.beschreibung %LIKE',
'rabatt' => 'aa.rabatt =',
'rabatt_gt' => 'aa.rabatt >',
'rabatt_gte' => 'aa.rabatt >=',
'rabatt_lt' => 'aa.rabatt <',
'rabatt_lte' => 'aa.rabatt <=',
'preis' => 'aa.preis =',
'preis_gt' => 'aa.preis >',
'preis_gte' => 'aa.preis >=',
'preis_lt' => 'aa.preis <',
'preis_lte' => 'aa.preis <=',
'menge' => 'aa.menge =',
'menge_gt' => 'aa.menge >',
'menge_gte' => 'aa.menge >=',
'menge_lt' => 'aa.menge <',
'menge_lte' => 'aa.menge <=',
'startdatum' => 'aa.startdatum LIKE',
'startdatum_gt' => 'aa.startdatum >',
'startdatum_gte' => 'aa.startdatum >=',
'startdatum_lt' => 'aa.startdatum <',
'startdatum_lte' => 'aa.startdatum <=',
'enddatum' => 'aa.enddatum LIKE',
'enddatum_gt' => 'aa.enddatum >',
'enddatum_gte' => 'aa.enddatum >=',
'enddatum_lt' => 'aa.enddatum <',
'enddatum_lte' => 'aa.enddatum <=',
'abgerechnet_bis' => 'aa.abgerechnetbis LIKE',
'abgerechnet_bis_gt' => 'aa.abgerechnetbis >',
'abgerechnet_bis_gte' => 'aa.abgerechnetbis >=',
'abgerechnet_bis_lt' => 'aa.abgerechnetbis <',
'abgerechnet_bis_lte' => 'aa.abgerechnetbis <=',
]);
$this->registerSortingParams([
'bezeichnung' => 'aa.bezeichnung',
'reihenfolge' => 'aa.sort',
'rabatt' => 'aa.rabatt',
'preis' => 'aa.preis',
'menge' => 'aa.menge',
'startdatum' => 'aa.startdatum',
'enddatum' => 'aa.enddatum',
'abgerechnet_bis' => 'aa.abgerechnetbis',
]);
$this->registerValidationRules([
'id' => 'not_present',
'abgerechnet_bis' => 'not_present',
'beschreibung_ersetzen' => 'in:1,0',
'startdatum' => 'date:Y-m-d',
'enddatum' => 'date:Y-m-d',
'zahlzyklus' => 'numeric',
'preis' => 'decimal',
'rabatt' => 'decimal',
'menge' => 'decimal',
'waehrung' => 'upper|length:3',
'preisart' => 'in:monat,monatx,jahr,wochen,einmalig,30tage,360tage',
'dokumenttyp' => 'in:rechnung,auftrag',
'projekt' => 'numeric',
'artikel' => 'numeric|db_value:artikel,id',
'adresse' => 'numeric',
'gruppe' => 'numeric',
'reihenfolge' => 'numeric',
]);
$this->registerIncludes([
'artikel' => [
'key' => 'artikel',
'resource' => ArticleResource::class,
'columns' => [
'a.id',
'a.nummer',
'a.name_de',
'a.name_en',
],
],
'gruppe' => [
'key' => 'gruppe',
'resource' => ArticleSubscriptionGroupResource::class,
'columns' => [
'g.id',
'g.beschreibung AS bezeichnung',
'g.beschreibung2 AS beschreibung',
'g.rabatt',
'g.gruppensumme',
'g.sort AS reihenfolge',
],
],
'adresse' => [
'key' => 'adresse',
'resource' => AddressResource::class,
'columns' => [
'a.id',
'a.typ',
'a.name',
'a.ansprechpartner',
'a.kundennummer',
],
],
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'aa.id',
'aa.bezeichnung',
'aa.beschreibung',
'aa.beschreibungersetzten AS beschreibung_ersetzen',
//'aa.nummer',
'aa.startdatum',
'aa.enddatum',
'aa.abgerechnetbis AS abgerechnet_bis',
'aa.zahlzyklus',
'aa.preis',
'aa.rabatt',
'aa.waehrung',
'aa.menge',
'aa.preisart', // monat, monatx, jahr, wochen, einmalig
'aa.dokument AS dokumenttyp', // rechnung, auftrag
'aa.artikel',
'aa.gruppe',
'aa.adresse',
'ad.kundennummer',
'aa.sort AS reihenfolge',
'aa.projekt',
])
->from(self::TABLE_NAME . ' AS aa')
->leftJoin('adresse AS ad', 'aa.adresse != 0 AND aa.adresse = ad.id');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('aa.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('aa.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return DeleteQuery
*/
protected function deleteQuery()
{
return $this->db->delete()->from(self::TABLE_NAME)->where('id = :id');
}
}
@@ -0,0 +1,95 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class CountryResource extends AbstractResource
{
const TABLE_NAME = 'laender';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung_de' => 'l.bezeichnung_de %LIKE%',
'bezeichnung_en' => 'l.bezeichnung_de %LIKE%',
'iso' => 'l.iso =',
'eu' => 'l.eu =',
'id_ext' => 'am.id_ext =', // @todo
]);
$this->registerSortingParams([
'bezeichnung' => 'l.bezeichnung_de',
'bezeichnung_de' => 'l.bezeichnung_de',
'bezeichnung_en' => 'l.bezeichnung_en',
'iso' => 'l.iso',
'eu' => 'l.eu',
]);
$this->registerValidationRules([
'id' => 'not_present',
'id_ext' => 'not_present', // @todo
'bezeichnung_de' => 'required|unique:laender,bezeichnung_de',
'bezeichnung_en' => 'required|unique:laender,bezeichnung_en',
'iso' => 'required|upper|length:2|unique:laender,iso',
'eu' => 'boolean',
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols(['l.*', 'am.id_ext'])->from(self::TABLE_NAME . ' AS l')
->leftJoin(
'api_mapping AS am',
'am.id_int = l.id AND am.tabelle = ' . $this->db->escapeString(self::TABLE_NAME)
);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('l.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('l.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,167 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class CrmDocumentResource extends AbstractResource
{
const TABLE_NAME = 'dokumente';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'typ' => 'd.typ %LIKE%',
'typ_equals' => 'd.typ LIKE',
'typ_exakt' => 'd.typ LIKE',
'betreff' => 'd.betreff %LIKE%',
'betreff_equals' => 'd.betreff LIKE',
'betreff_exakt' => 'd.betreff LIKE',
'projekt' => 'd.projekt =',
'adresse_from' => 'd.adresse_from =',
'adresse_to' => 'd.adresse_to =',
'deleted' => 'd.deleted =',
]);
$this->registerValidationRules([
'id' => 'not_present',
'typ' => 'required|in:email,brief,telefon,notiz',
'betreff' => 'required',
'projekt' => 'numeric',
'adresse_from' => 'numeric',
'adresse_to' => 'numeric',
'signatur' => 'numeric',
'fax' => 'boolean',
'printer' => 'boolean',
'sent' => 'boolean',
'deleted' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
'adresse_to' => [
'key' => 'adresse_to',
'resource' => AddressResource::class,
'columns' => [
'a.id',
'a.name',
'a.email',
'a.strasse',
'a.plz',
'a.ort',
'a.land',
'a.ansprechpartner',
],
],
'adresse_from' => [
'key' => 'adresse_from',
'resource' => AddressResource::class,
'columns' => [
'a.id',
'a.name',
'a.email',
'a.strasse',
'a.plz',
'a.ort',
'a.land',
'a.ansprechpartner',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'd.id',
'd.adresse_from',
'd.adresse_to',
'd.typ',
'd.von',
'd.an',
'd.email_an',
'd.send_as',
'd.email',
'd.email_cc',
'd.email_bcc',
'd.bearbeiter',
'd.email_an',
'd.firma_an',
'd.adresse',
'd.ansprechpartner',
'd.plz',
'd.ort',
'd.land',
'd.datum',
'd.uhrzeit',
'd.betreff',
'd.content',
'd.projekt',
'd.internebezeichnung',
'd.signatur',
'd.fax',
'd.sent',
'd.printer',
'd.deleted',
])->from(self::TABLE_NAME . ' AS d');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('d.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('d.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return DeleteQuery
*/
protected function deleteQuery()
{
return $this->db->delete()->from(self::TABLE_NAME)->where('id = :id');
}
}
@@ -0,0 +1,194 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Aura\SqlQuery\Exception;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
class DeliveryAddressResource extends AbstractResource
{
const TABLE_NAME = 'lieferadressen';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'adresse' => 'l.adresse =',
'typ' => 'l.typ =',
'name' => 'l.name %LIKE%',
'name_equals' => 'l.name LIKE',
'name_startswith' => 'l.name LIKE%',
'name_endswith' => 'l.name %LIKE',
'standardlieferadresse' => 'l.standardlieferadresse =',
'land' => 'l.land =',
'id_ext' => 'am.id_ext =',
]);
$this->registerSortingParams([
'typ' => 'l.typ',
'name' => 'l.name',
'plz' => 'l.plz',
'land' => 'l.land',
]);
$this->registerValidationRules([
'id' => 'not_present',
'id_ext' => 'not_present',
'name' => 'required',
'adresse' => 'numeric|db_value:adresse,id',
'typ' => 'db_value:adresse_typ,type',
'land' => 'upper|length:2|db_value:laender,iso',
'ust_befreit' => 'in:0,1,2,3',
'standardlieferadresse' => 'in:0,1',
]);
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'l.id',
'l.typ',
//'l.sprache', // Nicht änderbar über Formular
'l.name',
'l.abteilung',
'l.unterabteilung',
'l.strasse',
'l.ort',
'l.plz',
'l.land',
'l.telefon',
'l.telefax',
'l.email',
//'l.sonstiges', // Nicht änderbar über Formular
'l.adresszusatz',
//'l.steuer', // Nicht änderbar über Formular
'l.adresse',
//'l.ansprechpartner', // Nicht änderbar über Formular
'l.standardlieferadresse',
'l.gln',
'l.ustid',
'l.lieferbedingung',
'l.ust_befreit',
'l.interne_bemerkung',
'am.id_ext',
])
->from(self::TABLE_NAME . ' AS l')
->leftJoin(
'api_mapping AS am',
'am.id_int = l.id AND am.tabelle = ' . $this->db->escapeString(self::TABLE_NAME)
);
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('l.id = :id');
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('l.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @inheritdoc
*/
public function insert($inputVars)
{
// SQL-Fehler umgehen: Field 'sprache' doesn't have a default value
if (!isset($inputVars['abteilung'])) { $inputVars['abteilung'] = ''; }
if (!isset($inputVars['unterabteilung'])) { $inputVars['unterabteilung'] = ''; }
if (!isset($inputVars['strasse'])) { $inputVars['strasse'] = ''; }
if (!isset($inputVars['ort'])) { $inputVars['ort'] = ''; }
if (!isset($inputVars['plz'])) { $inputVars['plz'] = ''; }
if (!isset($inputVars['telefon'])) { $inputVars['telefon'] = ''; }
if (!isset($inputVars['telefax'])) { $inputVars['telefax'] = ''; }
if (!isset($inputVars['email'])) { $inputVars['email'] = ''; }
if (!isset($inputVars['steuer'])) { $inputVars['steuer'] = ''; }
if (!isset($inputVars['sprache'])) { $inputVars['sprache'] = ''; }
if (!isset($inputVars['sonstiges'])) { $inputVars['sonstiges'] = ''; }
if (!isset($inputVars['adresszusatz'])) { $inputVars['adresszusatz'] = ''; }
if (!isset($inputVars['lieferbedingung'])) { $inputVars['lieferbedingung'] = ''; }
// Angelegte Daten aus dem Result holen
$result = parent::insert($inputVars);
$data = $result->getData();
// Es darf nur eine Standard-Lieferadresse pro Hauptadresse geben!
if ((int)$data['standardlieferadresse'] === 1) {
$addressId = (int)$data['adresse'];
$deliveryAddressId = (int)$data['id'];
if ($addressId === 0) {
throw new InvalidArgumentException('AdressID can not be empty');
}
if ($deliveryAddressId === 0) {
throw new InvalidArgumentException('ID can not be empty');
}
// Vorhandene Standard-Lieferadresse zur "nicht-Standard"-Lieferadresse machen
$updateQuery = $this->db->update()
->table(self::TABLE_NAME)
->cols(['standardlieferadresse' => 0])
->where('standardlieferadresse = :eins')
->where('adresse = :adresse')
->where('id != :id')
->bindValues([
'eins' => 1,
'adresse' => $addressId,
'id' => $deliveryAddressId,
]);
$this->db->perform(
$updateQuery->getStatement(),
$updateQuery->getBindValues()
);
}
return $result;
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return DeleteQuery
*/
protected function deleteQuery()
{
return $this->db->delete()->from(self::TABLE_NAME)->where('id = :id');
}
}
@@ -0,0 +1,177 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für Gutschriften-Positionen
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentCreditNotePositionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'gutschrift_position';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'sort' => 'gupos.sort',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('gupos.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'gupos.id',
//'gupos.gutschrift', // Index
'gupos.projekt',
'gupos.artikel', // Index
'gupos.bezeichnung',
'gupos.beschreibung',
//'gupos.internerkommentar',
'gupos.nummer',
'gupos.menge',
'gupos.einheit',
'gupos.preis',
'gupos.waehrung',
'gupos.lieferdatum',
'gupos.vpe',
//'gupos.sort',
//'gupos.status',
'gupos.umsatzsteuer',
'gupos.bemerkung',
'gupos.artikelnummerkunde',
//'gupos.logdatei',
//'gupos.explodiert_parent_artikel',
//'gupos.keinrabatterlaubt',
//'gupos.grundrabatt',
//'gupos.rabattsync',
//'gupos.rabatt1',
//'gupos.rabatt2',
//'gupos.rabatt3',
//'gupos.rabatt4',
//'gupos.rabatt5',
'gupos.rabatt',
'gupos.zolltarifnummer',
'gupos.herkunftsland',
'gupos.lieferdatumkw',
'gupos.auftrag_position_id',
'gupos.teilprojekt',
'gupos.kostenstelle',
'gupos.steuersatz',
'gupos.steuertext',
//'gupos.erloese',
//'gupos.erloesefestschreiben',
'gupos.einkaufspreiswaehrung',
'gupos.einkaufspreis',
'gupos.einkaufspreisurspruenglich',
//'gupos.einkaufspreisid',
//'gupos.ekwaehrung',
//'gupos.deckungsbeitrag',
//'gupos.freifeld1',
//'gupos.freifeld2',
//'gupos.freifeld3',
//'gupos.freifeld4',
//'gupos.freifeld5',
//'gupos.freifeld6',
//'gupos.freifeld7',
//'gupos.freifeld8',
//'gupos.freifeld9',
//'gupos.freifeld10',
//'gupos.freifeld11',
//'gupos.freifeld12',
//'gupos.freifeld13',
//'gupos.freifeld14',
//'gupos.freifeld15',
//'gupos.freifeld16',
//'gupos.freifeld17',
//'gupos.freifeld18',
//'gupos.freifeld19',
//'gupos.freifeld20',
//'gupos.freifeld21',
//'gupos.freifeld22',
//'gupos.freifeld23',
//'gupos.freifeld24',
//'gupos.freifeld25',
//'gupos.freifeld26',
//'gupos.freifeld27',
//'gupos.freifeld28',
//'gupos.freifeld29',
//'gupos.freifeld30',
//'gupos.freifeld31',
//'gupos.freifeld32',
//'gupos.freifeld33',
//'gupos.freifeld34',
//'gupos.freifeld35',
//'gupos.freifeld36',
//'gupos.freifeld37',
//'gupos.freifeld38',
//'gupos.freifeld39',
//'gupos.freifeld40',
//'gupos.formelmenge',
//'gupos.formelpreis',
'gupos.ohnepreis',
'gupos.skontobetrag',
'gupos.steuerbetrag',
'gupos.skontosperre',
'gupos.ausblenden_im_pdf',
//'gupos.umsatz_netto_einzeln',
//'gupos.umsatz_netto_gesamt',
//'gupos.umsatz_brutto_einzeln',
//'gupos.umsatz_brutto_gesamt',
])
->from(self::TABLE_NAME . ' AS gupos');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('gupos.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für das Gutschrift-Protokoll
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentCreditNoteProtocolResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'gutschrift_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'zeit' => 'guproto.zeit',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('guproto.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'guproto.id',
'guproto.gutschrift',
'guproto.zeit',
'guproto.bearbeiter',
'guproto.grund',
])
->from(self::TABLE_NAME . ' AS guproto');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('guproto.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,255 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource für Gutschriften/Stornorechnungen
*/
class DocumentCreditNoteResource extends AbstractResource
{
/** @var string */
const TABLE_NAME = 'gutschrift';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'status' => 'gu.status LIKE',
'belegnr' => 'gu.belegnr %LIKE%',
'belegnr_equals' => 'gu.belegnr LIKE',
'belegnr_startswith' => 'gu.belegnr LIKE%',
'belegnr_endswith' => 'gu.belegnr %LIKE',
'kundennummer' => 'gu.kundennummer %LIKE%',
'kundennummer_equals' => 'gu.kundennummer LIKE',
'kundennummer_startswith' => 'gu.kundennummer LIKE%',
'kundennummer_endswith' => 'gu.kundennummer %LIKE',
'datum' => 'gu.datum =',
'datum_gt' => 'gu.datum >',
'datum_gte' => 'gu.datum >=',
'datum_lt' => 'gu.datum <',
'datum_lte' => 'gu.datum <=',
'rechnung' => 'gu.rechnung LIKE',
'rechnungid' => 'gu.rechnungid =',
'projekt' => 'gu.projekt =',
]);
$this->registerSortingParams([
'belegnr' => 'gu.belegnr',
'datum' => 'gu.datum',
]);
$this->registerIncludes([
'positionen' => [
'key' => 'positionen',
'resource' => DocumentCreditNotePositionResource::class,
'filter' => [
[
'property' => 'gutschrift',
'value' => ':id',
],
],
'sort' => [
'sort' => 'ASC',
],
],
'protokoll' => [
'key' => 'protokoll',
'resource' => DocumentCreditNoteProtocolResource::class,
'columns' => [
'guproto.id',
'guproto.zeit',
'guproto.bearbeiter',
'guproto.grund',
],
'filter' => [
[
'property' => 'gutschrift',
'value' => ':id',
],
],
'sort' => [
'zeit' => 'ASC',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('gu.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'gu.id',
'gu.firma',
'gu.projekt', // Index
'gu.status', // Index
'gu.anlegeart',
'gu.belegnr', // Index
'gu.datum', // Index
'gu.rechnung',
'gu.rechnungid',
'gu.stornorechnung',
'gu.kundennummer',
'gu.bearbeiter',
'gu.bearbeiterid',
'gu.freitext',
'gu.internebemerkung',
'gu.adresse', // Index
'gu.typ',
'gu.name',
'gu.titel',
'gu.ansprechpartnerid',
'gu.ansprechpartner',
'gu.abteilung',
'gu.unterabteilung',
'gu.adresszusatz',
'gu.strasse',
'gu.plz',
'gu.ort',
'gu.land',
'gu.bundesstaat',
'gu.telefon',
'gu.telefax',
'gu.email',
'gu.anschreiben',
//'gu.betreff',
//'gu.lieferschein',
'gu.versandart',
'gu.lieferdatum',
'gu.buchhaltung',
'gu.zahlungsweise',
'gu.zahlungsstatus',
'gu.ist',
'gu.soll',
'gu.zahlungszieltage',
'gu.zahlungszieltageskonto',
'gu.zahlungszielskonto',
'gu.gesamtsumme',
//'gu.bank_inhaber',
//'gu.bank_institut',
//'gu.bank_blz',
//'gu.bank_konto',
//'gu.kreditkarte_typ',
//'gu.kreditkarte_inhaber',
//'gu.kreditkarte_nummer',
//'gu.kreditkarte_pruefnummer',
//'gu.kreditkarte_monat',
//'gu.kreditkarte_jahr',
//'gu.paypalaccount',
'gu.versendet',
'gu.versendet_am',
'gu.versendet_per',
'gu.versendet_durch',
//'gu.inbearbeitung',
//'gu.logdatei',
'gu.manuell_vorabbezahlt',
'gu.manuell_vorabbezahlt_hinweis',
'gu.nicht_umsatzmindernd',
//'gu.dta_datei',
//'gu.dta_datei_verband',
//'gu.deckungsbeitragcalc',
//'gu.deckungsbeitrag',
'gu.erloes_netto',
'gu.umsatz_netto',
'gu.vertriebid', // Index
'gu.vertrieb',
'gu.aktion',
'gu.provision',
'gu.provision_summe',
//'gu.gruppe', // Index
'gu.ihrebestellnummer',
//'gu.usereditid', // Index
//'gu.useredittimestamp',
//'gu.realrabatt',
'gu.rabatt',
//'gu.rabatt1',
//'gu.rabatt2',
//'gu.rabatt3',
//'gu.rabatt4',
//'gu.rabatt5',
'gu.steuersatz_normal',
'gu.steuersatz_zwischen',
'gu.steuersatz_ermaessigt',
'gu.steuersatz_starkermaessigt',
'gu.steuersatz_dienstleistung',
'gu.ustid',
'gu.ustbrief',
'gu.ustbrief_eingang',
'gu.ustbrief_eingang_am',
'gu.ust_befreit',
'gu.waehrung',
'gu.keinsteuersatz',
//'gu.schreibschutz',
//'gu.pdfarchiviert',
//'gu.pdfarchiviertversion',
//'gu.ohne_briefpapier',
//'gu.lieferid',
//'gu.projektfiliale',
//'gu.zuarchivieren',
'gu.internebezeichnung',
//'gu.angelegtam',
'gu.sprache',
'gu.gln',
//'gu.deliverythresholdvatid',
'gu.kurs',
'gu.ohne_artikeltext',
'gu.anzeigesteuer',
'gu.kostenstelle',
'gu.bodyzusatz',
'gu.lieferbedingung',
'gu.skontobetrag',
'gu.skontoberechnet',
'gu.extsoll',
])->from(self::TABLE_NAME . ' AS gu');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('gu.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,159 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für Lieferschein-Positionen
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentDeliveryNotePositionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'lieferschein_position';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'sort' => 'lipos.sort',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('lipos.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'lipos.id',
//'lipos.lieferschein', // Index
'lipos.projekt',
'lipos.artikel', // Index
'lipos.bezeichnung',
'lipos.beschreibung',
//'lipos.internerkommentar',
'lipos.nummer',
'lipos.menge',
'lipos.einheit',
'lipos.vpe',
'lipos.lieferdatum',
'lipos.lieferdatumkw',
'lipos.artikelnummerkunde',
'lipos.kostenlos',
//'lipos.sort',
//'lipos.status',
//'lipos.ausblenden_im_pdf ',
'lipos.bemerkung',
'lipos.geliefert',
'lipos.abgerechnet',
//'lipos.logdatei',
//'lipos.lagertext',
//'lipos.auftrag_position_id', // Index
//'lipos.teilprojekt',
//'lipos.freifeld1',
//'lipos.freifeld2',
//'lipos.freifeld3',
//'lipos.freifeld4',
//'lipos.freifeld5',
//'lipos.freifeld6',
//'lipos.freifeld7',
//'lipos.freifeld8',
//'lipos.freifeld9',
//'lipos.freifeld10',
//'lipos.freifeld11',
//'lipos.freifeld12',
//'lipos.freifeld13',
//'lipos.freifeld14',
//'lipos.freifeld15',
//'lipos.freifeld16',
//'lipos.freifeld17',
//'lipos.freifeld18',
//'lipos.freifeld19',
//'lipos.freifeld20',
//'lipos.freifeld21',
//'lipos.freifeld22',
//'lipos.freifeld23',
//'lipos.freifeld24',
//'lipos.freifeld25',
//'lipos.freifeld26',
//'lipos.freifeld27',
//'lipos.freifeld28',
//'lipos.freifeld29',
//'lipos.freifeld30',
//'lipos.freifeld31',
//'lipos.freifeld32',
//'lipos.freifeld33',
//'lipos.freifeld34',
//'lipos.freifeld35',
//'lipos.freifeld36',
//'lipos.freifeld37',
//'lipos.freifeld38',
//'lipos.freifeld39',
//'lipos.freifeld40',
'lipos.seriennummer',
'lipos.herkunftsland',
'lipos.zolltarifnummer',
'lipos.zolleinzelwert',
'lipos.zollgesamtwert',
'lipos.zollwaehrung',
'lipos.zolleinzelgewicht',
'lipos.zollgesamtgewicht',
'lipos.nve',
'lipos.packstueck',
'lipos.vpemenge',
'lipos.einzelstueckmenge',
//'lipos.explodiert_parent',
//'lipos.explodiert_parent_artikel',
])
->from(self::TABLE_NAME . ' AS lipos');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('lipos.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für das Lieferschein-Protokoll
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Incldudes verwendet.
*/
class DocumentDeliveryNoteProtocolResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'lieferschein_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'zeit' => 'liproto.zeit',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('liproto.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'liproto.id',
'liproto.lieferschein',
'liproto.zeit',
'liproto.bearbeiter',
'liproto.grund',
])
->from(self::TABLE_NAME . ' AS liproto');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('liproto.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,248 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource für Lieferscheine
*/
class DocumentDeliveryNoteResource extends AbstractResource
{
/** @var string */
const TABLE_NAME = 'lieferschein';
/**
* @param array $filter
* @param array $sorting
* @param array $columns
* @param array $includes
* @param int $page
* @param int $paging
*
* @return Result\CollectionResult
*/
public function getList(
array $filter = [],
array $sorting = [],
array $columns = [],
array $includes = [],
$page = 1,
$paging = 20
) {
// Filter für Auftragsnummer über Auftrags-ID verknüpfen
if (isset($filter['auftrag']) && !isset($filter['auftragid'])) {
$select = $this->db->select()
->cols(['a.id'])
->from('auftrag AS a')
->where('a.belegnr = ?', $filter['auftrag']);
if (isset($filter['projekt'])) {
$select->where('a.projekt = ?', $filter['projekt']);
}
$orderId = $this->db->fetchValue($select->getStatement(), $select->getBindValues());
if ($orderId > 0) {
$filter['auftragid'] = $orderId;
unset($filter['auftrag']);
}
}
return parent::getList($filter, $sorting, $columns, $includes, $page, $paging);
}
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'status' => 'li.status LIKE',
'belegnr' => 'li.belegnr %LIKE%',
'belegnr_equals' => 'li.belegnr LIKE',
'belegnr_startswith' => 'li.belegnr LIKE%',
'belegnr_endswith' => 'li.belegnr %LIKE',
'kundennummer' => 'li.kundennummer %LIKE%',
'kundennummer_equals' => 'li.kundennummer LIKE',
'kundennummer_startswith' => 'li.kundennummer LIKE%',
'kundennummer_endswith' => 'li.kundennummer %LIKE',
'datum' => 'li.datum =',
'datum_gt' => 'li.datum >',
'datum_gte' => 'li.datum >=',
'datum_lt' => 'li.datum <',
'datum_lte' => 'li.datum <=',
'auftrag' => 'li.auftrag LIKE',
'auftragid' => 'li.auftragid =',
'projekt' => 'li.projekt =',
]);
$this->registerSortingParams([
'belegnr' => 'li.belegnr',
'datum' => 'li.datum',
]);
$this->registerIncludes([
'positionen' => [
'key' => 'positionen',
'resource' => DocumentDeliveryNotePositionResource::class,
'filter' => [
[
'property' => 'lieferschein',
'value' => ':id',
],
],
'sort' => [
'sort' => 'ASC',
],
],
'protokoll' => [
'key' => 'protokoll',
'resource' => DocumentDeliveryNoteProtocolResource::class,
'filter' => [
[
'property' => 'lieferschein',
'value' => ':id',
],
],
'columns' => [
'liproto.id',
'liproto.zeit',
'liproto.bearbeiter',
'liproto.grund',
],
'sort' => [
'zeit' => 'ASC',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('li.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'li.id',#
'li.firma',#
'li.projekt',# // Index
'li.status',# // Index
'li.lieferscheinart',#
'li.belegnr',# // Index
'li.kundennummer',#
'li.ihrebestellnummer',#
'li.datum',# // Index
'li.auftrag',#
'li.auftragid',# // Index
'li.freitext',#
'li.adresse',# // Index
'li.typ',#
'li.name',#
'li.titel',#
'li.ansprechpartnerid',#
'li.ansprechpartner',#
'li.abteilung',#
'li.unterabteilung',#
'li.adresszusatz',#
'li.strasse',#
'li.plz',#
'li.ort',#
'li.land',# // Index
'li.bundesstaat',#
'li.telefon',#
'li.telefax',#
'li.email',#
'li.anschreiben',#
//'li.betreff',#
//'li.vertriebid',# // Index
//'li.vertrieb',#
'li.versandart',#
'li.versand',#
'li.versendet',#
'li.versendet_am',#
'li.versendet_per',#
'li.versendet_durch',#
//'li.inbearbeitung_user',#
//'li.logdatei',#
//'li.schreibschutz',#
'li.ustid',#
'li.ust_befreit',#
'li.usereditid',# // Index
'li.useredittimestamp',#
'li.lieferantenretoure',#
'li.lieferantenretoureinfo',#
'li.lieferant',#
'li.pdfarchiviert',#
'li.pdfarchiviertversion',#
'li.internebemerkung',#
'li.ohne_briefpapier',#
'li.lieferid',#
'li.projektfiliale',#
'li.projektfiliale_eingelagert',#
'li.zuarchivieren',#
'li.internebezeichnung',#
'li.kommissionierung',#
'li.sprache',#
//'li.angelegtam',#
//'li.bundesland',#
'li.gln',#
//'li.rechnungid',#
//'li.bearbeiterid',#
'li.bearbeiter',#
'li.keinerechnung',# // Index
'li.ohne_artikeltext',#
'li.abweichendebezeichnung',#
'li.kostenstelle',#
'li.bodyzusatz',#
'li.lieferbedingung',#
'li.standardlager',#
'li.kommissionskonsignationslager',#
'li.teillieferungvon',#
'li.teillieferungnummer',#
'li.kiste',#
])->from(self::TABLE_NAME . ' AS li');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('li.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,181 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für Rechnungs-Positionen
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentInvoicePositionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'rechnung_position';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'sort' => 'repos.sort',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('repos.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'repos.id',
//'repos.rechnung', // Index
'repos.projekt',
'repos.artikel', // Index
'repos.bezeichnung',
'repos.beschreibung',
//'repos.internerkommentar',
'repos.nummer',
'repos.menge',
'repos.preis',
'repos.waehrung',
'repos.lieferdatum',
'repos.vpe',
//'repos.sort',
//'repos.status',
'repos.umsatzsteuer',
'repos.bemerkung',
//'repos.logdatei',
//'repos.explodiert_parent_artikel',
//'repos.punkte',
//'repos.bonuspunkte',
//'repos.mlmdirektpraemie',
//'repos.mlm_abgerechnet',
//'repos.keinrabatterlaubt',
//'repos.grundrabatt',
//'repos.rabattsync',
//'repos.rabatt1',
//'repos.rabatt2',
//'repos.rabatt3',
//'repos.rabatt4',
//'repos.rabatt5',
'repos.einheit',
'repos.rabatt',
'repos.zolltarifnummer',
'repos.herkunftsland',
'repos.artikelnummerkunde',
'repos.lieferdatumkw',
//'repos.auftrag_position_id', // Index
//'repos.teilprojekt',
//'repos.kostenstelle',
//'repos.erloese',
//'repos.erloesefestschreiben',
'repos.einkaufspreiswaehrung',
'repos.einkaufspreis',
'repos.einkaufspreisurspruenglich',
//'repos.einkaufspreisid',
//'repos.ekwaehrung',
//'repos.deckungsbeitrag',
//'repos.freifeld1',
//'repos.freifeld2',
//'repos.freifeld3',
//'repos.freifeld4',
//'repos.freifeld5',
//'repos.freifeld6',
//'repos.freifeld7',
//'repos.freifeld8',
//'repos.freifeld9',
//'repos.freifeld10',
//'repos.freifeld11',
//'repos.freifeld12',
//'repos.freifeld13',
//'repos.freifeld14',
//'repos.freifeld15',
//'repos.freifeld16',
//'repos.freifeld17',
//'repos.freifeld18',
//'repos.freifeld19',
//'repos.freifeld20',
//'repos.freifeld21',
//'repos.freifeld22',
//'repos.freifeld23',
//'repos.freifeld24',
//'repos.freifeld25',
//'repos.freifeld26',
//'repos.freifeld27',
//'repos.freifeld28',
//'repos.freifeld29',
//'repos.freifeld30',
//'repos.freifeld31',
//'repos.freifeld32',
//'repos.freifeld33',
//'repos.freifeld34',
//'repos.freifeld35',
//'repos.freifeld36',
//'repos.freifeld37',
//'repos.freifeld38',
//'repos.freifeld39',
//'repos.freifeld40',
//'repos.formelmenge',
//'repos.formelpreis',
'repos.ohnepreis',
'repos.steuersatz',
'repos.steuertext',
'repos.steuerbetrag',
'repos.skontobetrag',
'repos.skontosperre',
'repos.ausblenden_im_pdf',
//'repos.umsatz_netto_einzeln',
//'repos.umsatz_netto_gesamt',
//'repos.umsatz_brutto_einzeln',
//'repos.umsatz_brutto_gesamt',
])
->from(self::TABLE_NAME . ' AS repos');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('repos.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für das Rechungen-Protokoll
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentInvoiceProtocolResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'rechnung_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'zeit' => 'reproto.zeit',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('reproto.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'reproto.id',
'reproto.lieferschein',
'reproto.zeit',
'reproto.bearbeiter',
'reproto.grund',
])
->from(self::TABLE_NAME . ' AS reproto');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('reproto.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,327 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
use Xentral\Modules\Api\Resource\Result\ItemResult;
/**
* Ressource für Rechnungen
*/
class DocumentInvoiceResource extends AbstractResource
{
/** @var string */
const TABLE_NAME = 'rechnung';
/** @var string */
const POSITION_TABLE_NAME = 'rechnung_position';
/** @var string */
const PROTOCOL_TABLE_NAME = 'rechnung_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'status' => 're.status LIKE',
'belegnr' => 're.belegnr %LIKE%',
'belegnr_equals' => 're.belegnr LIKE',
'belegnr_startswith' => 're.belegnr LIKE%',
'belegnr_endswith' => 're.belegnr %LIKE',
'kundennummer' => 're.kundennummer %LIKE%',
'kundennummer_equals' => 're.kundennummer LIKE',
'kundennummer_startswith' => 're.kundennummer LIKE%',
'kundennummer_endswith' => 're.kundennummer %LIKE',
'datum' => 're.datum =',
'datum_gt' => 're.datum >',
'datum_gte' => 're.datum >=',
'datum_lt' => 're.datum <',
'datum_lte' => 're.datum <=',
'auftrag' => 're.auftrag LIKE',
'auftragid' => 're.auftragid =',
'projekt' => 're.projekt =',
]);
$this->registerSortingParams([
'belegnr' => 're.belegnr',
'datum' => 're.datum',
]);
$this->registerIncludes([
'positionen' => [
'key' => 'positionen',
'resource' => DocumentInvoicePositionResource::class,
'filter' => [
[
'property' => 'rechnung',
'value' => ':id',
],
],
'sort' => [
'sort' => 'ASC',
],
],
'protokoll' => [
'key' => 'protokoll',
'resource' => DocumentInvoiceProtocolResource::class,
'columns' => [
'reproto.id',
'reproto.zeit',
'reproto.bearbeiter',
'reproto.grund',
],
'filter' => [
[
'property' => 'rechnung',
'value' => ':id',
],
],
'sort' => [
'zeit' => 'ASC',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('re.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
're.id',
're.firma',
're.projekt', // Index
're.status', // Index
're.belegnr', // Index
're.anlegeart',
're.datum', // Index
're.auftrag',
're.auftragid', // Index
're.aborechnung',
're.kundennummer',
're.bearbeiterid',
're.bearbeiter',
're.freitext',
're.aktion',
//'re.internebemerkung',
're.adresse', // Index
're.typ',
're.name',
're.titel',
're.ansprechpartnerid',
're.ansprechpartner',
're.abteilung',
're.unterabteilung',
're.adresszusatz',
're.strasse',
're.plz',
're.ort',
're.land',
're.bundesstaat',
're.telefon',
're.telefax',
're.email',
're.anschreiben',
//'re.betreff',
//'re.lieferschein',
're.versandart',
're.lieferdatum',
're.buchhaltung',
're.zahlungsweise',
're.mahnwesenfestsetzen',
're.zahlungsstatus', // Index
're.ist',
're.soll', // Index
're.skonto_gegeben',
're.zahlungszieltage',
're.zahlungszieltageskonto',
're.zahlungszielskonto',
're.versendet',
're.versendet_am',
're.versendet_per',
're.versendet_durch',
're.versendet_mahnwesen',
're.mahnwesen',
're.mahnwesen_datum',
're.mahnwesen_gesperrt',
're.mahnwesen_internebemerkung',
're.datev_abgeschlossen',
//'re.inbearbeitung',
//'re.logdatei',
//'re.doppel',
//'re.autodruck_rz',
//'re.autodruck_periode',
//'re.autodruck_done',
//'re.autodruck_anzahlverband',
//'re.autodruck_anzahlkunde',
//'re.autodruck_mailverband',
//'re.autodruck_mailkunde',
//'re.dta_datei_verband',
//'re.dta_datei',
//'re.deckungsbeitragcalc',
//'re.deckungsbeitrag',
're.umsatz_netto',
're.erloes_netto',
//'re.vertriebid', // Index
//'re.vertrieb',
're.provision',
're.provision_summe',
//'re.gruppe', // Index
//'re.punkte',
//'re.bonuspunkte',
're.provdatum', // Index
're.ihrebestellnummer',
//'re.usereditid', // Index
//'re.useredittimestamp',
//'re.realrabatt',
're.rabatt',
're.einzugsdatum',
//'re.rabatt1',
//'re.rabatt2',
//'re.rabatt3',
//'re.rabatt4',
//'re.rabatt5',
're.forderungsverlust_datum',
're.forderungsverlust_betrag',
're.steuersatz_normal',
're.steuersatz_zwischen',
're.steuersatz_ermaessigt',
're.steuersatz_starkermaessigt',
're.steuersatz_dienstleistung',
're.ustid',
're.ust_befreit',
're.ustbrief',
're.ustbrief_eingang',
're.ustbrief_eingang_am',
're.waehrung',
're.keinsteuersatz',
//'re.schreibschutz',
//'re.pdfarchiviert',
//'re.pdfarchiviertversion',
//'re.ohne_briefpapier',
//'re.lieferid',
//'re.systemfreitext',
//'re.projektfiliale',
//'re.zuarchivieren',
're.internebezeichnung',
//'re.angelegtam',
're.abweichendebezeichnung',
're.bezahlt_am',
're.sprache',
//'re.bundesland',
're.gln',
//'re.deliverythresholdvatid',
're.kurs',
're.ohne_artikeltext',
're.anzeigesteuer',
're.kostenstelle',
're.bodyzusatz',
're.lieferbedingung',
're.skontobetrag',
're.skontoberechnet',
're.extsoll',
're.teilstorno',
])->from(self::TABLE_NAME . ' AS re');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('re.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false|DeleteQuery
*/
protected function deleteQuery()
{
return $this->db->delete()->from(self::TABLE_NAME)->where("id = :id AND (belegnr = '' OR belegnr = '0')");
}
/**
* @return false|DeleteQuery
*/
protected function deleteProtocolQuery()
{
return $this->db->delete()->from(self::PROTOCOL_TABLE_NAME)->where('rechnung = :id');
}
/**
* @return false|DeleteQuery
*/
protected function deletePositionQuery()
{
return $this->db->delete()->from(self::POSITION_TABLE_NAME)->where('rechnung = :id');
}
/**
* @param int $id
*
* @return ItemResult
*/
public function delete($id)
{
$deleteQuery = $this->deleteQuery();
if (!$deleteQuery) {
throw new EndpointNotAvailableException();
}
if (!$deleteQuery instanceof DeleteQuery && !$deleteQuery instanceof UpdateQuery) {
throw new InvalidArgumentException(sprintf(
'deleteQuery() must return an instance of %s or %s', DeleteQuery::class, UpdateQuery::class
));
}
try {
$affectedRow = (int)$this->db->fetchAffected($deleteQuery->getStatement(), ['id' => $id]);
if($affectedRow <= 0) {
throw new InvalidArgumentException(sprintf('Invoice can not deleted'));
}
$deleteQuery = $this->deletePositionQuery();
$this->db->perform($deleteQuery->getStatement(), ['id' => $id]);
$deleteQuery = $this->deleteProtocolQuery();
$this->db->perform($deleteQuery->getStatement(), ['id' => $id]);
$success = true;
} catch (Exception $e) {
$success = false;
}
$result = new ItemResult(['id' => $id]);
$result->setSuccess($success);
return $result;
}
}
@@ -0,0 +1,183 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für Angebotspositionen
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentOfferPositionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'angebot_position';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'sort' => 'anpos.sort',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('anpos.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'anpos.id',
//'anpos.angebot', // Index
'anpos.projekt',
'anpos.artikel', // Index
'anpos.bezeichnung',
'anpos.beschreibung',
//'anpos.internerkommentar',
'anpos.nummer',
'anpos.menge',
'anpos.preis',
'anpos.waehrung',
'anpos.lieferdatum',
'anpos.vpe',
//'anpos.sort',
//'anpos.status',
'anpos.umsatzsteuer',
'anpos.bemerkung',
'anpos.geliefert',
//'anpos.logdatei',
//'anpos.punkte',
//'anpos.bonuspunkte',
//'anpos.mlmdirektpraemie',
//'anpos.keinrabatterlaubt',
//'anpos.grundrabatt',
//'anpos.rabattsync',
//'anpos.rabatt1',
//'anpos.rabatt2',
//'anpos.rabatt3',
//'anpos.rabatt4',
//'anpos.rabatt5',
'anpos.einheit',
'anpos.optional',
'anpos.rabatt',
'anpos.zolltarifnummer',
'anpos.herkunftsland',
'anpos.artikelnummerkunde',
'anpos.lieferdatumkw',
//'anpos.teilprojekt',
//'anpos.kostenstelle',
//'anpos.erloese',
//'anpos.erloesefestschreiben',
//'anpos.einkaufspreiswaehrung',
'anpos.einkaufspreis',
'anpos.einkaufspreisurspruenglich',
//'anpos.einkaufspreisid',
//'anpos.ekwaehrung',
//'anpos.deckungsbeitrag',
//'anpos.freifeld1',
//'anpos.freifeld2',
//'anpos.freifeld3',
//'anpos.freifeld4',
//'anpos.freifeld5',
//'anpos.freifeld6',
//'anpos.freifeld7',
//'anpos.freifeld8',
//'anpos.freifeld9',
//'anpos.freifeld10',
//'anpos.freifeld11',
//'anpos.freifeld12',
//'anpos.freifeld13',
//'anpos.freifeld14',
//'anpos.freifeld15',
//'anpos.freifeld16',
//'anpos.freifeld17',
//'anpos.freifeld18',
//'anpos.freifeld19',
//'anpos.freifeld20',
//'anpos.freifeld21',
//'anpos.freifeld22',
//'anpos.freifeld23',
//'anpos.freifeld24',
//'anpos.freifeld25',
//'anpos.freifeld26',
//'anpos.freifeld27',
//'anpos.freifeld28',
//'anpos.freifeld29',
//'anpos.freifeld30',
//'anpos.freifeld31',
//'anpos.freifeld32',
//'anpos.freifeld33',
//'anpos.freifeld34',
//'anpos.freifeld35',
//'anpos.freifeld36',
//'anpos.freifeld37',
//'anpos.freifeld38',
//'anpos.freifeld39',
//'anpos.freifeld40',
//'anpos.formelmenge',
//'anpos.formelpreis',
'anpos.ohnepreis',
'anpos.textalternativpreis',
'anpos.steuersatz',
'anpos.steuertext',
'anpos.steuerbetrag',
'anpos.skontobetrag',
'anpos.skontosperre',
'anpos.berechnen_aus_teile',
'anpos.ausblenden_im_pdf',
//'anpos.explodiert_parent',
//'anpos.umsatz_netto_einzeln',
//'anpos.umsatz_netto_gesamt',
//'anpos.umsatz_brutto_einzeln',
//'anpos.umsatz_brutto_gesamt',
])
->from(self::TABLE_NAME . ' AS anpos');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('anpos.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für das Angebots-Protokoll
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Incldudes verwendet.
*/
class DocumentOfferProtocolResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'angebot_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'zeit' => 'anproto.zeit',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('anproto.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'anproto.id',
//'anproto.angebot',
'anproto.zeit',
'anproto.bearbeiter',
'anproto.grund',
])
->from(self::TABLE_NAME . ' AS anproto');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('anproto.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,280 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource für Angebote
*/
class DocumentOfferResource extends AbstractResource
{
/** @var string */
const TABLE_NAME = 'angebot';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'status' => 'an.status LIKE',
'belegnr' => 'an.belegnr %LIKE%',
'belegnr_equals' => 'an.belegnr LIKE',
'belegnr_startswith' => 'an.belegnr LIKE%',
'belegnr_endswith' => 'an.belegnr %LIKE',
'kundennummer' => 'an.kundennummer %LIKE%',
'kundennummer_equals' => 'an.kundennummer LIKE',
'kundennummer_startswith' => 'an.kundennummer LIKE%',
'kundennummer_endswith' => 'an.kundennummer %LIKE',
'datum' => 'an.datum =',
'datum_gt' => 'an.datum >',
'datum_gte' => 'an.datum >=',
'datum_lt' => 'an.datum <',
'datum_lte' => 'an.datum <=',
'projekt' => 'an.projekt =',
]);
$this->registerSortingParams([
'belegnr' => 'an.belegnr',
'datum' => 'an.datum',
]);
$this->registerIncludes([
'positionen' => [
'key' => 'positionen',
'resource' => DocumentOfferPositionResource::class,
'filter' => [
[
'property' => 'angebot',
'value' => ':id',
],
],
'sort' => [
'sort' => 'ASC',
],
],
'protokoll' => [
'key' => 'protokoll',
'resource' => DocumentOfferProtocolResource::class,
'filter' => [
[
'property' => 'angebot',
'value' => ':id',
],
],
'sort' => [
'zeit' => 'ASC',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('an.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'an.id',
'an.firma',
'an.projekt', // Index
'an.status', // Index
'an.belegnr', // Index
'an.kundennummer',
'an.aktion',
'an.anfrage',
'an.internebezeichnung',
'an.datum',
'an.gueltigbis',
'an.lieferdatum',
'an.lieferdatumkw',
'an.planedorderdate',
'an.abweichendebezeichnung',
//'an.auftrag',
'an.adresse', // Index
'an.typ',
'an.name',
'an.titel',
'an.ansprechpartnerid',
'an.ansprechpartner',
'an.abteilung',
'an.unterabteilung',
'an.adresszusatz',
'an.strasse',
'an.plz',
'an.ort',
'an.land',
'an.bundesstaat',
'an.telefon',
'an.telefax',
'an.email',
'an.anschreiben',
//'an.betreff',
//'an.vertrieb',
//'an.vertriebid', // Index
//'an.deckungsbeitragcalc',
//'an.deckungsbeitrag',
'an.gesamtsumme',
'an.erloes_netto',
'an.umsatz_netto',
//'an.provision',
//'an.provision_summe',
//'an.keinsteuersatz',
'an.versandart',
'an.lieferbedingung',
'an.autoversand',
'an.keinporto',
'an.gesamtsummeausblenden',
'an.zahlungsweise',
'an.zahlungszieltage',
'an.zahlungszieltageskonto',
'an.zahlungszielskonto',
'an.skontobetrag',
'an.skontoberechnet',
//'an.bank_inhaber',
//'an.bank_institut',
//'an.bank_blz',
//'an.bank_konto',
//'an.kreditkarte_typ',
//'an.kreditkarte_inhaber',
//'an.kreditkarte_nummer',
//'an.kreditkarte_pruefnummer',
//'an.kreditkarte_monat',
//'an.kreditkarte_jahr',
'an.abweichendelieferadresse',
'an.liefername',
'an.liefertitel',
'an.lieferansprechpartner',
'an.lieferabteilung',
'an.lieferunterabteilung',
'an.lieferadresszusatz',
'an.lieferstrasse',
'an.lieferort',
'an.lieferplz',
'an.lieferland',
'an.lieferbundesstaat',
'an.liefertelefon',
'an.liefertelefax',
'an.liefermail',
'an.lieferid',
'an.liefergln',
'an.lieferemail',
'an.abweichenderechnungsadresse',
'an.retyp',
'an.rechnungname',
'an.retelefon',
'an.reansprechpartner',
'an.retelefax',
'an.reabteilung',
'an.reemail',
'an.reunterabteilung',
'an.readresszusatz',
'an.restrasse',
'an.replz',
'an.reort',
'an.reland',
'an.versendet',
'an.versendet_am',
'an.versendet_per',
'an.versendet_durch',
//'an.inbearbeitung',
//'an.vermerk',
//'an.logdatei',
//'an.auftragid',
//'an.anfrageid',
//'an.gruppe', // Index
//'an.usereditid', // Index
//'an.useredittimestamp',
//'an.realrabatt',
//'an.rabatt',
//'an.rabatt1',
//'an.rabatt2',
//'an.rabatt3',
//'an.rabatt4',
//'an.rabatt5',
//'an.steuersatz_normal',
//'an.steuersatz_zwischen',
//'an.steuersatz_ermaessigt',
//'an.steuersatz_starkermaessigt',
//'an.steuersatz_dienstleistung',
//'an.schreibschutz',
//'an.pdfarchiviert',
//'an.pdfarchiviertversion',
//'an.ohne_briefpapier',
//'an.projektfiliale',
//'an.zuarchivieren',
//'an.angelegtam',
//'an.kopievon',
//'an.kopienummer',
'an.gln',
'an.bearbeiterid',
'an.bearbeiter',
'an.ohne_artikeltext',
'an.ustid',
'an.ust_befreit',
'an.anzeigesteuer',
'an.waehrung',
'an.sprache',
'an.kurs',
'an.kostenstelle',
'an.freitext',
'an.internebemerkung',
'an.bodyzusatz',
'an.shop',
'an.shopextid',
'an.internet',
//'an.transaktionsnummer',
//'an.packstation_inhaber',
//'an.packstation_station',
//'an.packstation_ident',
//'an.packstation_plz',
//'an.packstation_ort',
])->from(self::TABLE_NAME . ' AS an');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('an.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,190 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für Auftragspositionen
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentSalesOrderPositionResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'auftrag_position';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'sort' => 'aupos.sort',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('aupos.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'aupos.id',
'aupos.auftrag', // Index
'aupos.projekt',
'aupos.artikel', // Index
'aupos.bezeichnung',
'aupos.beschreibung',
//'aupos.internerkommentar',
'aupos.nummer',
'aupos.menge',
'aupos.preis',
'aupos.waehrung',
'aupos.lieferdatum',
'aupos.vpe',
//'aupos.sort',
//'aupos.status',
'aupos.umsatzsteuer',
'aupos.bemerkung',
'aupos.geliefert',
'aupos.geliefert_menge',
//'aupos.logdatei',
//'aupos.punkte',
//'aupos.bonuspunkte',
//'aupos.mlmdirektpraemie',
//'aupos.keinrabatterlaubt',
//'aupos.grundrabatt',
//'aupos.rabattsync',
//'aupos.rabatt1',
//'aupos.rabatt2',
//'aupos.rabatt3',
//'aupos.rabatt4',
//'aupos.rabatt5',
'aupos.einheit',
'aupos.webid',
'aupos.rabatt',
'aupos.nachbestelltexternereinkauf',
'aupos.potentiellerliefertermin',
'aupos.zolleinzelwert',
'aupos.zollgesamtwert',
'aupos.zollwaehrung',
'aupos.zolleinzelgewicht',
'aupos.zollgesamtgewicht',
'aupos.zolltarifnummer',
'aupos.herkunftsland',
'aupos.artikelnummerkunde',
'aupos.lieferdatumkw',
//'aupos.teilprojekt',
//'aupos.kostenstelle',
//'aupos.erloese',
//'aupos.erloesefestschreiben',
//'aupos.einkaufspreiswaehrung',
'aupos.einkaufspreis',
'aupos.einkaufspreisurspruenglich',
//'aupos.einkaufspreisid',
//'aupos.ekwaehrung',
//'aupos.deckungsbeitrag',
//'aupos.freifeld1',
//'aupos.freifeld2',
//'aupos.freifeld3',
//'aupos.freifeld4',
//'aupos.freifeld5',
//'aupos.freifeld6',
//'aupos.freifeld7',
//'aupos.freifeld8',
//'aupos.freifeld9',
//'aupos.freifeld10',
//'aupos.freifeld11',
//'aupos.freifeld12',
//'aupos.freifeld13',
//'aupos.freifeld14',
//'aupos.freifeld15',
//'aupos.freifeld16',
//'aupos.freifeld17',
//'aupos.freifeld18',
//'aupos.freifeld19',
//'aupos.freifeld20',
//'aupos.freifeld21',
//'aupos.freifeld22',
//'aupos.freifeld23',
//'aupos.freifeld24',
//'aupos.freifeld25',
//'aupos.freifeld26',
//'aupos.freifeld27',
//'aupos.freifeld28',
//'aupos.freifeld29',
//'aupos.freifeld30',
//'aupos.freifeld31',
//'aupos.freifeld32',
//'aupos.freifeld33',
//'aupos.freifeld34',
//'aupos.freifeld35',
//'aupos.freifeld36',
//'aupos.freifeld37',
//'aupos.freifeld38',
//'aupos.freifeld39',
//'aupos.freifeld40',
//'aupos.formelmenge',
//'aupos.formelpreis',
'aupos.ohnepreis',
'aupos.steuersatz',
'aupos.steuertext',
'aupos.steuerbetrag',
'aupos.skontobetrag',
'aupos.skontosperre',
'aupos.ausblenden_im_pdf',
//'aupos.explodiert',
//'aupos.explodiert_parent', // Index
//'aupos.umsatz_netto_einzeln',
//'aupos.umsatz_netto_gesamt',
//'aupos.umsatz_brutto_einzeln',
//'aupos.umsatz_brutto_gesamt',
])
->from(self::TABLE_NAME . ' AS aupos');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('aupos.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,85 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressoure für das Auftrags-Protokoll
*
* Ressource hat keinen eigenen Endpunkt; Ressource wird nur für Includes verwendet.
*/
class DocumentSalesOrderProtocolResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'auftrag_protokoll';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerSortingParams([
'zeit' => 'auproto.zeit',
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('auproto.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'auproto.id',
'auproto.auftrag',
'auproto.zeit',
'auproto.bearbeiter',
'auproto.grund',
])
->from(self::TABLE_NAME . ' AS auproto');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('auproto.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,345 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource für Aufträge (Verkauf)
*/
class DocumentSalesOrderResource extends AbstractResource
{
/** @var string */
const TABLE_NAME = 'auftrag';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'status' => 'au.status LIKE',
'belegnr' => 'au.belegnr %LIKE%',
'belegnr_equals' => 'au.belegnr LIKE',
'belegnr_startswith' => 'au.belegnr LIKE%',
'belegnr_endswith' => 'au.belegnr %LIKE',
'kundennummer' => 'au.kundennummer %LIKE%',
'kundennummer_equals' => 'au.kundennummer LIKE',
'kundennummer_startswith' => 'au.kundennummer LIKE%',
'kundennummer_endswith' => 'au.kundennummer %LIKE',
'internet' => 'au.internet %LIKE%',
'internet_equals' => 'au.internet LIKE',
'internet_startswith' => 'au.internet LIKE%',
'internet_endswith' => 'au.internet %LIKE',
'datum' => 'au.datum =',
'datum_gt' => 'au.datum >',
'datum_gte' => 'au.datum >=',
'datum_lt' => 'au.datum <',
'datum_lte' => 'au.datum <=',
'angebot' => 'au.angebot LIKE',
'angebotid' => 'au.angebotid =',
'projekt' => 'au.projekt =',
]);
$this->registerSortingParams([
'belegnr' => 'au.belegnr',
'datum' => 'au.datum',
]);
$this->registerIncludes([
'positionen' => [
'key' => 'positionen',
'resource' => DocumentSalesOrderPositionResource::class,
'filter' => [
[
'property' => 'auftrag',
'value' => ':id',
],
],
'sort' => [
'sort' => 'ASC',
],
],
'protokoll' => [
'key' => 'protokoll',
'resource' => DocumentSalesOrderProtocolResource::class,
'columns' => [
'auproto.id',
'auproto.zeit',
'auproto.bearbeiter',
'auproto.grund',
],
'filter' => [
[
'property' => 'auftrag',
'value' => ':id',
],
],
'sort' => [
'zeit' => 'ASC',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('au.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'au.id',
'au.firma',
'au.projekt', // Index
'au.status', // Index
'au.belegnr', // Index
'au.kundennummer',
'au.lieferantenauftrag',
'au.lieferant',
'au.aktion',
'au.angebot',
'au.ihrebestellnummer',
'au.internet', // Index
'au.internebezeichnung',
'au.datum',
'au.lieferdatum',
'au.lieferdatumkw',
'au.tatsaechlicheslieferdatum',
'au.reservationdate',
'au.abweichendebezeichnung',
'au.adresse', // Index
'au.typ',
'au.name',
'au.titel',
'au.ansprechpartnerid',
'au.ansprechpartner',
'au.abteilung',
'au.unterabteilung',
'au.adresszusatz',
'au.strasse',
'au.plz',
'au.ort',
'au.land',
'au.bundesstaat',
'au.telefon',
'au.telefax',
'au.email',
'au.anschreiben',
//'au.betreff',
//'au.vertrieb',
//'au.vertriebid', // Index
//'au.deckungsbeitragcalc',
//'au.deckungsbeitrag',
'au.gesamtsumme',
'au.erloes_netto',
'au.umsatz_netto',
//'au.provision',
//'au.provision_summe',
//'au.keinsteuersatz',
'au.lager_ok',
'au.porto_ok',
'au.ust_ok',
'au.check_ok',
'au.vorkasse_ok',
'au.nachnahme_ok',
'au.reserviert_ok',
'au.partnerid',
'au.folgebestaetigung',
'au.zahlungsmail',
'au.liefertermin_ok',
'au.teillieferung_moeglich',
'au.kreditlimit_ok',
'au.kreditlimit_freigabe',
'au.liefersperre_ok',
'au.teillieferungvon', // Index
'au.teillieferungnummer',
'au.autofreigabe',
'au.freigabe',
'au.nachbesserung',
'au.abgeschlossen',
'au.nachlieferung',
'au.versandart',
'au.lieferbedingung',
'au.autoversand',
'au.keinporto',
'au.art',
'au.fastlane',
'au.lieferungtrotzsperre',
'au.keinestornomail',
'au.keinetrackingmail',
'au.zahlungsmailcounter',
'au.zahlungsweise',
'au.zahlungszieltage',
'au.zahlungszieltageskonto',
'au.zahlungszielskonto',
'au.skontobetrag',
'au.skontoberechnet',
//'au.bank_inhaber',
//'au.bank_institut',
//'au.bank_blz',
//'au.bank_konto',
//'au.kreditkarte_typ',
//'au.kreditkarte_inhaber',
//'au.kreditkarte_nummer',
//'au.kreditkarte_pruefnummer',
//'au.kreditkarte_monat',
//'au.kreditkarte_jahr',
'au.abweichendelieferadresse',
'au.liefername',
'au.liefertitel',
'au.lieferansprechpartner',
'au.lieferabteilung',
'au.lieferunterabteilung',
'au.lieferadresszusatz',
'au.lieferstrasse',
'au.lieferort',
'au.lieferplz',
'au.lieferland',
'au.lieferbundesstaat',
'au.lieferemail',
'au.lieferid',
'au.liefergln',
'au.versendet',
'au.versendet_am',
'au.versendet_per',
'au.versendet_durch',
//'au.inbearbeitung',
//'au.logdatei',
'au.angebotid',
//'au.rechnungid',
//'au.anfrageid',
//'au.gruppe', // Index
//'au.usereditid', // Index
//'au.useredittimestamp',
//'au.realrabatt',
//'au.rabatt',
//'au.rabatt1',
//'au.rabatt2',
//'au.rabatt3',
//'au.rabatt4',
//'au.rabatt5',
//'au.steuersatz_normal',
//'au.steuersatz_zwischen',
//'au.steuersatz_ermaessigt',
//'au.steuersatz_starkermaessigt',
//'au.steuersatz_dienstleistung',
//'au.schreibschutz',
//'au.pdfarchiviert',
//'au.pdfarchiviertversion',
//'au.ohne_briefpapier',
//'au.projektfiliale',
//'au.zuarchivieren',
//'au.angelegtam',
//'au.partnerausgezahlt',
//'au.partnerausgezahltam',
//'au.kennen',
//'au.rma',
//'au.transaktionsnummer', // Index
//'au.vorabbezahltmarkieren',
//'au.einzugsdatum',
//'au.auftragseingangper',
//'au.systemfreitext',
//'au.saldo',
//'au.saldogeprueft',
//'au.rabatteportofestschreiben',
//'au.deliverythresholdvatid',
//'au.lieferantennummer',
//'au.lieferantkdrnummer', // Index
//'au.webid',
//'au.cronjobkommissionierung',
//'au.standardlager',
//'au.kommissionskonsignationslager',
//'au.extsoll',
'au.gln',
'au.bearbeiterid',
'au.bearbeiter',
'au.ohne_artikeltext',
'au.ustid',
'au.ust_befreit',
'au.ust_inner',
'au.anzeigesteuer',
'au.waehrung',
'au.sprache',
'au.kurs',
'au.kostenstelle',
'au.freitext',
'au.internebemerkung',
'au.bodyzusatz',
'au.shop',
'au.shopextid',
'au.shopextstatus',
//'au.stornogrund',
//'au.stornosonstiges',
//'au.stornorueckzahlung',
//'au.stornobetrag',
//'au.stornobankinhaber',
//'au.stornobankkonto',
//'au.stornobankblz',
//'au.stornobankbank',
//'au.stornogutschrift',
//'au.stornogutschriftbeleg',
//'au.stornowareerhalten',
//'au.stornomanuellebearbeitung',
//'au.stornokommentar',
//'au.stornobezahlt',
//'au.stornobezahltam',
//'au.stornobezahltvon',
//'au.stornoabgeschlossen',
//'au.stornorueckzahlungper',
//'au.stornowareerhaltenretour',
//'au.transaktionsnummer',
//'au.packstation_inhaber',
//'au.packstation_station',
//'au.packstation_ident',
//'au.packstation_plz',
//'au.packstation_ort',
])->from(self::TABLE_NAME . ' AS au');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('s.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,78 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource hat keinen eigenen API-Endpunkt (keine URL).
* Ressource dient nur als Include für die DocumentScanner-Ressource.
*/
class DocumentScannerMetaDataResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'docscan_metadata';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'dm.id',
'dm.meta_key',
'dm.meta_value',
])
->from(self::TABLE_NAME . ' AS dm');
}
/**
* @return false
*/
protected function selectOneQuery()
{
return false;
}
/**
* @return false
*/
protected function selectIdsQuery()
{
return false;
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,179 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Aura\SqlQuery\Exception;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class DocumentScannerResource extends AbstractResource
{
/** @var string TABLE_NAME */
const TABLE_NAME = 'docscan';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'titel' => 'd.titel %LIKE%',
'titel_equals' => 'd.titel LIKE',
'titel_startswith' => 'd.titel LIKE%',
'titel_endswith' => 'd.titel %LIKE',
'dateiname' => 'dv.dateiname %LIKE%',
'dateiname_equals' => 'dv.dateiname LIKE',
'dateiname_startswith' => 'dv.dateiname LIKE%',
'dateiname_endswith' => 'dv.dateiname %LIKE',
'datum' => 'dv.datum =',
'datum_gt' => 'dv.datum >',
'datum_gte' => 'dv.datum >=',
'datum_lt' => 'dv.datum <',
'datum_lte' => 'dv.datum <=',
'belegtyp' => 'dsg.belegtypen %LIKE%',
'stichwort' => 'dsg.stichwoerter %LIKE%',
'firma' => 'd.firma =',
]);
$this->registerSortingParams([
'titel' => 'd.titel',
'dateiname' => 'dv.dateiname',
'datum' => 'dv.datum',
]);
/*$this->registerValidationRules([
'id' => 'not_present',
'geloescht' => 'in:0,1',
]);*/
$this->registerIncludes([
'metadata' => [
'key' => 'metadata',
'resource' => DocumentScannerMetaDataResource::class,
'filter' => [
['property' => 'docscan_id', 'value' => ':docscan_id'],
],
'columns' => [
//'dm.id',
'dm.meta_key',
'dm.meta_value',
],
],
'stichwoerter' => [
'key' => 'stichwoerter',
'resource' => FileKeywordResource::class,
'filter' => [
['property' => 'datei', 'value' => ':id'],
],
'columns' => [
'ds.id',
'ds.subjekt AS stichwort',
'ds.objekt AS belegtyp',
'ds.parameter AS beleg_id',
'ds.sort',
],
],
]);
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'd.id',
'doc.id AS docscan_id',
'd.titel',
'd.beschreibung',
'd.nummer',
'd.firma',
'dv.ersteller',
'dv.datum',
'dv.version',
'dv.dateiname',
'dv.bemerkung',
'dv.size',
// 'dsg.belegtypen',
// 'dsg.stichwoerter',
])
->from(self::TABLE_NAME . ' AS doc')
->innerJoin('datei AS d', 'doc.datei = d.id')
->innerJoin('datei_stichwoerter AS ds', 'd.id = ds.datei AND ds.objekt LIKE \'DocScan\'')
->joinSubSelect(
'INNER',
'SELECT dv.datei, MAX(dv.id) AS max_id
FROM datei_version AS dv
GROUP BY dv.datei',
'dvm',
'd.id = dvm.datei'
)
->innerJoin(
'datei_version AS dv',
'd.id = dv.datei AND dv.id = dvm.max_id'
)
->joinSubSelect( // wird für Filter benötigt
'LEFT',
'SELECT
dsg.datei,
GROUP_CONCAT(DISTINCT dsg.subjekt) AS stichwoerter,
GROUP_CONCAT(DISTINCT dsg.objekt) AS belegtypen
FROM datei_stichwoerter AS dsg
GROUP BY dsg.datei',
'dsg',
'd.id = dsg.datei'
)
->where('d.geloescht <> 1');
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('d.id = :id');
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('d.id IN (:ids)');
}
/**
* @internal Insert ist möglich; wird aber über den Controller verarbeitet.
*
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Api\Resource\Exception;
class EndpointNotAvailableException extends \RuntimeException
{
protected $message = 'API-Endpoint is not available';
}
@@ -0,0 +1,8 @@
<?php
namespace Xentral\Modules\Api\Resource\Exception;
class ValidationRequiredException extends \RuntimeException
{
protected $message = 'Validation is required for inserting and updating resources.';
}
@@ -0,0 +1,29 @@
<?php
namespace Xentral\Modules\Api\Resource\Feature;
use Xentral\Modules\Api\Resource\Filter\Select\SimpleSearchFilter;
trait FilterFeatureTrait
{
/**
* Festlegen welche Filter erlaubt sind
*
* @example $this->registerFilterParams([
* 'title' => 'l.bezeichnung %LIKE%',
* 'title_starts_with' => 'l.bezeichnung LIKE%',
* 'title_ends_with' => 'l.bezeichnung %LIKE',
* 'title_exact' => 'l.bezeichnung LIKE',
* 'project' => 'l.projekt =',
* 'project_not' => 'l.projekt !=',
* 'amount_min' => 'l.amount >=',
* 'amount_max' => 'l.amount <=',
* ]);
*
* @param array $params
*/
protected function registerFilterParams(array $params)
{
$this->registerSelectFilter(new SimpleSearchFilter($params));
}
}
@@ -0,0 +1,185 @@
<?php
namespace Xentral\Modules\Api\Resource\Feature;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
use Xentral\Modules\Api\Exception\ResourceNotFoundException;
use Xentral\Modules\Api\Resource\AbstractResource;
trait IncludeFeatureTrait
{
/** @var array $includeSettings */
private $includeSettings;
/**
* @example in configure-Methode der Resource:
* $this->registerIncludes([
* 'projekte' => [
* 'key' => 'projekt',
* 'resource' => ProjectResource::class,
* 'columns' => [
* 'p.id',
* 'p.name',
* 'p.abkuerzung',
* 'p.beschreibung',
* 'p.farbe',
* ],
* ],
* ]);
*
* @param array $includes
*/
protected function registerIncludes($includes)
{
$this->includeSettings = $includes;
}
/**
* @param array $includes
* @param array $items
* @param bool $isCollection true=Mehrzeilig, false=Assoziatives Array
*
* @return array
*/
protected function integrateIncludes(array $includes, array &$items, $isCollection = true)
{
// Keine Includes gesetzt
if (empty($includes)) {
return $items;
}
if (empty($this->includeSettings)) {
return $items;
}
// Doppelte Includes entfernen
$includes = array_unique($includes);
// Einzelnes Item in Collection verwandeln
if (!$isCollection) {
$items = [$items];
}
foreach ($includes as $includeName) {
if (empty($includeName)) {
continue;
}
$settings = $this->getIncludeSetting($includeName);
$subKey = $settings['key'];
if (empty($subKey)) {
throw new \RuntimeException(sprintf(
'Include "%s" not posible. Key is missing.', $includeName
));
}
// Nur bestimmte Spalten inkludieren?
$columns = isset($settings['columns']) ? $settings['columns'] : [];
// 1:n Beziehung zwischen Resource und Subresource
if (isset($settings['filter'])) {
/** @var AbstractResource $subResource */
$subResource = $this->getResource($settings['resource']);
foreach ($items as &$item) {
// Filter aufbereiten
$filter = $settings['filter'];
foreach ($filter as &$filterItem) {
// Filter benötigt Wert aus Haupt-Resource
if (strpos($filterItem['value'], ':') === 0) {
$key = substr_replace($filterItem['value'], '', 0, 1);
$filterItem['value'] = $item[$key];
}
}
unset($filterItem);
$filter = ['filter' => $filter]; // In ComplexSearch-Filter wandeln
// Sortierung vorhanden?
$sort = !empty($settings['sort']) ? $settings['sort'] : [];
try {
/** @var AbstractResource $subResource */
$subResult = $subResource->getList($filter, $sort, $columns, [], 1, 1000);
$subItems = $subResult->getData();
} catch (ResourceNotFoundException $e) {
$subItems = [];
}
$item[$settings['key']] = $subItems;
}
unset($item);
// 1:1 Beziehung zwischen Resource und Subresource
} else {
// Prüfen ob Spalte zum Integrieren in Haupt-Ergebnis existiert
if (!$this->arrayColumnExists($items, $subKey)) {
throw new \RuntimeException(sprintf(
'Include "%s" not posible. Key "%s" is missing.', $includeName, $subKey
));
}
// Benötigte Subresourcen-IDs aus Haupt-Ergebnis holen
$subIds = array_unique(array_column($items, $subKey));
if (empty($subIds)) {
continue;
}
// Subresourcen anhand der IDs laden
try {
/** @var AbstractResource $subResource */
$subResource = $this->getResource($settings['resource']);
$subResult = $subResource->getIds($subIds, $columns);
} catch (ResourceNotFoundException $e) {
continue;
}
// Gefundene Subresourcen in Haupt-Ergebnis einbinden
array_walk($items, function (&$item, $id, $subItems) use ($subKey) {
$subId = (int)$item[$subKey];
if (isset($subItems[$subId])) {
$item[$subKey] = $subItems[$subId];
}
}, $subResult->getData());
}
}
if (!$isCollection) {
return $items[0];
}
return $items;
}
/**
* @param string $includeName
*
* @return array
*/
private function getIncludeSetting($includeName)
{
if (!isset($this->includeSettings[$includeName])) {
throw new InvalidArgumentException(
sprintf('Include "%s" is not registered.', $includeName)
);
}
return $this->includeSettings[$includeName];
}
/**
* @param array $items
* @param string $keyName
*
* @return bool
*/
private function arrayColumnExists(array $items, $keyName)
{
$row = current($items);
return array_key_exists($keyName, $row);
}
}
@@ -0,0 +1,24 @@
<?php
namespace Xentral\Modules\Api\Resource\Feature;
use Xentral\Modules\Api\Resource\Filter\Select\SortingFilter;
trait SortingFeatureTrait
{
/**
* Festlegen welche Sortierungen erlaubt sind
*
* @example $this->registerSortingParams([
* 'bezeichnung' => 'k.bezeichnung',
* 'projekt' => 'k.projekt',
* 'parent' => 'k.parent',
* ]);
*
* @param array $params
*/
protected function registerSortingParams(array $params)
{
$this->registerSelectFilter(new SortingFilter($params));
}
}
@@ -0,0 +1,88 @@
<?php
namespace Xentral\Modules\Api\Resource\Feature;
use Rakit\Validation\Validation;
use Xentral\Modules\Api\Exception\ValidationErrorException;
use Xentral\Modules\Api\Resource\Exception\ValidationRequiredException;
trait ValidationFeatureTrait
{
/** @var array $validationRules */
private $validationRules;
/** @var string $resourceTableName */
private $resourceTableName;
/**
* Validierungsregeln festlegen
*
* @example $this->registerValidationRules([
* 'id' => 'not_present',
* 'bezeichnung' => 'required|unique:artikelkategorien,bezeichnung',
* 'next_number' => 'numeric',
* 'projekt' => 'numeric',
* 'parent' => 'numeric',
* 'externenummer' => 'numeric',
* 'geloescht' => 'in:0,1',
* ]);
* @see https://github.com/rakit/validation#available-rules
*
* @param array $rules
*/
protected function registerValidationRules(array $rules)
{
$this->validationRules = $rules;
}
/**
* @param array $inputVars
* @param int $selfId
*/
protected function validateData($inputVars, $selfId = null)
{
if (empty($this->validationRules)) {
throw new ValidationRequiredException();
}
// Regeln aufbereiten
$rules = $this->validationRules;
if ($selfId) {
$needle = sprintf('unique:%s,', $this->resourceTableName);
foreach ($rules as $ruleKey => $ruleVal) {
if ($pos = strpos($ruleVal, $needle)) {
// Nach Anfang der nachfolgenden Regel suchen
$searchPos = $pos + strlen($needle);
$insertPos = strpos($ruleVal, '|', $searchPos);
// Keine weitere Regel gefunden; am Ende anfügen
if (!$insertPos) {
$insertPos = strlen($ruleVal);
}
// ID als dritten Parameter für UniqueRule übergeben
/** @see UniqueRule Parameter "except" */
$newRuleVal = substr_replace($ruleVal, ',' . $selfId, $insertPos, 0);
$rules[$ruleKey] = $newRuleVal;
}
}
}
/** @var Validation $validation */
$validation = $this->validator->validate($inputVars, $rules);
if ($validation->fails()) {
throw new ValidationErrorException($validation->errors()->all());
}
}
/**
* @param string $tableName
*/
protected function setTableName($tableName)
{
$this->resourceTableName = $tableName;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
/**
* Ressource hat keinen eigenen API-Endpunkt (keine URL).
* Ressource dient nur als Include für die Dateien-Ressource.
*/
class FileKeywordResource extends AbstractResource
{
const TABLE_NAME = 'datei_stichwoerter';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'ds.id',
'ds.subjekt',
'ds.objekt',
'ds.parameter',
'ds.sort',
])
->from(self::TABLE_NAME . ' AS ds');
}
/**
* @return false
*/
protected function selectOneQuery()
{
return false;
}
/**
* @return false
*/
protected function selectIdsQuery()
{
return false;
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,157 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Aura\SqlQuery\Exception;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class FileResource extends AbstractResource
{
const TABLE_NAME = 'datei';
/**
* @return void
*/
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'titel' => 'd.titel %LIKE%',
'titel_equals' => 'd.titel LIKE',
'titel_startswith' => 'd.titel LIKE%',
'titel_endswith' => 'd.titel %LIKE',
'dateiname' => 'dv.dateiname %LIKE%',
'dateiname_equals' => 'dv.dateiname LIKE',
'dateiname_startswith' => 'dv.dateiname LIKE%',
'dateiname_endswith' => 'dv.dateiname %LIKE',
'belegtyp' => 'ds.belegtypen %LIKE%',
'stichwort' => 'ds.stichwoerter %LIKE%',
]);
$this->registerSortingParams([
'titel' => 'd.titel',
'dateiname' => 'dv.dateiname',
'datum' => 'dv.datum',
]);
/*$this->registerValidationRules([
'id' => 'not_present',
'geloescht' => 'in:0,1',
]);*/
$this->registerIncludes([
'stichwoerter' => [
'key' => 'stichwoerter',
'resource' => FileKeywordResource::class,
'filter' => [
['property' => 'datei', 'value' => ':id'],
],
'columns' => [
'ds.id',
'ds.subjekt AS stichwort',
'ds.objekt AS belegtyp',
'ds.parameter AS beleg_id',
'ds.sort',
],
],
]);
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'd.id',
'd.titel',
'd.beschreibung',
'd.nummer',
'd.firma',
'dv.ersteller',
'dv.datum',
'dv.version',
'dv.dateiname',
'dv.bemerkung',
'dv.size',
'ds.belegtypen',
'ds.stichwoerter',
])
->from(self::TABLE_NAME . ' AS d')
->joinSubSelect(
'INNER',
'SELECT dv.datei, MAX(dv.id) AS max_id
FROM datei_version AS dv
GROUP BY dv.datei',
'dvm',
'd.id = dvm.datei'
)
->innerJoin(
'datei_version AS dv',
'd.id = dv.datei AND dv.id = dvm.max_id'
)
->joinSubSelect(
'LEFT',
'SELECT
ds.datei,
GROUP_CONCAT(ds.subjekt) AS stichwoerter,
GROUP_CONCAT(ds.objekt) AS belegtypen
FROM datei_stichwoerter AS ds
GROUP BY ds.datei',
'ds',
'd.id = ds.datei'
)
->where('d.geloescht <> 1');
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('d.id = :id');
}
/**
* @return SelectQuery
*
* @throws Exception
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('d.id IN (:ids)');
}
/**
* @internal Insert ist möglich; wird aber über den FileController verarbeitet.
*
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,115 @@
<?php
namespace Xentral\Modules\Api\Resource\Filter\Select;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
class ComplexSearchFilter implements SelectFilterInterface
{
public function __construct()
{
}
/**
* @param SelectQuery $query
* @param array $filter
*
* @return SelectQuery
*/
public function applyFilter(SelectQuery $query, array $filter)
{
$filterParams = isset($filter['filter']) && is_array($filter['filter']) ? $filter['filter'] : [];
// Komplexe Suchfilter mit Klammern umschließen
return $query->where(function ($inner) use ($filterParams) {
$this->appendFilterQuery($inner, $filterParams);
});
}
/**
* @return string
*/
public function getFilterType()
{
return SelectFilterInterface::TYPE_SEARCHING;
}
/**
* @param SelectQuery $select
* @param array $filter
*
* @return SelectQuery
*/
protected function appendFilterQuery(SelectQuery $select, array $filter)
{
// Kein Filter verwendet
if (empty($filter)) {
return $select;
}
// Spalten aus Query holen
$cols = $select->getCols();
// Filter an SelectQuery anfügen
foreach ($filter as $index => $item) {
if (empty($item['property']) && empty($item['value'])) {
throw new InvalidArgumentException('Filter not valid. "property" und "value" required.');
}
// Defaults für optionale Felder setzen
if (empty($item['expression'])) {
$item['expression'] = 'LIKE';
}
if (empty($item['operation'])) {
$item['operation'] = 'AND';
}
// Aliase ersetzen
// Notwendig für Properties die einen Alias haben.
// Nach Alias-Feldnamen kann nicht gesucht werden.
if (array_key_exists($item['property'], $cols)) {
$item['property'] = $cols[$item['property']];
}
switch (strtolower($item['expression'])) {
case 'eq':
$item['expression'] = '=';
break;
case 'not':
$item['expression'] = '!=';
break;
case 'lt':
$item['expression'] = '<';
break;
case 'lte':
$item['expression'] = '<=';
break;
case 'gt':
$item['expression'] = '>';
break;
case 'gte':
$item['expression'] = '>=';
break;
case 'like':
$item['expression'] = 'LIKE';
break;
case 'not_like':
$item['expression'] = 'NOT LIKE';
break;
default:
$item['expression'] = 'LIKE';
break;
}
if (strtoupper($item['operation']) === 'OR') {
$select->orWhere(sprintf('%s %s ?', $item['property'], $item['expression']), $item['value']);
} else {
$select->where(sprintf('%s %s ?', $item['property'], $item['expression']), $item['value']);
}
}
return $select;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Xentral\Modules\Api\Resource\Filter\Select;
use Xentral\Components\Database\SqlQuery\SelectQuery;
interface SelectFilterInterface
{
const TYPE_SORTING = 'sort';
const TYPE_SEARCHING = 'search';
/**
* @param SelectQuery $query
* @param array $filterParams
*
* @return SelectQuery
*/
public function applyFilter(SelectQuery $query, array $filterParams);
/**
* @return string
*/
public function getFilterType();
}
@@ -0,0 +1,35 @@
<?php
namespace Xentral\Modules\Api\Resource\Filter\Select;
use Xentral\Components\Database\SqlQuery\SelectQuery;
trait SelectFilterTrait
{
/** @var array $selectFilter */
protected $selectFilter = [];
/**
* @param SelectQuery $query
* @param array $settings
*
* @return SelectQuery
*/
protected function applySelectFilter(SelectQuery $query, array $settings)
{
foreach ($this->selectFilter as $filter) {
/** @var SelectFilterInterface $filter */
$query = $filter->applyFilter($query, $settings[$filter->getFilterType()]);
}
return $query;
}
/**
* @param SelectFilterInterface $filter
*/
public function registerSelectFilter(SelectFilterInterface $filter)
{
$this->selectFilter[] = $filter;
}
}
@@ -0,0 +1,124 @@
<?php
namespace Xentral\Modules\Api\Resource\Filter\Select;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
class SimpleSearchFilter implements SelectFilterInterface
{
/** @var array $registeredProperties */
protected $registeredProperties;
/**
* @param array $search
*/
public function __construct(array $search)
{
$this->registeredProperties = $search;
}
/**
* @param SelectQuery $query
* @param array $filter
*
* @return SelectQuery
*/
public function applyFilter(SelectQuery $query, array $filter)
{
return $this->appendFilterQuery($query, $filter);
}
/**
* @return string
*/
public function getFilterType()
{
return SelectFilterInterface::TYPE_SEARCHING;
}
/**
* @param SelectQuery $select
* @param array $filter
*
* @return SelectQuery
*/
protected function appendFilterQuery(SelectQuery $select, array $filter)
{
// Kein Filter verwendet
if (empty($filter)) {
return $select;
}
// Filter an SelectQuery anfügen
foreach ($filter as $property => $value) {
// $_GET['filter'] wird von ComplexSearchFilter verarbeitet
/* @see \Xentral\Modules\Api\Resource\Filter\Select\ComplexSearchFilter */
if ($property === 'filter') {
continue;
}
$filterProperty = $this->prepareFilterName($property);
$filterValue = $this->prepareFilterValue($property, $value);
$select->where(sprintf('%s :%s', $filterProperty, $property));
$select->bindValue((string)$property, $filterValue);
}
return $select;
}
/**
* @param string $filterName
*
* @return string
*/
protected function prepareFilterName($filterName)
{
$filterProperty = trim($this->getRegisteredProperty($filterName));
$filterProperty = str_replace('%', '', $filterProperty); // Prozent aus LIKE-Suche entfernen
return $filterProperty;
}
/**
* @param string $filterName
* @param mixed $filterValue
*
* @return mixed
*/
protected function prepareFilterValue($filterName, $filterValue)
{
$filterProperty = trim($this->getRegisteredProperty($filterName));
// LIKE-Suche aufbereiten
if (substr($filterProperty, -6) === ' LIKE%') {
$filterValue = "{$filterValue}%";
}
if (substr($filterProperty, -6) === ' %LIKE') {
$filterValue = "%{$filterValue}";
}
if (substr($filterProperty, -7) === ' %LIKE%') {
$filterValue = "%{$filterValue}%";
}
return $filterValue;
}
/**
* @param string $param
*
* @return string
*/
protected function getRegisteredProperty($param)
{
if (!isset($this->registeredProperties[$param])) {
throw new InvalidArgumentException(
sprintf('Search parameter "%s" is not supported.', $param)
);
}
return $this->registeredProperties[$param];
}
}
@@ -0,0 +1,90 @@
<?php
namespace Xentral\Modules\Api\Resource\Filter\Select;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Modules\Api\Exception\InvalidArgumentException;
class SortingFilter implements SelectFilterInterface
{
/** @var array $sortingParams Erlaubte Sortierungs-Parameter */
protected $sortingParams;
/**
* @param array $sortingParams
*/
public function __construct(array $sortingParams)
{
$this->sortingParams = $sortingParams;
}
/**
* @param SelectQuery $query
* @param array $filter
*
* @return SelectQuery
*/
public function applyFilter(SelectQuery $query, array $filter)
{
return $this->appendSorting($query, $filter);
}
/**
* @return string
*/
public function getFilterType()
{
return SelectFilterInterface::TYPE_SORTING;
}
/**
* @param array $sorting
* @param SelectQuery $selectQuery
*
* @return SelectQuery
*/
protected function appendSorting(SelectQuery $selectQuery, array $sorting)
{
// Keine Sortier-Parameter vorhanden
if (empty($sorting)) {
return $selectQuery;
}
foreach ($sorting as $property => $direction) {
if (is_int($property)) {
$property = $direction;
$direction = 'ASC';
}
$direction = strtoupper($direction);
if (!in_array($direction, ['ASC', 'DESC'], true)) {
throw new InvalidArgumentException(
sprintf('Sort direction "%s" is invalid', $direction)
);
}
$dbProperty = $this->getRegisteredProperty($property);
$selectQuery->orderBy([
sprintf('%s %s', $dbProperty, $direction)
]);
}
return $selectQuery;
}
/**
* @param string $property
*
* @return string
*/
protected function getRegisteredProperty($property)
{
if (!isset($this->sortingParams[$property])) {
throw new InvalidArgumentException(
sprintf('Sorting parameter "%s" is not registered.', $property)
);
}
return $this->sortingParams[$property];
}
}
@@ -0,0 +1,120 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class GroupResource extends AbstractResource
{
const TABLE_NAME = 'gruppen';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'name' => 'g.name %LIKE%',
'name_exakt' => 'g.name LIKE',
'kennziffer' => 'g.kennziffer %LIKE%',
'kennziffer_exakt' => 'g.kennziffer LIKE',
'art' => 'g.art LIKE',
'projekt' => 'g.projekt =',
'kategorie' => 'g.kategorie =',
'aktiv' => 'g.aktiv =',
]);
$this->registerSortingParams([
'name' => 'g.name',
'art' => 'g.art',
'kennziffer' => 'g.kennziffer',
'projekt' => 'g.projekt',
'kategorie' => 'g.kategorie',
'aktiv' => 'g.aktiv',
]);
$this->registerValidationRules([
'id' => 'not_present',
'name' => 'required',
'kennziffer' => 'required|alpha_dash|unique:gruppen,kennziffer',
'art' => 'in:gruppe,preisgruppe,verband,regionalgruppe,kategorie,vertreter',
'projekt' => 'numeric',
'kategorie' => 'numeric',
'aktiv' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
// @todo Gruppenkategorien
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'g.id',
'g.name',
'g.art',
'g.kennziffer',
'g.internebemerkung',
'g.projekt',
'g.kategorie',
'g.aktiv',
])->from(self::TABLE_NAME . ' AS g');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('g.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('g.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,124 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class PaymentMethodResource extends AbstractResource
{
const TABLE_NAME = 'zahlungsweisen';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung' => 'z.bezeichnung %LIKE%',
'bezeichnung_exakt' => 'z.bezeichnung LIKE',
'type' => 'z.type %LIKE%',
'type_exakt' => 'z.type LIKE',
'projekt' => 'z.projekt =',
'verhalten' => 'z.verhalten =',
'aktiv' => 'z.aktiv =',
]);
$this->registerSortingParams([
'bezeichnung' => 'z.bezeichnung',
'type' => 'z.type',
'projekt' => 'z.projekt',
'modul' => 'z.modul',
'aktiv' => 'z.aktiv',
]);
$this->registerValidationRules([
'id' => 'not_present',
'einstellungen_json' => 'not_present',
'freitext' => 'not_present',
'bezeichnung' => 'required',
'type' => 'required|unique:zahlungsweisen,type',
'projekt' => 'numeric',
'aktiv' => 'boolean',
'vorkasse' => 'boolean',
'automatischbezahlt' => 'boolean',
'automatischbezahltverbindlichkeit' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'z.id',
'z.type',
'z.bezeichnung',
'z.freitext',
'z.aktiv',
'z.automatischbezahlt',
'z.automatischbezahltverbindlichkeit',
'z.projekt',
'z.vorkasse',
'z.verhalten',
'z.modul',
])->from(self::TABLE_NAME . ' AS z')
->where('z.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('z.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('z.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,75 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class ProjectResource extends AbstractResource
{
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()->cols(
[
'p.id',
'p.name',
'p.abkuerzung',
'p.verantwortlicher',
'p.beschreibung',
'p.sonstiges',
'p.aktiv',
'p.farbe',
'p.autoversand',
'p.portocheck',
'p.automailrechnung',
'p.autobestellung',
'p.speziallieferschein',
'p.lieferscheinbriefpapier',
'p.speziallieferscheinbeschriftung',
'p.firma',
'p.geloescht',
]
)->from('projekt AS p')->where('p.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('p.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('p.id IN (:ids)');
}
/** @return false */
protected function insertQuery()
{
return false;
}
/** @return false */
protected function updateQuery()
{
return false;
}
/** @return false */
protected function deleteQuery()
{
return false;
}
/** @return void */
protected function configure()
{
}
}
@@ -0,0 +1,103 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class PropertyResource extends AbstractResource
{
const TABLE_NAME = 'artikeleigenschaften';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams(
[
'artikel' => 'a.artikel =',
'name' => 'a.name =',
'typ' => 'a.typ =',
'projekt' => 'a.projekt =',
'geloescht' => 'a.geloescht =',
]
);
$this->registerSortingParams(
[
'artikel' => 'a.artikel =',
'name' => 'a.name =',
'typ' => 'a.typ =',
'projekt' => 'a.projekt =',
'geloescht' => 'a.geloescht =',
]
);
$this->registerValidationRules(
[
'id' => 'not_present',
'artikel' => 'integer',
'projekt' => 'integer',
'geloescht' => 'in:0,1',
'name' => 'unique:artikeleigenschaften,name'
]
);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols(
[
'a.id',
'a.artikel',
'a.name',
'a.typ',
'a.projekt',
'a.geloescht',
]
)
->from('artikeleigenschaften AS a');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('a.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('a.id IN (:ids)');
}
/** @return false */
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/** @return false */
protected function updateQuery()
{
return $this->db->update()
->table('artikeleigenschaften')
->where('id = :id');
}
/** @return false */
protected function deleteQuery()
{
return $this->db->delete()
->from('artikeleigenschaften')
->where('id = :id');
}
}
@@ -0,0 +1,94 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class PropertyValueResource extends AbstractResource
{
const TABLE_NAME = 'artikeleigenschaftenwerte';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams(
[
'artikeleigenschaften' => 'a.artikeleigenschaften =',
'artikel' => 'a.artikel =',
'wert' => 'a.wert =',
]
);
$this->registerSortingParams(
[
'artikel' => 'a.artikel =',
'wert' => 'a.wert =',
]
);
$this->registerValidationRules(
[
'id' => 'not_present',
'artikel' => 'numeric|db_value:artikel,id',
'artikeleigenschaften' => 'numeric|db_value:artikeleigenschaften,id',
]
);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols(
[
'a.id',
'a.artikeleigenschaften',
'a.wert',
'a.artikel',
]
)
->from('artikeleigenschaftenwerte AS a');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('a.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('a.id IN (:ids)');
}
/** @return false */
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/** @return false */
protected function updateQuery()
{
return $this->db->update()
->table('artikeleigenschaftenwerte')
->where('id = :id');
}
/** @return false */
protected function deleteQuery()
{
return $this->db->delete()
->from('artikeleigenschaftenwerte')
->where('id = :id');
}
}
@@ -0,0 +1,69 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\Database;
use Xentral\Modules\Api\Validator\Validator;
class ResourceManager
{
/** @var Database $db */
protected $db;
/** @var Validator $validator */
protected $validator;
/** @var \Api $legacyApi */
protected $legacyApi;
/** @var array $resources Beinhaltet erzeugte Instanzen */
protected $resources = [];
/**
* @param Database $database
* @param Validator $validator
* @param \Api $api
*/
public function __construct($database, $validator, $api)
{
$this->db = $database;
$this->validator = $validator;
$this->legacyApi = $api;
}
/**
* Resource erzeugen
*
* @param string $className
*
* @return AbstractResource
*/
public function get($className)
{
$cleanName = $this->convertClassName($className);
// Resource erzeugen falls noch nicht vorhanden
if (!isset($this->resources[$cleanName])) {
$this->resources[$cleanName] = new $className(
$this->db,
$this->validator
);
if ($className === ArticleResource::class) {
$this->resources[$cleanName]->setLegacyApi($this->legacyApi);
}
}
return $this->resources[$cleanName];
}
/**
* @param string $className
*
* @return string
*/
protected function convertClassName($className)
{
return str_replace('\\', '_', strtolower($className));
}
}
@@ -0,0 +1,143 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class ResubmissionResource extends AbstractResource
{
const TABLE_NAME = 'wiedervorlage';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'adresse' => 'w.adresse =',
'bearbeiter' => 'w.bearbeiter =',
'adresse_mitarbeiter' => 'w.adresse_mitarbeiter =',
'projekt' => 'w.projekt =',
'stages' => 'w.stages =',
'id_ext' => 'am.id_ext =',
]);
$this->registerSortingParams([
'datum_angelegt' => 'w.datum_angelegt',
'zeit_angelegt' => 'w.zeit_angelegt',
'datum_erinnerung' => 'w.datum_erinnerung',
'zeit_erinnerung' => 'w.zeit_erinnerung',
'datum_abschluss' => 'w.datum_abschluss',
'bezeichnung' => 'w.bezeichnung',
'stages' => 'w.stages',
'prio' => 'w.prio',
]);
$this->registerValidationRules([
'id' => 'not_present',
'id_ext' => 'not_present',
'datum_angelegt' => 'date:Y-m-d',
'zeit_angelegt' => 'time:H:i:s',
'datum_erinnerung' => 'required|date:Y-m-d',
'zeit_erinnerung' => 'required|time:H:i:s',
'datum_abschluss' => 'date:Y-m-d',
'bezeichnung' => 'required|min:3',
'beschreibung' => 'min:3',
'bearbeiter' => 'numeric|db_value:adresse,id',
'adresse_mitarbeiter' => 'numeric|db_value:adresse,id',
'projekt' => 'numeric|db_value:projekt,id',
'stages' => 'numeric|db_value:wiedervorlage_stages,id',
'betrag' => 'decimal',
'chance' => 'integer|between:0,100',
'erinnerung_per_mail' => 'in:0,1',
'abgeschlossen' => 'in:0,1',
'oeffentlich' => 'in:0,1',
'prio' => 'in:0,1',
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'w.id',
'w.adresse',
'w.projekt',
//'w.adresse_mitarbeier',
'w.bezeichnung',
'w.beschreibung',
//'w.ergebnis',
'w.betrag',
//'w.erinnerung',
'w.erinnerung_per_mail',
//'w.erinnerung_empfaenger',
//'w.link',
//'w.module',
//'w.action',
//'w.parameter',
//'w.status',
'w.bearbeiter',
'w.adresse_mitarbeiter',
'w.datum_angelegt',
'w.zeit_angelegt',
'w.datum_erinnerung',
'w.zeit_erinnerung',
'w.datum_abschluss',
'w.oeffentlich',
'w.abgeschlossen',
'w.chance',
'w.prio',
'w.stages',
'w.color',
'am.id_ext',
])->from(self::TABLE_NAME . ' AS w')
->leftJoin(
'api_mapping AS am',
'am.id_int = w.id AND am.tabelle = ' . $this->db->escapeString(self::TABLE_NAME)
);
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('w.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('w.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,99 @@
<?php
namespace Xentral\Modules\Api\Resource\Result;
abstract class AbstractResult
{
const RESULT_TYPE_ITEM = 'item';
const RESULT_TYPE_COLLECTION = 'collection';
/** @var string $type */
protected $type;
/** @var array $data */
protected $data;
/** @var array $pagination */
protected $pagination;
/** @var bool $success Als Kennzeichen ob Anlegen oder Bearbeiten erfolgreich war */
protected $success;
/**
* @param array $collection
* @param array $pagination
*/
abstract public function __construct(array $collection, array $pagination = null);
/**
* Ergebnis als Array zurückgeben
*
* @return array
*/
public function getResult()
{
$result = [];
// Success-Flag ganz oben anzeigen
if ($this->success !== null) {
$result['success'] = $this->success;
}
$result['data'] = $this->getData();
// Paginierung als letztes anzeigen
if ($this->pagination !== null) {
$result['pagination'] = $this->pagination;
}
return $result;
}
/**
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* @return array
*/
public function getPagination()
{
return $this->pagination;
}
/**
* @return string [item|collection]
*/
public function getType()
{
return $this->type;
}
/**
* @param bool $success
*/
public function setSuccess($success)
{
$this->success = (bool)$success;
}
/**
* @return bool
*/
public function isItem()
{
return $this->type === self::RESULT_TYPE_ITEM;
}
/**
* @return bool
*/
public function isCollection()
{
return $this->type === self::RESULT_TYPE_COLLECTION;
}
}
@@ -0,0 +1,34 @@
<?php
namespace Xentral\Modules\Api\Resource\Result;
class CollectionResult extends AbstractResult
{
/**
* @param array $collection
* @param array|null $pagination
*/
public function __construct(array $collection, array $pagination = null)
{
if (empty($pagination)) {
//throw new \CountryInvalidArgumentException('CollectionResult must contain pagination'); // @todo für GetIDs
}
if (empty($collection)) {
throw new \InvalidArgumentException('CollectionResult can not be empty');
}
$firstKey = key($collection);
if (!is_numeric($firstKey)) {
throw new \InvalidArgumentException('CollectionResult can only store an index based array');
}
if (!is_array($collection[$firstKey]) || empty($collection[$firstKey])) {
throw new \RuntimeException('CollectionResult must contain at least one result');
}
// @todo Sicherstellen dass Paginierung passt
$this->type = self::RESULT_TYPE_COLLECTION;
$this->data = $collection;
$this->pagination = $pagination;
}
}
@@ -0,0 +1,27 @@
<?php
namespace Xentral\Modules\Api\Resource\Result;
class ItemResult extends AbstractResult
{
/**
* @param array $item
* @param array|null $pagination
*/
public function __construct(array $item, array $pagination = null)
{
if ($pagination !== null) {
throw new \InvalidArgumentException('ItemResult can not have pagination');
}
if (empty($item)) {
throw new \InvalidArgumentException('ItemResult can not be empty');
}
if (is_numeric(key($item))) {
throw new \InvalidArgumentException('ItemResult can only store an associative array');
}
$this->type = self::RESULT_TYPE_ITEM;
$this->data = $item;
}
}
@@ -0,0 +1,125 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\SelectQuery;
class SalesPriceResource extends AbstractResource
{
const TABLE_NAME = 'verkaufspreise';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'waehrung' => 'vp.waehrung',
'artikel' => 'vp.artikel =',
'projekt' => 'vp.projekt =',
'adresse' => 'vp.adresse =',
'gruppe' => 'vp.gruppe =',
'firma' => 'vp.firma =',
]);
$this->registerSortingParams([
'preis' => 'vp.preis',
'menge' => 'vp.ab_menge',
'vpe_menge' => 'vp.vpe_menge',
'projekt' => 'k.projekt',
]);
/*$this->registerValidationRules([
'id' => 'not_present',
'bezeichnung' => 'required|unique:artikelkategorien,bezeichnung',
'next_number' => 'numeric',
'projekt' => 'numeric',
'parent' => 'numeric',
'externenummer' => 'numeric',
'geloescht' => 'in:0,1',
]);*/
/*$this->registerIncludes([
'projekte' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);*/
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db
->select()
->cols([
'vp.id',
'vp.artikel',
'vp.objekt',
'vp.projekt',
'vp.adresse',
'vp.preis',
'vp.waehrung',
'vp.ab_menge',
'vp.vpe',
'vp.vpe_menge',
'vp.angelegt_am',
'vp.gueltig_ab',
'vp.gueltig_bis',
'vp.bemerkung',
'vp.firma',
'vp.kundenartikelnummer',
'vp.nichtberechnet',
])
->from(self::TABLE_NAME . ' AS vp')
->where('vp.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('vp.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('vp.id IN (:ids)');
}
/**
* @return false
*/
protected function insertQuery()
{
return false;
}
/**
* @return false
*/
protected function updateQuery()
{
return false;
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}
@@ -0,0 +1,120 @@
<?php
namespace Xentral\Modules\Api\Resource;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
class ShippingMethodResource extends AbstractResource
{
const TABLE_NAME = 'versandarten';
protected function configure()
{
$this->setTableName(self::TABLE_NAME);
$this->registerFilterParams([
'bezeichnung' => 'v.bezeichnung %LIKE%',
'bezeichnung_exakt' => 'v.bezeichnung LIKE',
'type' => 'v.type %LIKE%',
'type_exakt' => 'v.type LIKE',
'projekt' => 'v.projekt =',
'modul' => 'v.modul =',
'aktiv' => 'v.aktiv =',
]);
$this->registerSortingParams([
'bezeichnung' => 'v.bezeichnung',
'type' => 'v.type',
'projekt' => 'v.projekt',
'modul' => 'v.modul',
'aktiv' => 'v.aktiv',
]);
$this->registerValidationRules([
'id' => 'not_present',
'einstellungen_json' => 'not_present',
'bezeichnung' => 'required',
'type' => 'required|unique:versandarten,type',
'projekt' => 'numeric',
'aktiv' => 'boolean',
]);
$this->registerIncludes([
'projekt' => [
'key' => 'projekt',
'resource' => ProjectResource::class,
'columns' => [
'p.id',
'p.name',
'p.abkuerzung',
'p.beschreibung',
'p.farbe',
],
],
]);
}
/**
* @return SelectQuery
*/
protected function selectAllQuery()
{
return $this->db->select()
->cols([
'v.id',
'v.type',
'v.bezeichnung',
'v.aktiv',
'v.projekt',
'v.modul',
'v.paketmarke_drucker',
'v.export_drucker',
'v.ausprojekt',
'v.versandmail',
'v.geschaeftsbrief_vorlage',
])->from(self::TABLE_NAME . ' AS v')
->where('v.geloescht <> 1');
}
/**
* @return SelectQuery
*/
protected function selectOneQuery()
{
return $this->selectAllQuery()->where('v.id = :id');
}
/**
* @return SelectQuery
*/
protected function selectIdsQuery()
{
return $this->selectAllQuery()->where('v.id IN (:ids)');
}
/**
* @return InsertQuery
*/
protected function insertQuery()
{
return $this->db->insert()->into(self::TABLE_NAME);
}
/**
* @return UpdateQuery
*/
protected function updateQuery()
{
return $this->db->update()->table(self::TABLE_NAME)->where('id = :id');
}
/**
* @return false
*/
protected function deleteQuery()
{
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More