Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
var AddressDuplicates = (function ($) {
|
||||
'use strict';
|
||||
|
||||
var me = {
|
||||
|
||||
storage: {
|
||||
$fields: [],
|
||||
fieldConfig: [],
|
||||
debugging: false
|
||||
},
|
||||
|
||||
init: function () {
|
||||
me.registerEvents();
|
||||
},
|
||||
|
||||
registerEvents: function () {
|
||||
var isValid = true;
|
||||
|
||||
$('#name, #strasse, #plz, #ort').on('blur', function () {
|
||||
me.checkDuplicate($(this)).done(function (validationResult) {
|
||||
if(validationResult){
|
||||
if ($('#name').hasClass('duplicated_address_error')) {
|
||||
return; // Fehlermeldung wird schon angezeigt
|
||||
}
|
||||
$('<span class="duplicated_address" style="color:red">').html('Möglicherweise doppelt').insertAfter($('#name'));
|
||||
$('#name').addClass('duplicated_address_error');
|
||||
}else{
|
||||
$('#name').removeClass('duplicated_address_error');
|
||||
$('#name').next('span.duplicated_address').remove();
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
checkDuplicate: function () {
|
||||
var nameValue = $('#name').val();
|
||||
var streetValue = $('#strasse').val();
|
||||
var zipcodeValue = $('#plz').val();
|
||||
var placeValue = $('#ort').val();
|
||||
|
||||
return jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?module=adresse&action=create&cmd=duplicate',
|
||||
data: {name: nameValue, street: streetValue, zipcode: zipcodeValue, place: placeValue},
|
||||
dataType: 'json',
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
init: me.init
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
AddressDuplicates.init();
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
$(document).ready(function() {
|
||||
$('#e_adresse').focus();
|
||||
|
||||
$(document).on('click', '.address-label-edit', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var labelId = $(this).data('address-label-id');
|
||||
AdressetikettenEdit(labelId);
|
||||
});
|
||||
|
||||
$(document).on('click', '.address-label-delete', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var labelId = $(this).data('address-label-id');
|
||||
AdressetikettenDelete(labelId);
|
||||
});
|
||||
|
||||
$("#editAdressetiketten").dialog({
|
||||
modal: true,
|
||||
bgiframe: true,
|
||||
closeOnEscape:false,
|
||||
minWidth:650,
|
||||
maxHeight:700,
|
||||
autoOpen: false,
|
||||
buttons: {
|
||||
ABBRECHEN: function() {
|
||||
AdressetikettenReset();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
SPEICHERN: function() {
|
||||
AdressetikettenEditSave();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#editAdressetiketten").dialog({
|
||||
close: function( event, ui ) { AdressetikettenReset();}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function AdressetikettenReset()
|
||||
{
|
||||
$('#editAdressetiketten').find('#e_id').val('');
|
||||
$('#editAdressetiketten').find('#e_adresse').val('');
|
||||
$('#editAdressetiketten').find('#e_etikett').val('');
|
||||
$('#editAdressetiketten').find('#e_verwenden_als').val('');
|
||||
}
|
||||
|
||||
function AdressetikettenEditSave() {
|
||||
$.ajax({
|
||||
url: 'index.php?module=adressabhaengigesetikett&action=save',
|
||||
data: {
|
||||
//Alle Felder die fürs editieren vorhanden sind
|
||||
id: $('#e_id').val(),
|
||||
adresse: $('#e_adresse').val(),
|
||||
etikett: $('#e_etikett').val(),
|
||||
verwenden_als: $('#e_verwenden_als').val()
|
||||
},
|
||||
method: 'post',
|
||||
dataType: 'json',
|
||||
beforeSend: function() {
|
||||
App.loading.open();
|
||||
},
|
||||
success: function(data) {
|
||||
App.loading.close();
|
||||
if (data.status == 1) {
|
||||
AdressetikettenReset();
|
||||
updateLiveTable();
|
||||
$("#editAdressetiketten").dialog('close');
|
||||
} else {
|
||||
alert(data.statusText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function AdressetikettenEdit(id) {
|
||||
if(id > 0)
|
||||
{
|
||||
$.ajax({
|
||||
url: 'index.php?module=adressabhaengigesetikett&action=edit&cmd=get',
|
||||
data: {
|
||||
id: id
|
||||
},
|
||||
method: 'post',
|
||||
dataType: 'json',
|
||||
beforeSend: function() {
|
||||
App.loading.open();
|
||||
},
|
||||
success: function(data) {
|
||||
$('#editAdressetiketten').find('#e_id').val(data.id);
|
||||
$('#editAdressetiketten').find('#e_adresse').val(data.adresse);
|
||||
$('#editAdressetiketten').find('#e_etikett').val(data.etikett);
|
||||
$('#editAdressetiketten').find('#e_verwenden_als').val(data.verwenden_als);
|
||||
|
||||
App.loading.close();
|
||||
$("#editAdressetiketten").dialog('open');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
AdressetikettenReset();
|
||||
$("#editAdressetiketten").dialog('open');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateLiveTable(i) {
|
||||
var oTableL = $('#adressetiketten_list').dataTable();
|
||||
var tmp = $('.dataTables_filter input[type=search]').val();
|
||||
oTableL.fnFilter('%');
|
||||
//oTableL.fnFilter('');
|
||||
oTableL.fnFilter(tmp);
|
||||
}
|
||||
|
||||
function AdressetikettenDelete(id) {
|
||||
var conf = confirm('Wirklich löschen?');
|
||||
if (conf) {
|
||||
$.ajax({
|
||||
url: 'index.php?module=adressabhaengigesetikett&action=delete',
|
||||
data: {
|
||||
id: id
|
||||
},
|
||||
method: 'post',
|
||||
dataType: 'json',
|
||||
beforeSend: function() {
|
||||
App.loading.open();
|
||||
},
|
||||
success: function(data) {
|
||||
if (data.status == 1) {
|
||||
updateLiveTable();
|
||||
} else {
|
||||
alert(data.statusText);
|
||||
}
|
||||
App.loading.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice;
|
||||
|
||||
use Xentral\Core\DependencyInjection\ContainerInterface;
|
||||
|
||||
use Xentral\Modules\AmaInvoice\Scheduler\AmaInvoiceTask;
|
||||
use Xentral\Modules\AmaInvoice\Service\AmaInvoiceService;
|
||||
use Xentral\Modules\SuperSearch\Wrapper\CompanyConfigWrapper;
|
||||
|
||||
|
||||
final class Bootstrap
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function registerServices()
|
||||
{
|
||||
return [
|
||||
'AmaInvoiceService' => 'onInitAmaInvoiceService',
|
||||
// Cronjob-Tasks
|
||||
'AmaInvoiceTask' => 'onInitAmaInvoiceTask',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return AmaInvoiceTask
|
||||
*/
|
||||
public static function onInitAmaInvoiceTask(ContainerInterface $container)
|
||||
{
|
||||
return new AmaInvoiceTask(
|
||||
$container->get('AmaInvoiceService'),
|
||||
self::onInitCompanyConfigWrapper($container)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return AmaInvoiceService
|
||||
*/
|
||||
public static function onInitAmaInvoiceService(ContainerInterface $container)
|
||||
{
|
||||
return new AmaInvoiceService(
|
||||
$container->get('Database'),
|
||||
$container->get('FilesystemFactory'),
|
||||
$container->get('LegacyApplication')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return CompanyConfigWrapper
|
||||
*/
|
||||
private static function onInitCompanyConfigWrapper(ContainerInterface $container)
|
||||
{
|
||||
/** @var \ApplicationCore $app */
|
||||
$app = $container->get('LegacyApplication');
|
||||
|
||||
return new CompanyConfigWrapper($app->erp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Exception;
|
||||
|
||||
interface AmaInvoiceExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Exception;
|
||||
|
||||
final class AmazonInvoiceServiceException extends \InvalidArgumentException implements AmaInvoiceExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Exception;
|
||||
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements AmaInvoiceExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class SchedulerTaskAlreadyRunningException extends RuntimeException implements AmaInvoiceExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Exception;
|
||||
|
||||
final class ThrottlingException extends \InvalidArgumentException implements AmaInvoiceExceptionInterface
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Xentral\Modules\AmaInvoice\Scheduler;
|
||||
|
||||
use Exception;
|
||||
use Xentral\Modules\AmaInvoice\Exception\ThrottlingException;
|
||||
use Xentral\Modules\AmaInvoice\Service\AmaInvoiceService;
|
||||
use Xentral\Modules\AmaInvoice\Exception\SchedulerTaskAlreadyRunningException;
|
||||
use Xentral\Modules\SuperSearch\Wrapper\CompanyConfigWrapper;
|
||||
|
||||
final class AmaInvoiceTask
|
||||
{
|
||||
/** @var AmaInvoiceService $service */
|
||||
private $service;
|
||||
|
||||
/** @var CompanyConfigWrapper $config */
|
||||
private $config;
|
||||
|
||||
/** @var bool */
|
||||
private $useFtp = false;
|
||||
|
||||
|
||||
/**
|
||||
* AmaInvoiceService constructor.
|
||||
*
|
||||
* @param AmaInvoiceService $service
|
||||
* @param CompanyConfigWrapper $config
|
||||
*/
|
||||
public function __construct($service, $config)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
$taskActive = (int)$this->config->get('amainvoice_task_mutex');
|
||||
if ($taskActive > 0) {
|
||||
throw new SchedulerTaskAlreadyRunningException(
|
||||
'Amainvoice task is already running. Task can only run once at a time.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->config->set('amainvoice_task_mutex', '1');
|
||||
|
||||
$this->syncNewFiles();
|
||||
$this->config->set('amainvoice_task_mutex', '1');
|
||||
$this->service->executeImportDateDbEntries(false, false);
|
||||
$this->config->set('amainvoice_task_mutex', '1');
|
||||
$this->service->executeImportDateDbEntries(false, true);
|
||||
$this->config->set('amainvoice_task_mutex', '1');
|
||||
$this->service->executeImportDateDbEntries(true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function syncNewFiles(): void
|
||||
{
|
||||
$files = $this->service->getNewFiles();
|
||||
$csvFiles = [];
|
||||
$datevFiles = [];
|
||||
$positions = [];
|
||||
$datevs = [];
|
||||
$pdfFiles = [];
|
||||
$dateFiles = $this->getFirstApiFiles($files);
|
||||
if ($this->useFtp) {
|
||||
$csvFiles = $this->getExportCsvs($files);
|
||||
$datevFiles = $this->getDatevFiles($files);
|
||||
[$invoicePdfFiles, $returnOrderPdfFiles] = $this->getPdfFiles($files);
|
||||
$pdfFiles = array_merge($invoicePdfFiles, $returnOrderPdfFiles);
|
||||
foreach ($csvFiles as $csvFile) {
|
||||
$position = $this->syncExportCsv($csvFile);
|
||||
if (!empty($position)) {
|
||||
foreach ($position as $pos) {
|
||||
$positions[] = $pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($datevFiles as $datevFile) {
|
||||
$datev = $this->syncDatevCsv($datevFile);
|
||||
if (!empty($datev)) {
|
||||
foreach ($datev as $pos) {
|
||||
$datevs[] = $pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['invoice', 'returnorder'] as $type) {
|
||||
foreach ($dateFiles[$type] as $file) {
|
||||
try {
|
||||
$this->service->executeImportDateFile($file);
|
||||
}
|
||||
catch(ThrottlingException $e) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
if ($this->useFtp) {
|
||||
$datev = empty($datevs[$type]) ? [] : $datevs[$type];
|
||||
if (empty($positions[$type])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($datev as $amazonOrderId => $documents) {
|
||||
if (empty($positions[$type][$amazonOrderId])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($documents as $number => $document) {
|
||||
$numberInvoice = $number;
|
||||
if (empty($positions[$type][$amazonOrderId][$number])) {
|
||||
$numberInvoice = substr($number, 3);
|
||||
if (empty($positions[$type][$amazonOrderId][$numberInvoice])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$pdfFile = in_array($number . '.pdf', $pdfFiles, true) ? $number . '.pdf' : null;
|
||||
if (
|
||||
$this->service->createDocument(
|
||||
$type,
|
||||
$amazonOrderId,
|
||||
$number,
|
||||
$document,
|
||||
$positions[$type][$amazonOrderId][$numberInvoice],
|
||||
$pdfFile
|
||||
)
|
||||
) {
|
||||
if ($pdfFile !== null) {
|
||||
$this->service->markFile($pdfFile, 'pdf', 'imported');
|
||||
}
|
||||
} elseif ($pdfFile !== null) {
|
||||
$this->service->markFile($pdfFile, 'pdf', 'error');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->useFtp) {
|
||||
foreach ($datevFiles as $datevFile) {
|
||||
$this->service->markFile($datevFile, 'datev', 'imported');
|
||||
$this->service->cleanFile($datevFile);
|
||||
}
|
||||
|
||||
foreach ($csvFiles as $csvFile) {
|
||||
$this->service->markFile($csvFile, 'csv', 'imported');
|
||||
$this->service->cleanFile($csvFile);
|
||||
}
|
||||
foreach ($pdfFiles as $pdfFile) {
|
||||
$this->service->cleanFile($pdfFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function syncDatevCsv($file): array
|
||||
{
|
||||
$csvFile = $this->service->getFile($file);
|
||||
|
||||
return $this->service->getPositionFromDatevCsv($csvFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @throws Exception
|
||||
* @return array
|
||||
*/
|
||||
private function syncExportCsv($file): array
|
||||
{
|
||||
$csvFile = $this->service->getFile($file);
|
||||
|
||||
return $this->service->getPositionFromExportCsv($csvFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private function getPdfFiles($files): array
|
||||
{
|
||||
if (empty($files)) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
$ret = [[], []];
|
||||
foreach ($files as $file) {
|
||||
if (substr($file, -4) === '.pdf') {
|
||||
if (strpos($file, 'GS') === 0) {
|
||||
$ret[1][] = $file;
|
||||
}
|
||||
$ret[0][] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDatevFiles($files): array
|
||||
{
|
||||
if (empty($files)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
foreach ($files as $file) {
|
||||
if (strpos($file, 'EXTF_ERLOESE') === 0 && substr($file, -4) === '.csv') {
|
||||
$ret[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*
|
||||
* @return array|array[]
|
||||
*/
|
||||
private function getFirstApiFiles($files): array
|
||||
{
|
||||
$ret = [
|
||||
'invoice' => [],
|
||||
'returnorder' => [],
|
||||
];
|
||||
if (empty($files)) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (substr($file, -4) === '.inv') {
|
||||
$ret['invoice'][] = $file;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
if (substr($file, -4) === '.rem') {
|
||||
$ret['returnorder'][] = $file;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getExportCsvs($files): array
|
||||
{
|
||||
if (empty($files)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
foreach ($files as $file) {
|
||||
if (strpos($file, 'export_') === 0 && substr($file, -4) === '.csv') {
|
||||
$ret[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup(): void
|
||||
{
|
||||
$this->config->set('amainvoice_task_mutex', '0');
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
var AmaInvoice = function ($) {
|
||||
'use strict';
|
||||
|
||||
var me = {
|
||||
storage: {
|
||||
tableList: []
|
||||
},
|
||||
expertChange: function () {
|
||||
if ($('#expert').prop('checked')) {
|
||||
$('.trexpert').show();
|
||||
} else {
|
||||
$('.trexpert').hide();
|
||||
}
|
||||
},
|
||||
init: function () {
|
||||
$('#import').on('click', function () {
|
||||
me.storage.tableList = [];
|
||||
$('#amainvoice_list').find(':checked').each(function () {
|
||||
me.storage.tableList.push($(this).data('id'));
|
||||
});
|
||||
if (me.storage.tableList.length > 0) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'index.php?module=amainvoice&action=list&cmd=importlist',
|
||||
data: {
|
||||
list: me.storage.tableList
|
||||
},
|
||||
success: function () {
|
||||
me.storage.tableList = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if ($('#createorder').prop('checked')) {
|
||||
$('#expert').prop('checked', true);
|
||||
}
|
||||
$('#expert').on('change', function () {
|
||||
me.expertChange();
|
||||
});
|
||||
me.expertChange();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
init: me.init
|
||||
};
|
||||
|
||||
}(jQuery);
|
||||
|
||||
$(document).ready(function () {
|
||||
AmaInvoice.init();
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
#frmcreateshipment span.hiddenfilter
|
||||
{
|
||||
width:0;
|
||||
padding:0;
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
display:inline-block;
|
||||
}
|
||||
#frmcreateshipment input#weiter{
|
||||
background-color:#A2D624;
|
||||
}
|
||||
|
||||
#frmcreateshipment table#amazon_anlieferungcreate > tbody > tr > td:nth-child(8) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
var cartonfocus = null;
|
||||
$(document).ready(function() {
|
||||
if($('form#frmcreateshipment').length)
|
||||
{
|
||||
$('#fartikel').trigger('change');
|
||||
}
|
||||
$('input.cartoninput').on('change',function(){
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=carton&cmd=changedimensions',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
plan:$(this).data('plan'),
|
||||
nr:$(this).data('nr'),
|
||||
weight:$(this).parents('tr').first().find('input.cartonweight').val(),
|
||||
length:$(this).parents('tr').first().find('input.cartonlength').val(),
|
||||
height:$(this).parents('tr').first().find('input.cartonheight').val(),
|
||||
width:$(this).parents('tr').first().find('input.cartonwidth').val(),
|
||||
},
|
||||
success: function (data) {
|
||||
if(typeof data.max_nr != 'undefined') {
|
||||
if($('#dimension_'+data.max_nr).hasClass('hide')) {
|
||||
$("#amazon_anlieferungcreate_carton").DataTable( ).ajax.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('input.cartoncopywithamount').on('click',function(){
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=carton&cmd=copycartonwithamount',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
plan:$(this).data('plan'),
|
||||
nr:$(this).data('nr'),
|
||||
weight:$(this).parents('tr').first().find('input.cartonweight').val(),
|
||||
length:$(this).parents('tr').first().find('input.cartonlength').val(),
|
||||
height:$(this).parents('tr').first().find('input.cartonheight').val(),
|
||||
width:$(this).parents('tr').first().find('input.cartonwidth').val(),
|
||||
},
|
||||
success: function (data) {
|
||||
if(typeof data.max_nr != 'undefined') {
|
||||
for(var i = 1; i <= data.max_nr; i++) {
|
||||
$('input.cartonweight').val(data.weight);
|
||||
$('input.cartonlength').val(data.length);
|
||||
$('input.cartonheight').val(data.height);
|
||||
$('input.cartonwidth').val(data.width);
|
||||
}
|
||||
if($('#dimension_'+data.max_nr).hasClass('hide')) {
|
||||
$("#amazon_anlieferungcreate_carton").DataTable( ).ajax.reload();
|
||||
}
|
||||
if(typeof data.success != 'undefined' && data.success) {
|
||||
$("#amazon_anlieferungcreate_carton").DataTable( ).ajax.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var nr = $(this).data('nr');
|
||||
var weight = $(this).parents('tr').first().find('input.cartonweight').val();
|
||||
var length = $(this).parents('tr').first().find('input.cartonlength').val();
|
||||
var height = $(this).parents('tr').first().find('input.cartonheight').val();
|
||||
var width = $(this).parents('tr').first().find('input.cartonwidth').val();
|
||||
});
|
||||
$('input.cartoncopy').on('click',function(){
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=carton&cmd=changealldimensions',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
plan:$(this).data('plan'),
|
||||
nr:$(this).data('nr'),
|
||||
weight:$(this).parents('tr').first().find('input.cartonweight').val(),
|
||||
length:$(this).parents('tr').first().find('input.cartonlength').val(),
|
||||
height:$(this).parents('tr').first().find('input.cartonheight').val(),
|
||||
width:$(this).parents('tr').first().find('input.cartonwidth').val(),
|
||||
},
|
||||
success: function (data) {
|
||||
if(typeof data.max_nr != 'undefined') {
|
||||
for(var i = 1; i <= data.max_nr; i++) {
|
||||
$('input.cartonweight').val(data.weight);
|
||||
$('input.cartonlength').val(data.length);
|
||||
$('input.cartonheight').val(data.height);
|
||||
$('input.cartonwidth').val(data.width);
|
||||
}
|
||||
if($('#dimension_'+data.max_nr).hasClass('hide')) {
|
||||
$("#amazon_anlieferungcreate_carton").DataTable( ).ajax.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var nr = $(this).data('nr');
|
||||
var weight = $(this).parents('tr').first().find('input.cartonweight').val();
|
||||
var length = $(this).parents('tr').first().find('input.cartonlength').val();
|
||||
var height = $(this).parents('tr').first().find('input.cartonheight').val();
|
||||
var width = $(this).parents('tr').first().find('input.cartonwidth').val();
|
||||
});
|
||||
|
||||
|
||||
$('#amazon_anlieferungcreate_nonparcel').on('afterreload',function(){
|
||||
$('#amazon_anlieferungcreate_nonparcel .mhd').each(function() {
|
||||
$( this).autocomplete({
|
||||
source: function( request, response ) {
|
||||
$.ajax( {
|
||||
url: 'index.php?module=ajax&action=filter&rmodule=amazon&raction=new&rid=&filtername=lagermhdcharge&artikel='+encodeURI($(this.element).first().data('artikel')),
|
||||
dataType: 'json',
|
||||
data: {
|
||||
term: request.term
|
||||
},
|
||||
success: function( data ) {
|
||||
if(data == null)
|
||||
{
|
||||
response ([]);
|
||||
}else
|
||||
response( data.length === 1 && data[ 0 ].length === 0 ? [] : data );
|
||||
}
|
||||
});
|
||||
},select: function( event, ui ) {
|
||||
var i = ui.item.value;
|
||||
var zahl = i.indexOf(" ");
|
||||
var text = i.slice(0, zahl);
|
||||
$( this ).val( text );
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('#amazon_anlieferungcreate_nonparcel .mhd').on('change', function() {
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=new&cmd=change',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
bestbefore: $(this).parents('tr').first().find('.mhd').val(),
|
||||
numberofcases: $(this).parents('tr').first().find('.numberofcases').val(),
|
||||
unitspercase: $(this).parents('tr').first().find('.unitspercase').val(),
|
||||
article_id: $(this).data('artikel'),
|
||||
prep_polybagging: $(this).parents('tr').first().find('.prep_Polybagging:checked').lengh,
|
||||
prep_bubblewrapping: $(this).parents('tr').first().find('.prep_BubbleWrapping:checked').lengh,
|
||||
prep_taping: $(this).parents('tr').first().find('.prep_Taping:checked').lengh,
|
||||
prep_blackshrinkwrapping: $(this).parents('tr').first().find('.prep_BlackShrinkWrapping:checked').lengh,
|
||||
prep_labeling: $(this).parents('tr').first().find('.prep_Labeling:checked').lengh,
|
||||
prep_hangharment: $(this).parents('tr').first().find('.prep_HangGarment:checked').lengh
|
||||
},
|
||||
success: function (data) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#amazon_anlieferungcreate_nonparcel .numberofcases').on('change', function() {
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=new&cmd=change',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
bestbefore: $(this).parents('tr').first().find('.mhd').val(),
|
||||
numberofcases: $(this).parents('tr').first().find('.numberofcases').val(),
|
||||
unitspercase: $(this).parents('tr').first().find('.unitspercase').val(),
|
||||
article_id: $(this).data('artikel'),
|
||||
prep_polybagging: $(this).parents('tr').first().find('.prep_Polybagging:checked').lengh,
|
||||
prep_bubblewrapping: $(this).parents('tr').first().find('.prep_BubbleWrapping:checked').lengh,
|
||||
prep_taping: $(this).parents('tr').first().find('.prep_Taping:checked').lengh,
|
||||
prep_blackshrinkwrapping: $(this).parents('tr').first().find('.prep_BlackShrinkWrapping:checked').lengh,
|
||||
prep_labeling: $(this).parents('tr').first().find('.prep_Labeling:checked').lengh,
|
||||
prep_hangharment: $(this).parents('tr').first().find('.prep_HangGarment:checked').lengh
|
||||
},
|
||||
success: function (data) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#amazon_anlieferungcreate_nonparcel .prep').on('change', function() {
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=new&cmd=change',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
bestbefore: $(this).parents('tr').first().find('.mhd').val(),
|
||||
numberofcases: $(this).parents('tr').first().find('.numberofcases').val(),
|
||||
unitspercase: $(this).parents('tr').first().find('.unitspercase').val(),
|
||||
article_id: $(this).data('artikel'),
|
||||
prep_polybagging: $(this).parents('tr').first().find('.prep_Polybagging:checked').lengh,
|
||||
prep_bubblewrapping: $(this).parents('tr').first().find('.prep_BubbleWrapping:checked').lengh,
|
||||
prep_taping: $(this).parents('tr').first().find('.prep_Taping:checked').lengh,
|
||||
prep_blackshrinkwrapping: $(this).parents('tr').first().find('.prep_BlackShrinkWrapping:checked').lengh,
|
||||
prep_labeling: $(this).parents('tr').first().find('.prep_Labeling:checked').lengh,
|
||||
prep_hangharment: $(this).parents('tr').first().find('.prep_HangGarment:checked').lengh
|
||||
},
|
||||
success: function (data) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#amazon_anlieferungcreate_nonparcel .unitspercase').on('change', function() {
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=new&cmd=change',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
bestbefore: $(this).parents('tr').first().find('.mhd').val(),
|
||||
numberofcases: $(this).parents('tr').first().find('.numberofcases').val(),
|
||||
unitspercase: $(this).parents('tr').first().find('.unitspercase').val(),
|
||||
article_id: $(this).data('artikel'),
|
||||
prep_polybagging: $(this).parents('tr').first().find('.prep_Polybagging:checked').lengh,
|
||||
prep_bubblewrapping: $(this).parents('tr').first().find('.prep_BubbleWrapping:checked').lengh,
|
||||
prep_taping: $(this).parents('tr').first().find('.prep_Taping:checked').lengh,
|
||||
prep_blackshrinkwrapping: $(this).parents('tr').first().find('.prep_BlackShrinkWrapping:checked').lengh,
|
||||
prep_labeling: $(this).parents('tr').first().find('.prep_Labeling:checked').lengh,
|
||||
prep_hangharment: $(this).parents('tr').first().find('.prep_HangGarment:checked').lengh
|
||||
},
|
||||
success: function (data) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
$('#amazon_anlieferungcreate_nonparcel').trigger('afterreload');
|
||||
/*$('#typ').on('change',function(){
|
||||
if($(this).val()==='palette')
|
||||
{
|
||||
$('.adresse').show();
|
||||
}else{
|
||||
$('.adresse').hide();
|
||||
}
|
||||
});
|
||||
$('#typ').trigger('change');
|
||||
*/
|
||||
|
||||
$('#amazon_anlieferungcreate_carton').on('afterreload',function() {
|
||||
if(cartonfocus) {
|
||||
var num = $('#'+cartonfocus).val();
|
||||
$('#'+cartonfocus).val('').trigger('focus').val(num);
|
||||
}
|
||||
for(var i = 2; i <= 20; i++) {
|
||||
if($("#amazon_anlieferungcreate_carton").find("input.showcol[data-nr='" + i + "']").length)
|
||||
{
|
||||
$('#amazon_anlieferungcreate_carton > thead > tr > th:nth-child('+(i+1)+')').show();
|
||||
$('#amazon_anlieferungcreate_carton > tfoot > tr > th:nth-child('+(i+1)+')').show();
|
||||
$('#amazon_anlieferungcreate_carton > tbody > tr > td:nth-child('+(i+1)+')').show();
|
||||
$('#dimension_'+i).show();
|
||||
$('#dimension_'+i).toggleClass('hide', false);
|
||||
if($('#dimension_'+i+'.empty').length) {
|
||||
$.ajax({
|
||||
url: 'index.php?module=amazon&action=carton&cmd=getdimension',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: {
|
||||
plan:$(this).data('plan'),
|
||||
nr:i
|
||||
},
|
||||
success: function (data) {
|
||||
//$('input.cartonlength[data-]')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if($("#amazon_anlieferungcreate_carton").find("input.hidecol[data-nr='" + i + "']").length)
|
||||
{
|
||||
$('#amazon_anlieferungcreate_carton > thead > tr > th:nth-child('+(i+1)+')').hide();
|
||||
$('#amazon_anlieferungcreate_carton > tfoot > tr > th:nth-child('+(i+1)+')').hide();
|
||||
$('#amazon_anlieferungcreate_carton > tbody > tr > td:nth-child('+(i+1)+')').hide();
|
||||
$('#dimension_'+i).hide();
|
||||
}
|
||||
}
|
||||
$('#amazon_anlieferungcreate_carton .cartonmenge').on('change',function() {
|
||||
$.ajax( {
|
||||
url: 'index.php?module=amazon&action=carton&cmd=change',
|
||||
dataType: 'json',
|
||||
type:'POST',
|
||||
data: {
|
||||
el: this.id,value:$(this).val()
|
||||
},
|
||||
success: function( data ) {
|
||||
var oTable = $('#amazon_anlieferungcreate_carton').DataTable( );
|
||||
oTable.ajax.reload();
|
||||
|
||||
/*if(typeof data.arr != 'undefined' && data.arr.length > 0)
|
||||
{
|
||||
var ths = $('#amazon_anlieferungcreate_carton tfoot tr').first().find('th');
|
||||
var i = 0;
|
||||
for(i = 0; i < data.arr.length; i++)
|
||||
{
|
||||
if(data.arr[i].anz != data.arr[i].menge)
|
||||
{
|
||||
$(ths[i + 1]).html('<span style="color:red">'+data.arr[i].anz + ' / ' + data.arr[i].menge+'</span>');
|
||||
}else {
|
||||
$(ths[i + 1]).html(data.arr[i].anz + ' / ' + data.arr[i].menge);
|
||||
}
|
||||
|
||||
}
|
||||
}*/
|
||||
if(typeof data.ok != 'undefined' && data.ok == 1)
|
||||
{
|
||||
$('#weiter').prop('disabled', false);
|
||||
} else{
|
||||
$('#weiter').prop('disabled', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
$('#amazon_anlieferungcreate_carton .cartonmenge').on('focus',function(){
|
||||
cartonfocus = this.id;
|
||||
});
|
||||
$('#amazon_anlieferungcreate_carton .cartonmenge').on('focusout',function(){
|
||||
cartonfocus = null;
|
||||
});
|
||||
});
|
||||
$('#amazon_anlieferungcreate_carton').trigger('afterreload');
|
||||
setTimeout(function() {
|
||||
$('#amazon_anlieferungcreate_carton .cartonmenge').first().trigger('change');
|
||||
},200);
|
||||
if($('form#frmAmazonNew').length) {
|
||||
var $thead = $('#amazon_anlieferungcreate_nonparcel').find('thead');
|
||||
var thlength = $($thead).find('tr').first().find('th').length;
|
||||
if(thlength > 13) {
|
||||
$($thead).find('tr').last().after(
|
||||
'<tr class="checkall"><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'
|
||||
);
|
||||
$trall = $($thead).find('tr.checkall');
|
||||
for(var i = 13; i < thlength; i++) {
|
||||
$($trall).html(
|
||||
$($trall).html() + '<td><input type="checkbox" class=\"call\" /></td>'
|
||||
);
|
||||
}
|
||||
$($trall).find('input.call').on('change',function(){
|
||||
var nr = $(this).parents('td').first().prevAll().length;
|
||||
var value = $(this).prop('checked');
|
||||
$('#amazon_anlieferungcreate_nonparcel').find('tbody > tr').each(function(){
|
||||
var $tds = $(this).find('td')[nr];
|
||||
$($tds).find('input').prop('checked', value);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF;
|
||||
|
||||
use Xentral\Components\SchemaCreator\Collection\SchemaCollection;
|
||||
use Xentral\Components\SchemaCreator\Index;
|
||||
use Xentral\Components\SchemaCreator\Option\TableOption;
|
||||
use Xentral\Components\SchemaCreator\Schema\TableSchema;
|
||||
use Xentral\Components\SchemaCreator\Type;
|
||||
use Xentral\Core\DependencyInjection\ContainerInterface;
|
||||
|
||||
final class Bootstrap
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function registerServices(): array
|
||||
{
|
||||
return [
|
||||
'PurchaseOrderInformationRepository' => 'onInitPurchaseOrderInformationRepository',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return PurchaseOrderInformationRepository
|
||||
*/
|
||||
public static function onInitPurchaseOrderInformationRepository(ContainerInterface $container
|
||||
): PurchaseOrderInformationRepository {
|
||||
return new PurchaseOrderInformationRepository(
|
||||
$container->get('Database')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\MissingInformationException;
|
||||
|
||||
class AcknowledgementItem
|
||||
{
|
||||
/**
|
||||
* Shipping 100 percent of ordered product
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CODE_ACCEPTED = '00';
|
||||
/**
|
||||
* Canceled out of stock
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CODE_REJECT_OUT_OF_STOCK = '03';
|
||||
/**
|
||||
* No article found for SKU
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CODE_REJECT_INVALID_SKU = '02';
|
||||
|
||||
const AVAILABLE_CODES = [
|
||||
'00' => 'Shipping 100 percent of ordered product',
|
||||
'02' => 'Canceled due to missing/invalid SKU',
|
||||
'03' => 'Canceled out of stock',
|
||||
'04' => 'Canceled due to duplicate Amazon Ship ID',
|
||||
'05' => 'Canceled due to missing/invalid Bill To Location Code',
|
||||
'06' => 'Canceled due to missing/invalid Ship From Location Code',
|
||||
'07' => 'Canceled due to missing/invalid Customer Ship to Name',
|
||||
'08' => 'Canceled due to missing/invalid Customer Ship to Address Line 1',
|
||||
'10' => 'Canceled due to missing/invalid Customer Ship to City',
|
||||
'11' => 'Canceled due to missing/invalid Customer Ship to State',
|
||||
'12' => 'Canceled due to missing/invalid Customer Ship to Postal Code',
|
||||
'13' => 'Canceled due to missing/invalid Customer Ship to Country Code',
|
||||
'20' => 'Canceled due to missing/invalid Shipping Carrier/Shipping Method',
|
||||
'21' => 'Canceled due to missing/invalid Ship to Address Line 2',
|
||||
'22' => 'Canceled due to missing/invalid Ship to Address Line 3',
|
||||
'50' => 'Canceled due to Tax Nexus Issue',
|
||||
'51' => 'Canceled due to Restricted SKU/Qty',
|
||||
];
|
||||
|
||||
/** @var PurchaseOrderItem */
|
||||
private $item;
|
||||
|
||||
/** @var string */
|
||||
private $code;
|
||||
|
||||
public function __construct(PurchaseOrderItem $item, string $code)
|
||||
{
|
||||
$this->item = $item;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function isRejected(): bool
|
||||
{
|
||||
return $this->code !== self::CODE_ACCEPTED;
|
||||
}
|
||||
|
||||
public function isAccepted(): bool
|
||||
{
|
||||
return $this->code === self::CODE_ACCEPTED;
|
||||
}
|
||||
|
||||
public function getStatusCode(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = [
|
||||
'itemSequenceNumber' => $this->item->getItemSequenceNumber(),
|
||||
'buyerProductIdentifier' => $this->item->getBuyerProductIdentifier(),
|
||||
'vendorProductIdentifier' => $this->item->getVendorProductIdentifier(),
|
||||
'acknowledgedQuantity' => $this->item->getQuantity()->toArray(),
|
||||
];
|
||||
|
||||
unset($data['acknowledgedQuantity']['unitSize']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class Address
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
/** @var array */
|
||||
private $addressLines;
|
||||
/** @var string */
|
||||
private $city;
|
||||
/** @var string */
|
||||
private $countryCode;
|
||||
/** @var string */
|
||||
private $postalCode;
|
||||
/** @var string */
|
||||
private $stateOrRegion;
|
||||
/** @var string */
|
||||
private $phone;
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAddressLines(array $addressLines): self
|
||||
{
|
||||
$this->addressLines = $addressLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCity(string $city): self
|
||||
{
|
||||
$this->city = $city;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCountryCode(string $countryCode): self
|
||||
{
|
||||
$this->countryCode = $countryCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function setPostalCode(string $postalCode): self
|
||||
{
|
||||
$this->postalCode = $postalCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStateOrRegion(string $stateOrRegion): self
|
||||
{
|
||||
$this->stateOrRegion = $stateOrRegion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPhone(string $phone): self
|
||||
{
|
||||
$this->phone = $phone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getAddressLines(): array
|
||||
{
|
||||
return $this->addressLines;
|
||||
}
|
||||
|
||||
public function getCity(): string
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
public function getCountryCode(): string
|
||||
{
|
||||
return $this->countryCode;
|
||||
}
|
||||
|
||||
public function getPostalCode(): string
|
||||
{
|
||||
return $this->postalCode;
|
||||
}
|
||||
|
||||
public function getStateOrRegion(): string
|
||||
{
|
||||
return $this->stateOrRegion;
|
||||
}
|
||||
|
||||
public function getPhone(): string
|
||||
{
|
||||
return $this->phone;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_filter([
|
||||
'name' => $this->name,
|
||||
'addressLine1' => $this->addressLines[0],
|
||||
'addressLine2' => $this->addressLines[1],
|
||||
'addressLine3' => $this->addressLines[2],
|
||||
'city' => $this->city,
|
||||
'stateOrRegion' => $this->stateOrRegion,
|
||||
'postalCode' => $this->postalCode,
|
||||
'countryCode' => $this->countryCode,
|
||||
'phone' => $this->phone,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class Container
|
||||
{
|
||||
/** @var string */
|
||||
private $containerType;
|
||||
/** @var string */
|
||||
private $containerIdentifier;
|
||||
/** @var string */
|
||||
private $length;
|
||||
/** @var string */
|
||||
private $width;
|
||||
/** @var string */
|
||||
private $height;
|
||||
/** @var string */
|
||||
private $unitOfMeasure;
|
||||
/** @var array */
|
||||
private $items = [];
|
||||
|
||||
public function __construct(string $containerIdentifier, string $containerType = 'carton')
|
||||
{
|
||||
$this->containerIdentifier = $containerIdentifier;
|
||||
$this->containerType = $containerType;
|
||||
}
|
||||
|
||||
public function setDimensions(string $length, string $width, string $height, string $unitOfMeasure = 'CM')
|
||||
{
|
||||
$this->length = $length;
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->unitOfMeasure = $unitOfMeasure;
|
||||
}
|
||||
|
||||
public function addItem(
|
||||
string $itemSequenceNumber,
|
||||
string $buyerProductIdentifier,
|
||||
string $vendorProductIdentifier,
|
||||
Quantity $packedQuantity
|
||||
) {
|
||||
$quantity = $packedQuantity->toArray();
|
||||
unset($quantity['unitSize']);
|
||||
$this->items[] = [
|
||||
'itemSequenceNumber' => (int)$itemSequenceNumber,
|
||||
'buyerProductIdentifier' => $buyerProductIdentifier,
|
||||
'vendorProductIdentifier' => $vendorProductIdentifier,
|
||||
'packedQuantity' => $quantity,
|
||||
];
|
||||
}
|
||||
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'containerType' => $this->containerType,
|
||||
'containerIdentifier' => $this->containerIdentifier,
|
||||
'dimensions' => [
|
||||
'length' => $this->length,
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'unitOfMeasure' => $this->unitOfMeasure,
|
||||
],
|
||||
'weight' => [
|
||||
'unitOfMeasure' => 'KG',
|
||||
'value' => '1',
|
||||
],
|
||||
'packedItems' => $this->items,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class InventoryItem
|
||||
{
|
||||
/** @var Quantity */
|
||||
private $quantity;
|
||||
/** @var string|null */
|
||||
private $vendorProductIdentifier;
|
||||
/** @var bool|null */
|
||||
private $isObsolete;
|
||||
/** @var string */
|
||||
private $buyerProductIdentifier;
|
||||
|
||||
public function __construct(
|
||||
Quantity $quantity,
|
||||
?string $vendorProductIdentifier = null,
|
||||
?bool $isObsolete = false,
|
||||
?string $buyerProductIdentifier = null
|
||||
) {
|
||||
$this->quantity = $quantity;
|
||||
$this->vendorProductIdentifier = $vendorProductIdentifier;
|
||||
$this->isObsolete = $isObsolete;
|
||||
$this->buyerProductIdentifier = $buyerProductIdentifier;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_filter(
|
||||
[
|
||||
'buyerProductIdentifier' => $this->buyerProductIdentifier,
|
||||
'vendorProductIdentifier' => $this->vendorProductIdentifier,
|
||||
'availableQuantity' => $this->quantity->toArray(),
|
||||
'isObsolete' => $this->isObsolete,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\MissingInformationException;
|
||||
|
||||
class Invoice
|
||||
{
|
||||
/** @var string */
|
||||
private $invoiceNumber;
|
||||
/** @var DateTime */
|
||||
private $invoiceDate;
|
||||
/** @var Address */
|
||||
private $billToAddress;
|
||||
/** @var Price */
|
||||
private $invoiceTotal;
|
||||
/** @var array|InvoiceItem[] */
|
||||
private $items;
|
||||
/** @var SellingParty */
|
||||
private $remitToParty;
|
||||
/** @var Warehouse */
|
||||
private $warehouse;
|
||||
|
||||
public function __construct(
|
||||
string $invoiceNumber,
|
||||
DateTime $invoiceDate,
|
||||
SellingParty $remitToParty,
|
||||
Warehouse $warehouse
|
||||
) {
|
||||
$this->invoiceNumber = $invoiceNumber;
|
||||
$this->invoiceDate = $invoiceDate;
|
||||
$this->remitToParty = $remitToParty;
|
||||
$this->warehouse = $warehouse;
|
||||
}
|
||||
|
||||
public function addItem(InvoiceItem $item): self
|
||||
{
|
||||
$this->items[] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBillToAddress(Address $address)
|
||||
{
|
||||
$this->billToAddress = $address;
|
||||
}
|
||||
|
||||
public function setInvoiceTotal(Price $invoiceTotal): self
|
||||
{
|
||||
$this->invoiceTotal = $invoiceTotal;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
if (!$this->invoiceTotal) {
|
||||
throw MissingInformationException::property('invoiceTotal');
|
||||
}
|
||||
|
||||
return [
|
||||
'invoiceNumber' => $this->invoiceNumber,
|
||||
'invoiceDate' => $this->invoiceDate,
|
||||
'remitToParty' => $this->remitToParty->toArray(),
|
||||
'shipFromParty' => $this->formatShipFromParty(),
|
||||
'invoiceTotal' => $this->invoiceTotal->toArray(),
|
||||
'taxTotals' => $this->grabTaxTotalsFromInvoiceItems(),
|
||||
'items' => $this->mapInvoiceItemsToArray(),
|
||||
];
|
||||
}
|
||||
|
||||
private function grabTaxTotalsFromInvoiceItems(): array
|
||||
{
|
||||
return array_map(function (InvoiceItem $item){
|
||||
return $item->getTaxDetails()->toArray();
|
||||
}, $this->items);
|
||||
}
|
||||
|
||||
private function mapInvoiceItemsToArray()
|
||||
{
|
||||
return array_map(
|
||||
function (InvoiceItem $item) {
|
||||
return $item->toArray();
|
||||
},
|
||||
$this->items
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently the warehouse uses the taxRegistrationDetails and address of the remitToParty
|
||||
*/
|
||||
private function formatShipFromParty(): array
|
||||
{
|
||||
$data = $this->remitToParty->toArray();
|
||||
$data['partyId'] = $this->warehouse->getWarehouseId();
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class InvoiceItem
|
||||
{
|
||||
/** @var string */
|
||||
private $itemSequenceNumber;
|
||||
/** @var string */
|
||||
private $buyerProductIdentifier;
|
||||
/** @var string */
|
||||
private $vendorProductIdentifier;
|
||||
/** @var Quantity */
|
||||
private $invoicedQuantity;
|
||||
/** @var Price */
|
||||
private $netCost;
|
||||
/** @var string */
|
||||
private $purchaseOrderNumber;
|
||||
/** @var TaxDetails */
|
||||
private $taxDetails;
|
||||
|
||||
public function getItemSequenceNumber(): string
|
||||
{
|
||||
return $this->itemSequenceNumber;
|
||||
}
|
||||
|
||||
public function setItemSequenceNumber(string $itemSequenceNumber): self
|
||||
{
|
||||
$this->itemSequenceNumber = $itemSequenceNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBuyerProductIdentifier(): string
|
||||
{
|
||||
return $this->buyerProductIdentifier;
|
||||
}
|
||||
|
||||
public function setBuyerProductIdentifier(string $buyerProductIdentifier): self
|
||||
{
|
||||
$this->buyerProductIdentifier = $buyerProductIdentifier;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVendorProductIdentifier(): string
|
||||
{
|
||||
return $this->vendorProductIdentifier;
|
||||
}
|
||||
|
||||
public function setVendorProductIdentifier(string $vendorProductIdentifier): self
|
||||
{
|
||||
$this->vendorProductIdentifier = $vendorProductIdentifier;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInvoicedQuantity(): Quantity
|
||||
{
|
||||
return $this->invoicedQuantity;
|
||||
}
|
||||
|
||||
public function setInvoicedQuantity(Quantity $invoicedQuantity): self
|
||||
{
|
||||
$this->invoicedQuantity = $invoicedQuantity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNetCost(): Price
|
||||
{
|
||||
return $this->netCost;
|
||||
}
|
||||
|
||||
public function setNetCost(Price $netCost): self
|
||||
{
|
||||
$this->netCost = $netCost;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPurchaseOrderNumber(): string
|
||||
{
|
||||
return $this->purchaseOrderNumber;
|
||||
}
|
||||
|
||||
public function setPurchaseOrderNumber(string $purchaseOrderNumber): self
|
||||
{
|
||||
$this->purchaseOrderNumber = $purchaseOrderNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxDetails(): TaxDetails
|
||||
{
|
||||
return $this->taxDetails;
|
||||
}
|
||||
|
||||
public function setTaxDetails(TaxDetails $taxDetails): self
|
||||
{
|
||||
$this->taxDetails = $taxDetails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'purchaseOrderNumber' => $this->purchaseOrderNumber,
|
||||
'itemSequenceNumber' => $this->itemSequenceNumber,
|
||||
'invoicedQuantity' => $this->invoicedQuantity->toArray(),
|
||||
'netCost' => $this->netCost->toArray(),
|
||||
'taxDetails' => $this->taxDetails->toArray(),
|
||||
// not implemented yet
|
||||
'chargeDetails' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class Price
|
||||
{
|
||||
/** @var string */
|
||||
private $currency;
|
||||
/** @var float */
|
||||
private $amount;
|
||||
|
||||
public function __construct(string $currency, float $amount)
|
||||
{
|
||||
$this->currency = $currency;
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
public function getAmount(): float
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'currencyCode' => $this->currency,
|
||||
'amount' => $this->amount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class PurchaseOrder
|
||||
{
|
||||
/** @var string */
|
||||
private $purchaseOrderNumber;
|
||||
/** @var DateTime|false */
|
||||
private $purchaseOrderDate;
|
||||
/** @var array|PurchaseOrderItem[] */
|
||||
private $items;
|
||||
/** @var SellingParty */
|
||||
private $sellingParty;
|
||||
/** @var Address */
|
||||
private $shipToParty;
|
||||
/** @var string */
|
||||
private $warehouseId;
|
||||
/** @var ShipmentDetails */
|
||||
private $shipmentDetails;
|
||||
/** @var array */
|
||||
private $rawData;
|
||||
|
||||
public function __construct(
|
||||
string $purchaseOrderNumber,
|
||||
$purchaseOrderDate,
|
||||
array $items,
|
||||
SellingParty $sellingParty,
|
||||
string $warehouseId,
|
||||
Address $shipToParty,
|
||||
ShipmentDetails $shipmentDetails,
|
||||
array $rawData = []
|
||||
) {
|
||||
$this->purchaseOrderNumber = $purchaseOrderNumber;
|
||||
$this->purchaseOrderDate = $purchaseOrderDate;
|
||||
$this->items = $items;
|
||||
$this->sellingParty = $sellingParty;
|
||||
$this->warehouseId = $warehouseId;
|
||||
$this->shipToParty = $shipToParty;
|
||||
$this->shipmentDetails = $shipmentDetails;
|
||||
$this->rawData = $rawData;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getPurchaseOrderNumber(): string
|
||||
{
|
||||
return $this->purchaseOrderNumber;
|
||||
}
|
||||
|
||||
/** @return DateTime|false */
|
||||
public function getPurchaseOrderDate()
|
||||
{
|
||||
return $this->purchaseOrderDate;
|
||||
}
|
||||
|
||||
/** @return array|PurchaseOrderItem[] */
|
||||
public function getItems()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/** @return SellingParty */
|
||||
public function getSellingParty(): SellingParty
|
||||
{
|
||||
return $this->sellingParty;
|
||||
}
|
||||
|
||||
public function getShipToParty(): Address
|
||||
{
|
||||
return $this->shipToParty;
|
||||
}
|
||||
|
||||
public function getShipmentDetails(): ShipmentDetails
|
||||
{
|
||||
return $this->shipmentDetails;
|
||||
}
|
||||
|
||||
public function getRawData(): array
|
||||
{
|
||||
return $this->rawData;
|
||||
}
|
||||
|
||||
public static function fromPurchaseOrderResponse(array $data)
|
||||
{
|
||||
if (isset($data['payload'])) {
|
||||
$data = $data['payload'];
|
||||
}
|
||||
|
||||
$items = array_map(
|
||||
function (array $item) {
|
||||
return PurchaseOrderItem::fromPurchaseOrderResponse($item);
|
||||
},
|
||||
$data['orderDetails']['items']
|
||||
);
|
||||
|
||||
$sellingParty = new SellingParty($data['orderDetails']['sellingParty']['partyId']);
|
||||
$shipToParty = (new Address())
|
||||
->setName($data['orderDetails']['shipToParty']['name'])
|
||||
->setAddressLines(
|
||||
[
|
||||
$data['orderDetails']['shipToParty']['addressLine1'],
|
||||
$data['orderDetails']['shipToParty']['addressLine2'],
|
||||
$data['orderDetails']['shipToParty']['addressLine3'],
|
||||
]
|
||||
)
|
||||
->setCity($data['orderDetails']['shipToParty']['city'])
|
||||
->setStateOrRegion($data['orderDetails']['shipToParty']['stateOrRegion'])
|
||||
->setPostalCode($data['orderDetails']['shipToParty']['postalCode'])
|
||||
->setCountryCode($data['orderDetails']['shipToParty']['countryCode']);
|
||||
|
||||
$shipmentDetails = new ShipmentDetails(
|
||||
$data['orderDetails']['shipmentDetails']['isPriorityShipment'],
|
||||
$data['orderDetails']['shipmentDetails']['isPslipRequired'],
|
||||
$data['orderDetails']['shipmentDetails']['shipMethod'],
|
||||
self::parseDate($data['orderDetails']['shipmentDetails']['shipmentDates']['requiredShipDate']),
|
||||
self::parseDate($data['orderDetails']['shipmentDetails']['shipmentDates']['promisedDeliveryDate']),
|
||||
isset($data['orderDetails']['shipmentDetails']['messageToCustomer'])
|
||||
? $data['orderDetails']['shipmentDetails']['messageToCustomer']
|
||||
: ''
|
||||
);
|
||||
|
||||
// @TODO billToParty needs to be set
|
||||
return new static(
|
||||
$data['purchaseOrderNumber'],
|
||||
self::parseDate($data['orderDetails']['orderDate']),
|
||||
$items,
|
||||
$sellingParty,
|
||||
$data['orderDetails']['shipFromParty']['partyId'],
|
||||
$shipToParty,
|
||||
$shipmentDetails,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
protected static function parseDate(string $iso8601DateString): DateTime
|
||||
{
|
||||
$date = DateTime::createFromFormat(
|
||||
DateTime::ISO8601,
|
||||
$iso8601DateString
|
||||
);
|
||||
|
||||
if(!$date instanceof DateTime){
|
||||
throw new InvalidArgumentException("Date is not in ISO8601 format: {$iso8601DateString}");
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\MissingInformationException;
|
||||
|
||||
class PurchaseOrderAcknowledgement
|
||||
{
|
||||
/** @var string */
|
||||
private $vendorOrderNumber;
|
||||
/** @var string */
|
||||
private $purchaseOrderNumber;
|
||||
/** @var SellingParty */
|
||||
private $sellingParty;
|
||||
/** @var Warehouse */
|
||||
private $warehouse;
|
||||
/** @var array|AcknowledgementItem[] */
|
||||
private $items = [];
|
||||
|
||||
public function __construct(string $purchaseOrderNumber)
|
||||
{
|
||||
$this->purchaseOrderNumber = $purchaseOrderNumber;
|
||||
}
|
||||
|
||||
public function getPurchaseOrderNumber(): string
|
||||
{
|
||||
return $this->purchaseOrderNumber;
|
||||
}
|
||||
|
||||
public function addItem(AcknowledgementItem $item): self
|
||||
{
|
||||
$this->items[] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setVendorOrderNumber(string $vendorOrderNumber): self
|
||||
{
|
||||
$this->vendorOrderNumber = $vendorOrderNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSellingParty(SellingParty $sellingParty): self
|
||||
{
|
||||
$this->sellingParty = $sellingParty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setWarehouse(Warehouse $warehouse): self
|
||||
{
|
||||
$this->warehouse = $warehouse;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasRejectedItems(): bool
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if ($item->isRejected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getStatusCodeOfFirstRejectedItem(): string
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if ($item->isRejected()) {
|
||||
return $item->getStatusCode();
|
||||
}
|
||||
}
|
||||
throw new \RuntimeException('No rejected item found');
|
||||
}
|
||||
|
||||
protected function generateStatus(): array
|
||||
{
|
||||
$statusCode = AcknowledgementItem::CODE_ACCEPTED;
|
||||
if ($this->hasRejectedItems()) {
|
||||
$statusCode = $this->getStatusCodeOfFirstRejectedItem();
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => $statusCode,
|
||||
'description' => AcknowledgementItem::AVAILABLE_CODES[$statusCode],
|
||||
];
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
if (!$this->warehouse) {
|
||||
throw MissingInformationException::property('warehouse');
|
||||
}
|
||||
if ($this->warehouse->hasNoAddress()) {
|
||||
throw MissingInformationException::property('warehouse address');
|
||||
}
|
||||
if (!$this->vendorOrderNumber) {
|
||||
throw MissingInformationException::property('vendorOrderNumber');
|
||||
}
|
||||
|
||||
// Map AcknowledgementItems to array
|
||||
$items = array_map(
|
||||
function (AcknowledgementItem $item) {
|
||||
return $item->toArray();
|
||||
},
|
||||
$this->items
|
||||
);
|
||||
|
||||
$data = [
|
||||
'purchaseOrderNumber' => $this->purchaseOrderNumber,
|
||||
'vendorOrderNumber' => $this->vendorOrderNumber,
|
||||
'acknowledgementDate' => (new DateTime())->format(DateTime::ATOM),
|
||||
'acknowledgementStatus' => $this->generateStatus(),
|
||||
'sellingParty' => $this->sellingParty->toArray(),
|
||||
'shipFromParty' => $this->warehouse->toArray(),
|
||||
'itemAcknowledgements' => $items,
|
||||
];
|
||||
|
||||
// In the PurchaseOrderAcknowledgement endpoint the key
|
||||
// is named taxInfo instead of taxRegistrationDetails
|
||||
$data['sellingParty']['taxInfo'] = $data['sellingParty']['taxRegistrationDetails'];
|
||||
$data['shipFromParty']['taxInfo'] = $data['sellingParty']['taxInfo'];
|
||||
unset($data['sellingParty']['taxRegistrationDetails']);
|
||||
unset($data['sellingParty']['taxInfo']['taxRegistrationAddress']);
|
||||
unset($data['shipFromParty']['taxInfo']['taxRegistrationAddress']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class PurchaseOrderItem
|
||||
{
|
||||
/** @var string */
|
||||
private $itemSequenceNumber;
|
||||
/** @var string */
|
||||
private $buyerProductIdentifier;
|
||||
/** @var string */
|
||||
private $vendorProductIdentifier;
|
||||
/** @var string */
|
||||
private $title;
|
||||
/** @var Quantity */
|
||||
private $quantity;
|
||||
/** @var Price */
|
||||
private $price;
|
||||
/** @var float */
|
||||
private $taxRate;
|
||||
|
||||
public function __construct(
|
||||
string $itemSequenceNumber,
|
||||
string $buyerProductIdentifier,
|
||||
string $vendorProductIdentifier,
|
||||
string $title,
|
||||
Quantity $quantity,
|
||||
Price $price,
|
||||
float $taxRate
|
||||
) {
|
||||
$this->itemSequenceNumber = $itemSequenceNumber;
|
||||
$this->buyerProductIdentifier = $buyerProductIdentifier;
|
||||
$this->vendorProductIdentifier = $vendorProductIdentifier;
|
||||
$this->title = $title;
|
||||
$this->quantity = $quantity;
|
||||
$this->price = $price;
|
||||
$this->taxRate = $taxRate;
|
||||
}
|
||||
|
||||
public function getItemSequenceNumber(): string
|
||||
{
|
||||
return $this->itemSequenceNumber;
|
||||
}
|
||||
|
||||
public function getBuyerProductIdentifier(): string
|
||||
{
|
||||
return $this->buyerProductIdentifier;
|
||||
}
|
||||
|
||||
public function getVendorProductIdentifier(): string
|
||||
{
|
||||
return $this->vendorProductIdentifier;
|
||||
}
|
||||
|
||||
public function setVendorProductIdentifier(string $vendorProductIdentifier): void
|
||||
{
|
||||
$this->vendorProductIdentifier = $vendorProductIdentifier;
|
||||
}
|
||||
|
||||
public function getQuantity(): Quantity
|
||||
{
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function getPrice(): Price
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getTaxRate(): float
|
||||
{
|
||||
return $this->taxRate;
|
||||
}
|
||||
|
||||
public function reject(string $code): AcknowledgementItem
|
||||
{
|
||||
return new AcknowledgementItem($this, $code);
|
||||
}
|
||||
|
||||
public function accept(): AcknowledgementItem
|
||||
{
|
||||
return new AcknowledgementItem($this, AcknowledgementItem::CODE_ACCEPTED);
|
||||
}
|
||||
|
||||
public static function fromPurchaseOrderResponse(array $data): self
|
||||
{
|
||||
return new static(
|
||||
$data['itemSequenceNumber'],
|
||||
$data['buyerProductIdentifier'],
|
||||
$data['vendorProductIdentifier'],
|
||||
$data['title'],
|
||||
Quantity::fromArray($data['orderedQuantity']),
|
||||
new Price($data['netPrice']['currencyCode'], $data['netPrice']['amount']),
|
||||
(float)$data['taxDetails']['taxLineItem'][0]['taxRate']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class Quantity
|
||||
{
|
||||
/** @var int */
|
||||
private $amount;
|
||||
/** @var string */
|
||||
private $unitOfMeasure;
|
||||
/** @var int */
|
||||
private $unitSize;
|
||||
|
||||
public function __construct(int $amount, string $unitOfMeasure = 'Each', ?int $unitSize = 1)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
$this->unitOfMeasure = $unitOfMeasure;
|
||||
$this->unitSize = $unitSize;
|
||||
}
|
||||
|
||||
public function getAmount(): int
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function getUnitOfMeasure(): string
|
||||
{
|
||||
return $this->unitOfMeasure;
|
||||
}
|
||||
|
||||
public function getUnitSize(): int
|
||||
{
|
||||
return $this->unitSize;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'amount' => $this->amount,
|
||||
'unitOfMeasure' => $this->unitOfMeasure,
|
||||
'unitSize' => $this->unitSize,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data)
|
||||
{
|
||||
return new static(
|
||||
$data['amount'],
|
||||
$data['unitOfMeasure'],
|
||||
isset($data['unitSize']) ? $data['unitSize'] : null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class SellingParty
|
||||
{
|
||||
/** @var string */
|
||||
private $partyId;
|
||||
|
||||
/** @var Address */
|
||||
private $address;
|
||||
|
||||
/** @var TaxRegistrationDetails */
|
||||
private $taxRegistrationDetails;
|
||||
|
||||
public function __construct(string $partyId)
|
||||
{
|
||||
$this->partyId = $partyId;
|
||||
}
|
||||
|
||||
public function getPartyId(): string
|
||||
{
|
||||
return $this->partyId;
|
||||
}
|
||||
|
||||
public function getAddress(): Address
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress(Address $address): self
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxRegistrationDetails(): TaxRegistrationDetails
|
||||
{
|
||||
return $this->taxRegistrationDetails;
|
||||
}
|
||||
|
||||
public function setTaxRegistrationDetails(TaxRegistrationDetails $taxRegistrationDetails): self
|
||||
{
|
||||
$this->taxRegistrationDetails = $taxRegistrationDetails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'partyId' => $this->partyId,
|
||||
'address' => $this->address->toArray(),
|
||||
'taxRegistrationDetails' => $this->taxRegistrationDetails->toArray()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class ShipmentConfirmation extends ShippingLabelRequest
|
||||
{
|
||||
public function toArray()
|
||||
{
|
||||
$data = [
|
||||
'purchaseOrderNumber' => $this->purchaseOrderNumber,
|
||||
'sellingParty' => $this->sellingParty->toArray(),
|
||||
'shipmentDetails' => [
|
||||
'shippedDate' => (new \DateTime('now'))->format(DATE_ATOM),
|
||||
'shipmentStatus' => 'SHIPPED'
|
||||
],
|
||||
'shipFromParty' => $this->warehouse->toArray(),
|
||||
'items' => $this->extractItemsFromContainers(),
|
||||
'containers' => array_map(
|
||||
function (Container $container) {
|
||||
return $container->toArray();
|
||||
},
|
||||
$this->containers
|
||||
),
|
||||
];
|
||||
|
||||
$data['sellingParty']['taxRegistrationDetails'] = [$data['sellingParty']['taxRegistrationDetails']];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all items form the single containers because they are
|
||||
* needed in the top level of the shipment confirmation as well.
|
||||
*/
|
||||
private function extractItemsFromContainers(): array
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->containers as $container) {
|
||||
$items = array_merge($items, $container->getItems());
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function (array $item) {
|
||||
// In the items of the shipmentConfirmation the key is
|
||||
// called shippedQuantity instead of packedQuantity
|
||||
$item['shippedQuantity'] = $item['packedQuantity'];
|
||||
unset($item['packedQuantity']);
|
||||
|
||||
return $item;
|
||||
},
|
||||
$items
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class ShipmentDetails
|
||||
{
|
||||
/** @var bool */
|
||||
private $isPriorityShipment;
|
||||
/** @var bool */
|
||||
private $isPslipRequired;
|
||||
/** @var string */
|
||||
private $shipMethod;
|
||||
/** @var DateTime */
|
||||
private $promisedDeliveryDate;
|
||||
/** @var DateTime */
|
||||
private $requiredShipDate;
|
||||
/** @var string */
|
||||
private $messageToCustomer;
|
||||
|
||||
public function __construct(
|
||||
bool $isPriorityShipment,
|
||||
bool $isPslipRequired,
|
||||
string $shipMethod,
|
||||
DateTime $requiredShipDate,
|
||||
DateTime $promisedDeliveryDate,
|
||||
string $messageToCustomer
|
||||
) {
|
||||
$this->isPriorityShipment = $isPriorityShipment;
|
||||
$this->isPslipRequired = $isPslipRequired;
|
||||
$this->shipMethod = $shipMethod;
|
||||
$this->requiredShipDate = $requiredShipDate;
|
||||
$this->promisedDeliveryDate = $promisedDeliveryDate;
|
||||
$this->messageToCustomer = $messageToCustomer;
|
||||
}
|
||||
|
||||
public function isPriorityShipment(): bool
|
||||
{
|
||||
return $this->isPriorityShipment;
|
||||
}
|
||||
|
||||
public function isPslipRequired(): bool
|
||||
{
|
||||
return $this->isPslipRequired;
|
||||
}
|
||||
|
||||
|
||||
public function getShipMethod(): string
|
||||
{
|
||||
return $this->shipMethod;
|
||||
}
|
||||
|
||||
public function getRequiredShipDate(): DateTime
|
||||
{
|
||||
return $this->requiredShipDate;
|
||||
}
|
||||
|
||||
public function getPromisedDeliveryDate(): DateTime
|
||||
{
|
||||
return $this->promisedDeliveryDate;
|
||||
}
|
||||
|
||||
public function getMessageToCustomer(): string
|
||||
{
|
||||
return $this->messageToCustomer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class ShippingLabel implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
private $purchaseOrderNumber;
|
||||
/** @var string */
|
||||
private $encodedLabelData;
|
||||
/** @var string */
|
||||
private $labelFormat;
|
||||
/** @var string */
|
||||
private $trackingNumber;
|
||||
|
||||
public function __construct(string $purchaseOrderNumber, string $encodedLabelData, string $labelFormat = 'PNG')
|
||||
{
|
||||
//@TODO check if we need to implement multiple labels per order
|
||||
$this->purchaseOrderNumber = $purchaseOrderNumber;
|
||||
$this->encodedLabelData = $encodedLabelData;
|
||||
$this->labelFormat = $labelFormat;
|
||||
}
|
||||
|
||||
public function getTrackingNumber(): string
|
||||
{
|
||||
return $this->trackingNumber;
|
||||
}
|
||||
|
||||
public function setTrackingNumber(string $trackingNumber): self
|
||||
{
|
||||
$this->trackingNumber = $trackingNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasTrackingNumber(): bool
|
||||
{
|
||||
return $this->trackingNumber !== null;
|
||||
}
|
||||
|
||||
public function getEncodedLabelData(): string
|
||||
{
|
||||
return $this->encodedLabelData;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'purchase_order_number' => $this->purchaseOrderNumber,
|
||||
'tracking_number' => $this->trackingNumber,
|
||||
'encodedLabelData' => $this->encodedLabelData
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class ShippingLabelRequest
|
||||
{
|
||||
/** @var string */
|
||||
protected $purchaseOrderNumber;
|
||||
/** @var SellingParty */
|
||||
protected $sellingParty;
|
||||
/** @var Warehouse */
|
||||
protected $warehouse;
|
||||
/** @var array|Container[] */
|
||||
protected $containers = [];
|
||||
|
||||
public function __construct(string $purchaseOrderNumber, SellingParty $sellingParty, Warehouse $warehouse)
|
||||
{
|
||||
$this->purchaseOrderNumber = $purchaseOrderNumber;
|
||||
$this->sellingParty = $sellingParty;
|
||||
$this->warehouse = $warehouse;
|
||||
}
|
||||
|
||||
public function addContainer(Container $container)
|
||||
{
|
||||
$this->containers[] = $container;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'purchaseOrderNumber' => $this->purchaseOrderNumber,
|
||||
'sellingParty' => [
|
||||
'partyId' => $this->sellingParty->getPartyId()
|
||||
],
|
||||
'shipFromParty' => [
|
||||
'partyId' => $this->warehouse->getWarehouseId()
|
||||
],
|
||||
'containers' => array_map(
|
||||
function (Container $container) {
|
||||
return $container->toArray();
|
||||
},
|
||||
$this->containers
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class TaxDetails
|
||||
{
|
||||
/** @var string */
|
||||
private $taxType;
|
||||
/** @var string */
|
||||
private $taxRate;
|
||||
/** @var Price */
|
||||
private $taxAmount;
|
||||
/** @var Price */
|
||||
private $taxableAmount;
|
||||
|
||||
public function __construct(string $taxType, string $taxRate, Price $taxAmount, Price $taxableAmount)
|
||||
{
|
||||
$this->taxType = $taxType;
|
||||
$this->taxRate = $taxRate;
|
||||
$this->taxAmount = $taxAmount;
|
||||
$this->taxableAmount = $taxableAmount;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'taxType' => $this->taxType,
|
||||
'taxRate' => $this->taxRate,
|
||||
'taxAmount' => $this->taxAmount->toArray(),
|
||||
'taxableAmount' => $this->taxableAmount->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class TaxRegistrationDetails
|
||||
{
|
||||
/** @var string */
|
||||
private $taxRegistrationType;
|
||||
/** @var string */
|
||||
private $taxRegistrationNumber;
|
||||
/** @var Address */
|
||||
private $taxRegistrationAddress;
|
||||
|
||||
public function getTaxRegistrationType(): string
|
||||
{
|
||||
return $this->taxRegistrationType;
|
||||
}
|
||||
|
||||
public function setTaxRegistrationType(string $taxRegistrationType): self
|
||||
{
|
||||
$this->taxRegistrationType = $taxRegistrationType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxRegistrationNumber(): string
|
||||
{
|
||||
return $this->taxRegistrationNumber;
|
||||
}
|
||||
|
||||
public function setTaxRegistrationNumber(string $taxRegistrationNumber): self
|
||||
{
|
||||
$this->taxRegistrationNumber = $taxRegistrationNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxRegistrationAddress(): Address
|
||||
{
|
||||
return $this->taxRegistrationAddress;
|
||||
}
|
||||
|
||||
public function setTaxRegistrationAddress(Address $taxRegistrationAddress): self
|
||||
{
|
||||
$this->taxRegistrationAddress = $taxRegistrationAddress;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'taxRegistrationType' => $this->taxRegistrationType,
|
||||
'taxRegistrationNumber' => $this->taxRegistrationNumber,
|
||||
'taxRegistrationAddress' => $this->taxRegistrationAddress->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class Token
|
||||
{
|
||||
/** @var string */
|
||||
private $accessToken;
|
||||
|
||||
/** @var string */
|
||||
private $refreshToken;
|
||||
|
||||
/** @var DateTimeImmutable */
|
||||
private $expirationDate;
|
||||
|
||||
public function __construct(string $accessToken, string $refreshToken, int $expiresInSeconds = 3600)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
$this->refreshToken = $refreshToken;
|
||||
$this->expirationDate = new DateTimeImmutable( "+{$expiresInSeconds} seconds");
|
||||
}
|
||||
|
||||
public function getAccessToken(): string
|
||||
{
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
public function getRefreshToken(): string
|
||||
{
|
||||
return $this->refreshToken;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return new DateTime('now') > $this->expirationDate;
|
||||
}
|
||||
|
||||
public static function fromResponse(ResponseInterface $response): self
|
||||
{
|
||||
$tokenInformation = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
return new static($tokenInformation['access_token'], $tokenInformation['refresh_token'], $tokenInformation['expires_in']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class Transaction
|
||||
{
|
||||
const STATUS_FAILURE = 'Failure';
|
||||
const STATUS_PROCESSING = 'Processing';
|
||||
const STATUS_SUCCESS = 'Success';
|
||||
const STATUS_WAITING = 'Waiting';
|
||||
const STATUS_CLOSED = 'Closed';
|
||||
|
||||
/** @var int */
|
||||
private $id;
|
||||
/** @var string */
|
||||
private $externalId;
|
||||
/** @var string */
|
||||
private $subject;
|
||||
/** @var string */
|
||||
private $subject_id;
|
||||
/** @var string */
|
||||
private $status;
|
||||
/** @var array */
|
||||
private $errors;
|
||||
/** @var DateTime */
|
||||
private $created_at;
|
||||
/** @var DateTime */
|
||||
private $updated_at;
|
||||
|
||||
|
||||
public function __construct(string $subject = '')
|
||||
{
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
public function isWaiting(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_WAITING;
|
||||
}
|
||||
|
||||
public function isProcessing(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PROCESSING;
|
||||
}
|
||||
|
||||
public function hasFailed(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_FAILURE;
|
||||
}
|
||||
|
||||
public function hasSucceeded(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExternalId(): ?string
|
||||
{
|
||||
return $this->externalId;
|
||||
}
|
||||
|
||||
public function setExternalId(string $externalId): self
|
||||
{
|
||||
$this->externalId = $externalId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSubject(): string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
public function setSubject(string $subject): self
|
||||
{
|
||||
$this->subject = $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSubjectId(): ?string
|
||||
{
|
||||
return $this->subject_id;
|
||||
}
|
||||
|
||||
public function setSubjectId(string $subject_id): self
|
||||
{
|
||||
$this->subject_id = $subject_id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): self
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function setErrors(array $errors): self
|
||||
{
|
||||
$this->errors = $errors;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime
|
||||
{
|
||||
return $this->created_at;
|
||||
}
|
||||
|
||||
public function setCreatedAt(DateTime $created_at): self
|
||||
{
|
||||
$this->created_at = $created_at;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): DateTime
|
||||
{
|
||||
return $this->updated_at;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(DateTime $updated_at): self
|
||||
{
|
||||
$this->updated_at = $updated_at;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Data;
|
||||
|
||||
class Warehouse
|
||||
{
|
||||
/** @var string */
|
||||
private $warehouseId;
|
||||
/** @var Address */
|
||||
private $address;
|
||||
|
||||
public function __construct(string $warehouseId)
|
||||
{
|
||||
$this->warehouseId = $warehouseId;
|
||||
}
|
||||
|
||||
public function setAddress(Address $address): self
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasNoAddress(): bool
|
||||
{
|
||||
return $this->address === null;
|
||||
}
|
||||
|
||||
public function getWarehouseId(): string
|
||||
{
|
||||
return $this->warehouseId;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'partyId' => $this->warehouseId,
|
||||
'address' => $this->address->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
class ColumnNotFoundException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
class DuplicatePurchaseOrderException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
use Exception;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\AcknowledgementItem;
|
||||
|
||||
class InvalidAcknowledgementCodeException extends Exception
|
||||
{
|
||||
public static function invalidCode(string $code)
|
||||
{
|
||||
return new static(
|
||||
"Invalid acknowledgement code \"{$code}\". Hast to be one of: \n" . implode(
|
||||
"\n",
|
||||
AcknowledgementItem::AVAILABLE_CODES
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function missingCode(string $code)
|
||||
{
|
||||
return new static(
|
||||
'Acknowledgement is not accepted nor rejected. You have to call accept() or reject()'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class IssueTokenException extends RuntimeException
|
||||
{
|
||||
public static function fromResponse(?ResponseInterface $response = null): self
|
||||
{
|
||||
return new self($response ? 'exception with response info' : 'exception without response info');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class MissingInformationException extends Exception
|
||||
{
|
||||
public static function property(string $property)
|
||||
{
|
||||
return new static("\"{$property}\" is not set!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
class PurchaseOrderNumberNotFoundException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Exception;
|
||||
|
||||
class TransferException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Models;
|
||||
|
||||
use DateTime;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\PurchaseOrder;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\ShippingLabel;
|
||||
|
||||
class PurchaseOrderInformation
|
||||
{
|
||||
/** Step 1: Purchase order resulted in an entry in the database which was not processed yet */
|
||||
public const STATUS_UNPROCESSED = null;
|
||||
/** Step 2: Purchase order resulted in an order but was not acknowledged yet */
|
||||
public const STATUS_PROCESSING = 'processing';
|
||||
|
||||
/** Step 3: Acknowledgement was sent, but it was not yet cleared by Amazon */
|
||||
public const STATUS_ACKNOWLEDGEMENT_SENT = 'acknowledgement_sent';
|
||||
/** Step 3b: Purchase could not be processed automatically and requires user input */
|
||||
public const STATUS_WAITING_FOR_USER_INPUT = 'waiting_for_user_input';
|
||||
/** Step 3c: There was an error on the remote end preventing the acknowledgement from being processed*/
|
||||
public const STATUS_ACKNOWLEDGEMENT_FAILED = 'acknowledgement_failed';
|
||||
/** Step 4: Acknowledgement was accepted by Amazon */
|
||||
public const STATUS_ACKNOWLEDGEMENT_ACCEPTED = 'acknowledgement_accepted';
|
||||
/** Step 4b: Acknowledgement was rejected by amazon */
|
||||
public const STATUS_ACKNOWLEDGEMENT_REJECTED = 'acknowledgement_rejected';
|
||||
|
||||
/** Step 5: Shipping label was requested, but not yet provided by Amazon */
|
||||
public const STATUS_SHIPPING_LABEL_REQUESTED = 'shipping_label_requested';
|
||||
/** Step 6: Shipping label request was successful and can be downloaded */
|
||||
public const STATUS_SHIPPING_LABEL_ACCEPTED = 'shipping_label_accepted';
|
||||
/** Step 6b: Shipping label request was rejected by amazon */
|
||||
public const STATUS_SHIPPING_LABEL_REJECTED = 'shipping_label_rejected';
|
||||
|
||||
/** Step 7: */
|
||||
public const STATUS_SHIPMENT_CONFIRMATION_SENT = 'shipment_confirmation_sent';
|
||||
|
||||
/** Rejected by user input */
|
||||
public const STATUS_REJECTED_BY_USER_INPUT = 'rejected_by_user_input';
|
||||
|
||||
/** @var string */
|
||||
private $externalId;
|
||||
/** @var string */
|
||||
private $raw;
|
||||
/** @var null|int */
|
||||
private $orderId;
|
||||
/** @var bool */
|
||||
private $acknowledged = false;
|
||||
/** @var null|string */
|
||||
private $acknowledgementTransactionId;
|
||||
/** @var bool */
|
||||
private $shippingLabelRequested = false;
|
||||
/** @var null|string */
|
||||
private $shippingLabelRequestTransactionId;
|
||||
/** @var null|string */
|
||||
private $shippingLabelData;
|
||||
/** @var DateTime|null */
|
||||
private $createdAt;
|
||||
/** @var Datetime|null */
|
||||
private $updatedAt;
|
||||
/** @var string */
|
||||
private $status;
|
||||
/** @var string */
|
||||
private $shipmentConfirmationTransactionId;
|
||||
|
||||
public function __construct(string $externalId)
|
||||
{
|
||||
$this->externalId = $externalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getShipmentConfirmationTransactionId(): ?string
|
||||
{
|
||||
return $this->shipmentConfirmationTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $shipmentConfirmationTransactionId
|
||||
*/
|
||||
public function setShipmentConfirmationTransactionId(string $shipmentConfirmationTransactionId): void
|
||||
{
|
||||
$this->shipmentConfirmationTransactionId = $shipmentConfirmationTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function wasShipmentConfirmationSent(): bool
|
||||
{
|
||||
return $this->shipmentConfirmationTransactionId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus(): ?string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
*/
|
||||
public function setStatus(string $status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function canFetchShippingLabels(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_SHIPPING_LABEL_ACCEPTED
|
||||
|| (empty($this->shippingLabelData) && $this->status === self::STATUS_SHIPMENT_CONFIRMATION_SENT);
|
||||
}
|
||||
|
||||
public function hasShippingLabel(): bool
|
||||
{
|
||||
return $this->shippingLabelData !== null;
|
||||
}
|
||||
|
||||
public function getPurchaseOrderNumber(): string
|
||||
{
|
||||
return $this->externalId;
|
||||
}
|
||||
|
||||
public function getRawJson(): ?string
|
||||
{
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
public function getOrderId(): ?int
|
||||
{
|
||||
return $this->orderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $orderId
|
||||
*/
|
||||
public function setOrderId(?int $orderId): void
|
||||
{
|
||||
$this->orderId = $orderId;
|
||||
}
|
||||
|
||||
public function isAcknowledged(): bool
|
||||
{
|
||||
return $this->acknowledged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $acknowledged
|
||||
*/
|
||||
public function setAcknowledged(bool $acknowledged): void
|
||||
{
|
||||
$this->acknowledged = $acknowledged;
|
||||
}
|
||||
|
||||
public function getAcknowledgementTransactionId(): ?string
|
||||
{
|
||||
return $this->acknowledgementTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $acknowledgementTransactionId
|
||||
*/
|
||||
public function setAcknowledgementTransactionId(?string $acknowledgementTransactionId): void
|
||||
{
|
||||
$this->acknowledgementTransactionId = $acknowledgementTransactionId;
|
||||
}
|
||||
|
||||
public function isShippingLabelRequested(): bool
|
||||
{
|
||||
return $this->shippingLabelRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $shippingLabelRequested
|
||||
*/
|
||||
public function setShippingLabelRequested(bool $shippingLabelRequested): void
|
||||
{
|
||||
$this->shippingLabelRequested = $shippingLabelRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExternalId(): string
|
||||
{
|
||||
return $this->externalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getShippingLabelRequestTransactionId(): ?string
|
||||
{
|
||||
return $this->shippingLabelRequestTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $shippingLabelRequestTransactionId
|
||||
*/
|
||||
public function setShippingLabelRequestTransactionId(?string $shippingLabelRequestTransactionId): void
|
||||
{
|
||||
$this->shippingLabelRequestTransactionId = $shippingLabelRequestTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShippingLabel[]
|
||||
*/
|
||||
public function getShippingLabels(): array
|
||||
{
|
||||
$decodedShippingLabels = json_decode($this->shippingLabelData, true);
|
||||
|
||||
return array_map(
|
||||
function (array $data) {
|
||||
$shippingLabel = new ShippingLabel(
|
||||
$data['purchase_order_number'],
|
||||
$data['encodedLabelData']
|
||||
);
|
||||
$shippingLabel->setTrackingNumber($data['tracking_number']);
|
||||
|
||||
return $shippingLabel;
|
||||
},
|
||||
$decodedShippingLabels
|
||||
);
|
||||
}
|
||||
|
||||
public function setShippingLabels(array $shippingLabels): void
|
||||
{
|
||||
$this->shippingLabelData = json_encode($shippingLabels);
|
||||
}
|
||||
|
||||
public function getShippingLabelData(): ?string
|
||||
{
|
||||
return $this->shippingLabelData;
|
||||
}
|
||||
|
||||
public function setShippingLabelData(string $shippingLabelData): void
|
||||
{
|
||||
$this->shippingLabelData = $shippingLabelData;
|
||||
}
|
||||
|
||||
public function setRaw(string $raw): void
|
||||
{
|
||||
$this->raw = $raw;
|
||||
}
|
||||
|
||||
public function getPurchaseOrder(): PurchaseOrder
|
||||
{
|
||||
return PurchaseOrder::fromPurchaseOrderResponse(json_decode($this->raw, true));
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTime
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $createdAt
|
||||
*/
|
||||
public function setCreatedAt(DateTime $createdAt): void
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?DateTime
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Datetime $updatedAt
|
||||
*/
|
||||
public function setUpdatedAt(Datetime $updatedAt): void
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF;
|
||||
|
||||
use Xentral\Components\Database\Database;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\PurchaseOrder;
|
||||
use Xentral\Modules\AmazonVendorDF\Models\PurchaseOrderInformation;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\ColumnNotFoundException;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\DuplicatePurchaseOrderException;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\PurchaseOrderNumberNotFoundException;
|
||||
|
||||
class PurchaseOrderInformationRepository
|
||||
{
|
||||
/** @var string */
|
||||
private $tableName = 'amazon_vendor_df_purchase_orders';
|
||||
|
||||
private $columns = [
|
||||
'status',
|
||||
'external_id',
|
||||
'raw',
|
||||
'order_id',
|
||||
'acknowledged',
|
||||
'acknowledgement_transaction_id',
|
||||
'shipping_label_requested',
|
||||
'shipping_label_request_transaction_id',
|
||||
'shipping_label_data',
|
||||
'shipment_confirmation_transaction_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'shopexport_id',
|
||||
];
|
||||
|
||||
/** @var Database */
|
||||
private $database;
|
||||
|
||||
public function __construct(Database $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
public function createPurchaseOrderInformation(PurchaseOrder $purchaseOrder, int $shopExportId):void
|
||||
{
|
||||
if ($this->doesPurchaseOrderInformationExist($purchaseOrder->getPurchaseOrderNumber())) {
|
||||
throw new DuplicatePurchaseOrderException();
|
||||
}
|
||||
|
||||
$statement = $this->database
|
||||
->insert()
|
||||
->into($this->tableName)
|
||||
->cols(['external_id', 'raw', 'shopexport_id'])
|
||||
->getStatement();
|
||||
|
||||
|
||||
|
||||
$values = [
|
||||
'external_id' => $purchaseOrder->getPurchaseOrderNumber(),
|
||||
'raw' => json_encode($purchaseOrder->getRawData()),
|
||||
'shopexport_id' => json_encode($shopExportId),
|
||||
];
|
||||
$this->database->perform($statement, $values);
|
||||
}
|
||||
|
||||
public function doesPurchaseOrderInformationExist(string $purchaseOrderNumber): bool
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols(['id'])
|
||||
->from($this->tableName)
|
||||
->where('external_id = :external_id')
|
||||
->bindValue('external_id', $purchaseOrderNumber)
|
||||
->limit(1);
|
||||
|
||||
$purchaseOrderId = $this->database->fetchRow($statement->getStatement(), $statement->getBindValues());
|
||||
|
||||
return !empty($purchaseOrderId['id']);
|
||||
}
|
||||
|
||||
public function countPurchaseOrdersWaitingForImport(int $shopExportId): int
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols(['id'])
|
||||
->from($this->tableName)
|
||||
->where('shopexport_id = :shopexport_id')
|
||||
->where('acknowledged = 0')
|
||||
->where('status IS NULL')
|
||||
->bindValue('shopexport_id', $shopExportId);
|
||||
|
||||
return count($this->database->fetchAssoc($statement->getStatement(), $statement->getBindValues()));
|
||||
}
|
||||
|
||||
public function getNextPurchaseOrderInformationToImport(int $shopExportId): PurchaseOrderInformation
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols($this->columns)
|
||||
->from($this->tableName)
|
||||
->where('shopexport_id = :shopexport_id')
|
||||
->where('acknowledged = 0')
|
||||
->where('status IS NULL') //TODO Konstante verwenden
|
||||
->bindValue('shopexport_id', $shopExportId)
|
||||
->orderBy(['created_at ASC'])
|
||||
->limit(1);
|
||||
|
||||
$data = $this->database->fetchRow($statement->getStatement(), $statement->getBindValues());
|
||||
|
||||
return $this->buildPurchaseOrderInformation($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $constraints
|
||||
*
|
||||
* @return PurchaseOrderInformation[]
|
||||
*/
|
||||
public function listPurchaseOrderInformation(array $constraints = []): array
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols($this->columns)
|
||||
->from($this->tableName);
|
||||
|
||||
foreach ($constraints as $constraint) {
|
||||
$column = $constraint[0];
|
||||
if(!in_array($column,$this->columns, false)){
|
||||
throw new ColumnNotFoundException("Column '{$column}' does not exist in table {$this->tableName}");
|
||||
}
|
||||
$value = $constraint[1];
|
||||
if ($value === null) {
|
||||
$statement->where("{$column} IS NULL");
|
||||
continue;
|
||||
}
|
||||
|
||||
$operator = empty($constraint[2]) ? '=' : $constraint[2];
|
||||
|
||||
if ($constraint[1] instanceof \DateTime) {
|
||||
$statement->where("{$column} {$operator} :{$column}");
|
||||
$statement->bindValue($column, $value->format('Y-m-d H:i:s'));
|
||||
} else {
|
||||
$statement->where("{$column} {$operator} :{$column}");
|
||||
$statement->bindValue($column, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function (array $data) {
|
||||
return $this->buildPurchaseOrderInformation($data);
|
||||
},
|
||||
$this->database->fetchAll($statement->getStatement(), $statement->getBindValues())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function savePurchaseOrderInformation(PurchaseOrderInformation $purchaseOrderInformation): PurchaseOrderInformation
|
||||
{
|
||||
if (empty($purchaseOrderInformation->getPurchaseOrderNumber())) {
|
||||
//TODO Throw
|
||||
}
|
||||
|
||||
$columnsToSave = [];
|
||||
$columnsToIgnore = ['raw', 'created_at', 'updated_at', 'shopexport_id'];
|
||||
foreach ($this->columns as $column){
|
||||
if(!in_array($column,$columnsToIgnore)){
|
||||
$columnsToSave[] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
$statement = $this->database
|
||||
->update()
|
||||
->cols($columnsToSave)
|
||||
->table($this->tableName)
|
||||
->where('external_id = :external_id')
|
||||
->bindValue('external_id', $purchaseOrderInformation->getPurchaseOrderNumber())
|
||||
->bindValues(
|
||||
[
|
||||
'order_id' => $purchaseOrderInformation->getOrderId(),
|
||||
'status' => $purchaseOrderInformation->getStatus(),
|
||||
'acknowledged' => $purchaseOrderInformation->isAcknowledged(),
|
||||
'acknowledgement_transaction_id' => $purchaseOrderInformation->getAcknowledgementTransactionId(),
|
||||
'shipping_label_requested' => $purchaseOrderInformation->isShippingLabelRequested(),
|
||||
'shipping_label_request_transaction_id' => $purchaseOrderInformation->getShippingLabelRequestTransactionId(),
|
||||
'shipping_label_data' => $purchaseOrderInformation->getShippingLabelData(),
|
||||
'shipment_confirmation_transaction_id' => $purchaseOrderInformation->getShipmentConfirmationTransactionId(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->database->perform($statement, $statement->getBindValues());
|
||||
|
||||
$purchaseOrderInformation->setUpdatedAt(new \DateTime());
|
||||
|
||||
return $purchaseOrderInformation;
|
||||
}
|
||||
|
||||
|
||||
public function getPurchaseOrderInformationByPurchaseOrderNumber(string $purchaseOrderNumber): PurchaseOrderInformation
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols($this->columns)
|
||||
->from($this->tableName)
|
||||
->where('external_id = :external_id')
|
||||
->bindValue('external_id', $purchaseOrderNumber)
|
||||
->limit(1);
|
||||
|
||||
$purchaseOrderData = $this->database->fetchRow($statement->getStatement(), $statement->getBindValues());
|
||||
|
||||
return $this->buildPurchaseOrderInformation($purchaseOrderData);
|
||||
}
|
||||
|
||||
public function getPurchaseOrderInformationByOrderId(int $orderId): PurchaseOrderInformation
|
||||
{
|
||||
$statement = $this->database
|
||||
->select()
|
||||
->cols($this->columns)
|
||||
->from($this->tableName)
|
||||
->where('order_id = :order_id')
|
||||
->bindValue('order_id', $orderId)
|
||||
->limit(1);
|
||||
|
||||
$purchaseOrderData = $this->database->fetchRow($statement->getStatement(), $statement->getBindValues());
|
||||
|
||||
return $this->buildPurchaseOrderInformation($purchaseOrderData);
|
||||
}
|
||||
|
||||
private function buildPurchaseOrderInformation(array $data): PurchaseOrderInformation
|
||||
{
|
||||
if(empty($data['external_id'])){
|
||||
throw new PurchaseOrderNumberNotFoundException();
|
||||
}
|
||||
|
||||
$purchaseOrderStatus = new PurchaseOrderInformation($data['external_id']);
|
||||
if (isset($data['raw'])) {
|
||||
$purchaseOrderStatus->setRaw($data['raw']);
|
||||
}
|
||||
if (isset($data['order_id'])) {
|
||||
$purchaseOrderStatus->setOrderId($data['order_id']);
|
||||
}
|
||||
if (isset($data['acknowledged'])) {
|
||||
$purchaseOrderStatus->setAcknowledged((bool)$data['acknowledged']);
|
||||
}
|
||||
if (isset($data['acknowledgement_transaction_id'])) {
|
||||
$purchaseOrderStatus->setAcknowledgementTransactionId($data['acknowledgement_transaction_id']);
|
||||
}
|
||||
if (isset($data['shipping_label_requested'])) {
|
||||
$purchaseOrderStatus->setShippingLabelRequested((bool)$data['shipping_label_requested']);
|
||||
}
|
||||
if (isset($data['shipping_label_request_transaction_id'])) {
|
||||
$purchaseOrderStatus->setShippingLabelRequestTransactionId($data['shipping_label_request_transaction_id']);
|
||||
}
|
||||
if (isset($data['shipping_label_data'])) {
|
||||
$purchaseOrderStatus->setShippingLabelData($data['shipping_label_data']);
|
||||
}
|
||||
if (isset($data['shipment_confirmation_transaction_id'])) {
|
||||
$purchaseOrderStatus->setAcknowledged($data['shipment_confirmation_transaction_id']);
|
||||
}
|
||||
if (isset($data['created_at'])) {
|
||||
$purchaseOrderStatus->setCreatedAt(\DateTime::createFromFormat('Y-m-d H:i:s', $data['created_at']));
|
||||
}
|
||||
if (isset($data['updated_at'])) {
|
||||
$purchaseOrderStatus->setUpdatedAt(\DateTime::createFromFormat('Y-m-d H:i:s', $data['updated_at']));
|
||||
}
|
||||
if (isset($data['status'])) {
|
||||
$purchaseOrderStatus->setStatus($data['status']);
|
||||
}
|
||||
|
||||
return $purchaseOrderStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\InventoryItem;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Transaction;
|
||||
|
||||
class InventoryService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function updateInventory(string $warehouseId, array $items, string $sellingPartyId, bool $isFullUpdate = false)
|
||||
{
|
||||
//First map all InventoryItems to an array
|
||||
$items = array_map(
|
||||
function (InventoryItem $item) {
|
||||
$data = $item->toArray();
|
||||
unset($data['availableQuantity']['unitSize']);
|
||||
|
||||
return $data;
|
||||
},
|
||||
$items
|
||||
);
|
||||
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
"/vendor/directFulfillment/inventory/v1/warehouses/{$warehouseId}/items",
|
||||
[
|
||||
'json' => [
|
||||
'inventory' => [
|
||||
'sellingParty' => [
|
||||
'partyId' => $sellingPartyId
|
||||
],
|
||||
'items' => $items,
|
||||
'isFullUpdate' => $isFullUpdate
|
||||
]
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return (new Transaction('inventory_update'))->setExternalId($payload['transactionId']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Invoice;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Transaction;
|
||||
|
||||
class InvoiceService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function submitInvoice(Invoice $invoice): Transaction
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
'/vendor/directFulfillment/payments/v1/invoices',
|
||||
['json' => [$invoice->toArray()]]
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return (new Transaction('invoice'))->setExternalId($payload['transactionId']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use DateTime;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\TransferException as GuzzleTransferException;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\PurchaseOrder;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\PurchaseOrderAcknowledgement;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Transaction;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\TransferException;
|
||||
|
||||
class PurchaseOrderService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime|null $createdAfter
|
||||
* @param DateTime|null $createdBefore
|
||||
* @param int|null $limit
|
||||
*
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function getPurchaseOrderNumbers(
|
||||
?DateTime $createdAfter = null,
|
||||
?DateTime $createdBefore = null,
|
||||
?int $limit = null
|
||||
): array {
|
||||
$orders = $this->getOrders($createdAfter, $createdBefore, $limit);
|
||||
|
||||
return array_map(
|
||||
function (array $order) {
|
||||
return $order['purchaseOrderNumber'];
|
||||
},
|
||||
$orders
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime|null $createdAfter
|
||||
* @param DateTime|null $createdBefore
|
||||
* @param int|null $limit
|
||||
*
|
||||
* @return array|PurchaseOrder[]
|
||||
*/
|
||||
public function getPurchaseOrders(
|
||||
?DateTime $createdAfter = null,
|
||||
?DateTime $createdBefore = null,
|
||||
?int $limit = null
|
||||
): array {
|
||||
$orders = $this->getOrders($createdAfter, $createdBefore, $limit);
|
||||
|
||||
return array_map(
|
||||
function (array $order) {
|
||||
return PurchaseOrder::fromPurchaseOrderResponse($order);
|
||||
},
|
||||
$orders
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime|null $createdAfter
|
||||
* @param DateTime|null $createdBefore
|
||||
* @param int|null $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOrders(
|
||||
?DateTime $createdAfter = null,
|
||||
?DateTime $createdBefore = null,
|
||||
?int $limit = null
|
||||
): array {
|
||||
$finished = false;
|
||||
$nextToken = null;
|
||||
$orders = [];
|
||||
if ($limit !== null) {
|
||||
$response = $this->sendGetOrdersRequest($createdAfter, $createdBefore, null, $limit);
|
||||
$orders = array_merge($orders, $response['payload']['orders']);
|
||||
} else {
|
||||
while (!$finished) {
|
||||
$response = $this->sendGetOrdersRequest($createdAfter, $createdBefore, $nextToken);
|
||||
if ($response['payload']['pagination']['nextToken']) {
|
||||
$nextToken = $response['payload']['pagination']['nextToken'];
|
||||
} else {
|
||||
$finished = true;
|
||||
}
|
||||
$orders = array_merge($orders, $response['payload']['orders']);
|
||||
}
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
public function getOrder(string $purchaseOrderNumber): PurchaseOrder
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'GET',
|
||||
"/vendor/directFulfillment/orders/v1/purchaseOrders/{$purchaseOrderNumber}"
|
||||
);
|
||||
|
||||
$payload = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
return PurchaseOrder::fromPurchaseOrderResponse($payload);
|
||||
}
|
||||
|
||||
public function submitAcknowledgement(PurchaseOrderAcknowledgement $acknowledgement): Transaction
|
||||
{
|
||||
try {
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
'/vendor/directFulfillment/orders/v1/acknowledgements',
|
||||
[
|
||||
'json' => [
|
||||
'orderAcknowledgements' => [$acknowledgement->toArray()],
|
||||
],
|
||||
]
|
||||
);
|
||||
}catch (GuzzleTransferException $exception){
|
||||
throw new TransferException('Error while submitting acknowledgement',0, $exception);
|
||||
}
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return (new Transaction('purchase_order_acknowledgement'))->setExternalId($payload['transactionId']);
|
||||
}
|
||||
|
||||
protected function formatDate(DateTime $date)
|
||||
{
|
||||
return $date->format(DateTime::ISO8601);
|
||||
}
|
||||
|
||||
protected function sendGetOrdersRequest(
|
||||
?DateTime $createdAfter,
|
||||
?DateTime $createdBefore,
|
||||
?string $nextToken = null,
|
||||
?int $limit = null
|
||||
): array {
|
||||
$response = $this->client->request(
|
||||
'GET',
|
||||
'/vendor/directFulfillment/orders/v1/purchaseOrders',
|
||||
[
|
||||
'query' => array_merge(
|
||||
[
|
||||
'createdAfter' => $this->formatDate($createdAfter ?? new DateTime('-7 days')),
|
||||
'createdBefore' => $this->formatDate($createdBefore ?? new DateTime()),
|
||||
'limit' => $limit ?: 100,
|
||||
'includeDetails' => true,
|
||||
'sortOrder' => 'DESC',
|
||||
],
|
||||
$nextToken !== null ? ['nextToken' => $nextToken] : []
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
return json_decode($response->getBody()->getContents(), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\ShipmentConfirmation;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\ShippingLabel;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\ShippingLabelRequest;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Transaction;
|
||||
|
||||
class ShippingService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $purchaseOrderNumber
|
||||
*
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @return array|ShippingLabel[]
|
||||
*/
|
||||
public function getShippingLabels(string $purchaseOrderNumber): array
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'GET',
|
||||
"/vendor/directFulfillment/shipping/v1/shippingLabels/{$purchaseOrderNumber}"
|
||||
);
|
||||
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return array_map(
|
||||
function (array $data) use ($payload) {
|
||||
$label = new ShippingLabel($payload['purchaseOrderNumber'], $data['content'], $payload['labelFormat']);
|
||||
if (isset($data['trackingNumber']) && $data['trackingNumber'] !== '') {
|
||||
$label->setTrackingNumber($data['trackingNumber']);
|
||||
}
|
||||
|
||||
return $label;
|
||||
},
|
||||
$payload['labelData']
|
||||
);
|
||||
}
|
||||
|
||||
public function submitShippingLabelRequest(ShippingLabelRequest $shippingLabelRequest): Transaction
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
'/vendor/directFulfillment/shipping/v1/shippingLabels',
|
||||
[
|
||||
'json' => [
|
||||
'shippingLabelRequests' => [$shippingLabelRequest->toArray()],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return (new Transaction('shipping_label_request'))->setExternalId($payload['transactionId']);
|
||||
}
|
||||
|
||||
public function submitShipmentConfirmation(ShipmentConfirmation $confirmation)
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
'/vendor/directFulfillment/shipping/v1/shipmentConfirmations',
|
||||
[
|
||||
'json' => [
|
||||
'shipmentConfirmations' => [
|
||||
$confirmation->toArray(),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload'];
|
||||
|
||||
return (new Transaction('shipment_confirmation'))->setExternalId($payload['transactionId']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Token;
|
||||
use Xentral\Modules\AmazonVendorDF\Exception\IssueTokenException;
|
||||
|
||||
class TokenService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function requestToken(string $refreshToken, string $clientId, string $clientSecret)
|
||||
{
|
||||
try {
|
||||
$response = $this->client->request(
|
||||
'POST',
|
||||
'https://api.amazon.com/auth/o2/token',
|
||||
[
|
||||
'form_params' => [
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $clientId,
|
||||
'client_secret' => $clientSecret,
|
||||
],
|
||||
]
|
||||
);
|
||||
}catch (BadResponseException $badResponseException){
|
||||
throw IssueTokenException::fromResponse($badResponseException->getResponse());
|
||||
}
|
||||
|
||||
return Token::fromResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF\Service;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Transaction;
|
||||
|
||||
class TransactionService
|
||||
{
|
||||
/** @var ClientInterface */
|
||||
private $client;
|
||||
|
||||
public function __construct(ClientInterface $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
public function updateTransactionStatus(Transaction $transaction): Transaction
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'GET',
|
||||
"/vendor/directFulfillment/transactions/v1/transactions/{$transaction->getExternalId()}"
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload']['transactionStatus'];
|
||||
|
||||
$transaction->setStatus($payload['status']);
|
||||
if($transaction->hasFailed()){
|
||||
$transaction->setErrors($payload['errors']);
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function getTransactionByTransactionId(string $transactionId): Transaction
|
||||
{
|
||||
$response = $this->client->request(
|
||||
'GET',
|
||||
"/vendor/directFulfillment/transactions/v1/transactions/{$transactionId}"
|
||||
);
|
||||
|
||||
// The response data is wrapped in a `payload` key
|
||||
$payload = json_decode($response->getBody()->getContents(), true)['payload']['transactionStatus'];
|
||||
|
||||
$transaction = new Transaction();
|
||||
$transaction->setStatus($payload['status']);
|
||||
if ($transaction->hasFailed()) {
|
||||
$transaction->setErrors($payload['errors']);
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Modules\AmazonVendorDF;
|
||||
|
||||
use Aws\Credentials\Credentials;
|
||||
use Aws\Signature\SignatureV4;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Xentral\Modules\AmazonVendorDF\Data\Token;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\InventoryService;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\InvoiceService;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\PurchaseOrderService;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\ShippingService;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\TokenService;
|
||||
use Xentral\Modules\AmazonVendorDF\Service\TransactionService;
|
||||
|
||||
class ServiceFactory
|
||||
{
|
||||
const API_BASE_URL = 'https://sellingpartnerapi-eu.amazon.com';
|
||||
|
||||
/** @var TokenService */
|
||||
private $tokenService;
|
||||
/** @var Token */
|
||||
private $token;
|
||||
/** @var string */
|
||||
private $refreshToken;
|
||||
/** @var string */
|
||||
private $clientId;
|
||||
/** @var string */
|
||||
private $clientSecret;
|
||||
/** @var SignatureV4 */
|
||||
private $signature;
|
||||
/** @var Credentials */
|
||||
private $credentials;
|
||||
/** @var ClientInterface */
|
||||
private $authenticatedClient;
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
string $refreshToken,
|
||||
string $clientId,
|
||||
string $clientSecret,
|
||||
string $awsIamKey,
|
||||
string $awsIamSecret,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->tokenService = new TokenService(new Client());
|
||||
$this->refreshToken = $refreshToken;
|
||||
$this->clientId = $clientId;
|
||||
$this->clientSecret = $clientSecret;
|
||||
$this->signature = new SignatureV4('execute-api', 'eu-west-1');
|
||||
$this->credentials = new Credentials($awsIamKey, $awsIamSecret);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getShippingService(): ShippingService
|
||||
{
|
||||
return new ShippingService($this->getAuthenticatedClient());
|
||||
}
|
||||
|
||||
public function getInvoiceService(): InvoiceService
|
||||
{
|
||||
return new InvoiceService($this->getAuthenticatedClient());
|
||||
}
|
||||
|
||||
public function getInventoryService(): InventoryService
|
||||
{
|
||||
return new InventoryService($this->getAuthenticatedClient());
|
||||
}
|
||||
|
||||
public function getPurchaseOrderService(): PurchaseOrderService
|
||||
{
|
||||
return new PurchaseOrderService($this->getAuthenticatedClient());
|
||||
}
|
||||
|
||||
public function getTransactionService(): TransactionService
|
||||
{
|
||||
return new TransactionService($this->getAuthenticatedClient());
|
||||
}
|
||||
|
||||
private function getAuthenticatedClient(): ClientInterface
|
||||
{
|
||||
if (!$this->authenticatedClient || $this->getToken()->isExpired()) {
|
||||
$stack = HandlerStack::create();
|
||||
$stack->push($this->getSignatureMiddleware());
|
||||
$stack->push($this->getLoggingMiddleWare());
|
||||
|
||||
$this->authenticatedClient = new Client(
|
||||
[
|
||||
'handler' => $stack,
|
||||
'base_uri' => self::API_BASE_URL,
|
||||
'headers' => [
|
||||
'x-amz-access-token' => $this->getToken()->getAccessToken(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->authenticatedClient;
|
||||
}
|
||||
|
||||
private function getSignatureMiddleware()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return function (RequestInterface $request, array $options) use ($handler) {
|
||||
return $handler($this->signature->signRequest($request, $this->credentials), $options);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private function getLoggingMiddleWare()
|
||||
{
|
||||
return function (callable $handler) {
|
||||
return function (RequestInterface $request, array $options) use ($handler) {
|
||||
$promise = $handler($request, $options);
|
||||
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($request) {
|
||||
$this->logRequestAndResponse($request, $response, LogLevel::DEBUG);
|
||||
|
||||
return $response;
|
||||
},
|
||||
function (ResponseInterface $response) use ($request) {
|
||||
$this->logRequestAndResponse($request, $response, LogLevel::ERROR);
|
||||
|
||||
return $response;
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private function logRequestAndResponse(RequestInterface $request, ResponseInterface $response, string $level)
|
||||
{
|
||||
$request->getBody()->rewind();
|
||||
|
||||
$this->logger->log(
|
||||
$level,
|
||||
'Amazon Vendor DF API request',
|
||||
[
|
||||
'request' => [
|
||||
'uri' => (string)$request->getUri(),
|
||||
'method' => $request->getMethod(),
|
||||
'body' => json_decode($request->getBody()->getContents(), true),
|
||||
],
|
||||
'response' => [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'headers' => $response->getHeaders(),
|
||||
'body' => json_decode($response->getBody()->getContents(), true),
|
||||
],
|
||||
]
|
||||
|
||||
);
|
||||
|
||||
$response->getBody()->rewind();
|
||||
}
|
||||
|
||||
private function getToken()
|
||||
{
|
||||
if (!$this->token) {
|
||||
$this->token = $this->tokenService->requestToken($this->refreshToken, $this->clientId, $this->clientSecret);
|
||||
}
|
||||
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user