diff --git a/.gitignore b/.gitignore index 9f1a478..61f5f59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /xsd/ /vendor/ /nbproject/ +/composer.lock diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..5f138a3 --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +C:37:"PHPUnit\Runner\DefaultTestResultCache":4394:{a:2:{s:7:"defects";a:40:{s:40:"Test\mServer\ClientTest::testprocessInit";i:4;s:38:"Test\mServer\ClientTest::testlogBanner";i:4;s:34:"Test\mServer\ClientTest::testsetUp";i:4;s:36:"Test\mServer\ClientTest::testsetAuth";i:3;s:40:"Test\mServer\ClientTest::testsetInstance";i:4;s:43:"Test\mServer\ClientTest::testsetApplication";i:4;s:46:"Test\mServer\ClientTest::testsetCheckDuplicity";i:4;s:37:"Test\mServer\ClientTest::testcurlInit";i:4;s:42:"Test\mServer\ClientTest::testsetPostFields";i:4;s:42:"Test\mServer\ClientTest::testdoCurlRequest";i:4;s:43:"Test\mServer\ClientTest::testperformRequest";i:4;s:44:"Test\mServer\ClientTest::testprocessResponse";i:4;s:38:"Test\mServer\ClientTest::testgetStatus";i:4;s:37:"Test\mServer\ClientTest::testtakeData";i:4;s:35:"Test\mServer\ClientTest::testcreate";i:4;s:40:"Test\mServer\ClientTest::testaddToPohoda";i:2;s:43:"Test\mServer\ClientTest::testupdateInPohoda";i:2;s:39:"Test\mServer\ClientTest::testfilterToMe";i:4;s:49:"Test\mServer\ClientTest::testgetColumnsFromPohoda";i:2;s:37:"Test\mServer\ClientTest::test__wakeup";i:2;s:39:"Test\mServer\AdressbookTest::testcreate";i:4;s:36:"Test\mServer\InvoiceTest::testcreate";i:4;s:37:"Test\mServer\InvoiceTest::testaddItem";i:4;s:35:"Test\mServer\InvoiceTest::testextId";i:4;s:40:"Test\mServer\ResponseTest::testuseCaller";i:4;s:50:"Test\mServer\ResponseTest::testprocessResponsePack";i:4;s:54:"Test\mServer\ResponseTest::testprocessResponsePackItem";i:4;s:53:"Test\mServer\ResponseTest::testprocessProducedDetails";i:4;s:51:"Test\mServer\ResponseTest::testprocessImportDetails";i:4;s:50:"Test\mServer\ResponseTest::testprocessResponseData";i:4;s:43:"Test\mServer\ResponseTest::testtypesToArray";i:4;s:42:"Test\mServer\ResponseTest::testtypeToArray";i:4;s:38:"Test\mServer\ResponseTest::testgetNote";i:4;s:35:"Test\mServer\ResponseTest::testisOk";i:4;s:39:"Test\mServer\ResponseTest::testgetState";i:4;s:44:"Test\mServer\ResponseTest::testgetAgendaData";i:4;s:45:"Test\mServer\ResponseTest::testprepareElement";i:4;s:44:"Test\mServer\ResponseTest::testanyXmlToArray";i:4;s:36:"Test\mServer\ResponseTest::testparse";i:4;s:41:"Test\mServer\ResponseTest::testxmlToArray";i:4;}s:5:"times";a:40:{s:40:"Test\mServer\ClientTest::testprocessInit";d:0.004;s:38:"Test\mServer\ClientTest::testlogBanner";d:0.001;s:34:"Test\mServer\ClientTest::testsetUp";d:0.001;s:36:"Test\mServer\ClientTest::testsetAuth";d:0.001;s:40:"Test\mServer\ClientTest::testsetInstance";d:0.001;s:43:"Test\mServer\ClientTest::testsetApplication";d:0;s:46:"Test\mServer\ClientTest::testsetCheckDuplicity";d:0.001;s:37:"Test\mServer\ClientTest::testcurlInit";d:0;s:42:"Test\mServer\ClientTest::testsetPostFields";d:0;s:42:"Test\mServer\ClientTest::testdoCurlRequest";d:0;s:43:"Test\mServer\ClientTest::testperformRequest";d:0.001;s:44:"Test\mServer\ClientTest::testprocessResponse";d:0.001;s:38:"Test\mServer\ClientTest::testgetStatus";d:26.706;s:37:"Test\mServer\ClientTest::testtakeData";d:37.494;s:35:"Test\mServer\ClientTest::testcreate";d:0.003;s:40:"Test\mServer\ClientTest::testaddToPohoda";d:1.216;s:43:"Test\mServer\ClientTest::testupdateInPohoda";d:1.017;s:39:"Test\mServer\ClientTest::testfilterToMe";d:0.002;s:49:"Test\mServer\ClientTest::testgetColumnsFromPohoda";d:0.975;s:37:"Test\mServer\ClientTest::test__wakeup";d:0.003;s:39:"Test\mServer\AdressbookTest::testcreate";d:0.002;s:36:"Test\mServer\InvoiceTest::testcreate";d:0.001;s:37:"Test\mServer\InvoiceTest::testaddItem";d:0;s:35:"Test\mServer\InvoiceTest::testextId";d:0;s:40:"Test\mServer\ResponseTest::testuseCaller";d:0;s:50:"Test\mServer\ResponseTest::testprocessResponsePack";d:0;s:54:"Test\mServer\ResponseTest::testprocessResponsePackItem";d:0;s:53:"Test\mServer\ResponseTest::testprocessProducedDetails";d:0;s:51:"Test\mServer\ResponseTest::testprocessImportDetails";d:0;s:50:"Test\mServer\ResponseTest::testprocessResponseData";d:0.001;s:43:"Test\mServer\ResponseTest::testtypesToArray";d:0;s:42:"Test\mServer\ResponseTest::testtypeToArray";d:0;s:38:"Test\mServer\ResponseTest::testgetNote";d:0;s:35:"Test\mServer\ResponseTest::testisOk";d:0;s:39:"Test\mServer\ResponseTest::testgetState";d:0;s:44:"Test\mServer\ResponseTest::testgetAgendaData";d:0;s:45:"Test\mServer\ResponseTest::testprepareElement";d:0;s:44:"Test\mServer\ResponseTest::testanyXmlToArray";d:0;s:36:"Test\mServer\ResponseTest::testparse";d:0;s:41:"Test\mServer\ResponseTest::testxmlToArray";d:0;}}} \ No newline at end of file diff --git a/README.md b/README.md index ac11a97..0633699 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ +![Project Logo](phpmserver.svg?raw=true) + # PHPmServer -client library for Stormware's Pohoda mServer +client library for Stormware's [mPohoda mServer](https://www.stormware.cz/pohoda/xml/mserver/) + +Features: + + * Create/Update/Delete AddressBook record + * Create/Update/Delete Invoice record + +Installation: +------------- Install using composer: @@ -7,8 +17,33 @@ Install using composer: composer require vitexsoftware/pohoda-connector ``` +Or clone https://github.com/VitexSoftware/PHP-Pohoda-Connector.git + +Configuration +------------- + +Classess check at startup for this constants or environment variables: + + * POHODA_ICO - company indentificator + * POHODA_URL - mServer endpoint + * POHODA_USERNAME - Pohoda user's login + * POHODA_PASSWORD - Pohoda user's password + * POHODA_TIMEOUT - Curl timeout + * POHODA_COMPRESS - compress is disabled by default + * POHODA_DEBUG - debug mode is disabled by default + + +Usage +----- + +See usage examples in [tests](tests) directory + + * [Check Connection](tests/check-connection.php) + * [Add Addressbook record](tests/insert-address.php) + * [Create Invoice](tests/insert-invoice.php) + * [Addressbook reading](tests/read-address.php) + * [Address update](tests/update-address.php) -See also: https://github.com/Spoje-NET/PohodaSQL , https://github.com/Spoje-NET/php-abraflexi +See also my other libraries: https://github.com/Spoje-NET/PohodaSQL , https://github.com/Spoje-NET/php-abraflexi -The Response class was taken from https://github.com/jakubdusek/PohodaResponseParser By @jakubdusek diff --git a/composer.json b/composer.json index 219d9f4..3755dec 100644 --- a/composer.json +++ b/composer.json @@ -12,11 +12,9 @@ "require": { "ext-curl": "*", "ext-iconv": "*", - "ext-simplexml": "*", "riesenia/pohoda": ">=1.5 || dev-master", "vitexsoftware/ease-core": ">=1.9", - "lightools/xml": "v2.0.0", - "sabre/xml": "2.2.3" + "lightools/xml": "v2.0.0" }, "autoload": { "psr-4": { diff --git a/phpmserver.svg b/phpmserver.svg new file mode 100644 index 0000000..50a1bbc --- /dev/null +++ b/phpmserver.svg @@ -0,0 +1,203 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?php + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..acaa88a --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,22 @@ + + + + + ./tests + + + + + + src/mServer + + + + + diff --git a/src/mServer/Adressbook.php b/src/mServer/Adressbook.php index e2e673e..975f1d1 100644 --- a/src/mServer/Adressbook.php +++ b/src/mServer/Adressbook.php @@ -1,9 +1,10 @@ + * @copyright (C) 2020 Vitex Software */ namespace mServer; @@ -13,7 +14,7 @@ * * @author vitex */ -class Adressbook extends Client /* implements \Sabre\Xml\XmlDeserializable */ { +class Adressbook extends Client { /** * Current Object's agenda @@ -27,45 +28,6 @@ class Adressbook extends Client /* implements \Sabre\Xml\XmlDeserializable */ { */ public $requestXml = null; - /* - public function getElementMap($extra = []) { - return - array_merge( - [ - '{http://www.stormware.cz/schema/version_2/list_addBook.xsd}listAddressBook' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\repeatingElements($reader, '{http://www.stormware.cz/schema/version_2/list_addBook.xsd}addressbook'); - }, - '{http://www.stormware.cz/schema/version_2/list_addBook.xsd}addressbook' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\keyValue($reader, 'http://www.stormware.cz/schema/version_2/list_addBook.xsd'); - }, - '{http://www.stormware.cz/schema/version_2/addressbook.xsd}addressbookHeader' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\keyValue($reader, 'http://www.stormware.cz/schema/version_2/addressbook.xsd'); - }, - '{http://www.stormware.cz/schema/version_2/type.xsd}address' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\keyValue($reader, 'http://www.stormware.cz/schema/version_2/type.xsd'); - }, - '{http://www.stormware.cz/schema/version_2/type.xsd}shipToAddress' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\keyValue($reader, 'http://www.stormware.cz/schema/version_2/type.xsd'); - }, - ], parent::getElementMap($extra)); - } - - static function xmlDeserialize(\Sabre\Xml\Reader $reader) { - $addressBook = new self(); - // Borrowing a parser from the KeyValue class. - $keyValue = \Sabre\Xml\Element\KeyValue::xmlDeserialize($reader); - - if (isset($keyValue['{http://example.org/books}title'])) { - $book->title = $keyValue['{http://example.org/books}title']; - } - if (isset($keyValue['{http://example.org/books}author'])) { - $book->author = $keyValue['{http://example.org/books}author']; - } - - return $addressBook; - } - */ - /** * Create Agenda document using given data * @@ -76,35 +38,9 @@ public function create($data) { } /** - * Convert decimal coordineates to GPS field value - * - * @param string $latitude - * @param string $longitude - * - * @return string 50° 04' 58.9781" N 000° 00' 00.0000" E - */ - public static function LatLongToGPS(string $latitude, string $longitude) { - return self::DECtoDMS($longitude) . 'N ' . self::DECtoDMS($longitude) . 'E '; - } - - /** - * Converts decimal longitude / latitude to DMS ( Degrees / minutes / seconds ) - * This is the piece of code which may appear to be inefficient, but to avoid - * issues with floating point math we extract the integer part and the float - * part by using a string function. - * - * @param string $dec float as string ex. 50.111199 - * - * @return string 50°06'40.3" + * AddressBook records name column + * @var string */ - public static function DECtoDMS(string $dec) { - $vars = explode(".", $dec); - $deg = $vars[0]; - $tempma = "0." . $vars[1]; - $tempma = $tempma * 3600; - $min = floor($tempma / 60); - $sec = round($tempma - ($min * 60), 4); - return $deg . '° ' . $min . "' " . $sec . '" '; - } + public $nameColumn = 'address:company'; } diff --git a/src/mServer/Client.php b/src/mServer/Client.php index 6de9ee5..61a32d3 100644 --- a/src/mServer/Client.php +++ b/src/mServer/Client.php @@ -1,7 +1,7 @@ * @copyright (C) 2020 Vitex Software @@ -28,7 +28,7 @@ class Client extends \Ease\Sand { * * @var string */ - public static $libVersion = '0.1'; + public static $libVersion = '0.3'; /** * Curl Handle. @@ -148,14 +148,21 @@ class Client extends \Ease\Sand { public $requestXml = null; /** - * + * Where to find current record name. + * @var string column name or path in array address:company + */ + public $nameColumn = null; + + /** + * mServer client class * - * @param mixed $init default record id or initial data. See processInit() + * @param mixed $init default record id or initial data. See processInit() * @param array $options Connection settings and other options override */ public function __construct($init = null, $options = []) { $this->setUp($options); $this->curlInit(); + Pohoda::$encoding = 'UTF-8'; $this->pohoda = new Pohoda($this->ico); $this->pohoda->setApplicationName(Functions::cfg('APP_NAME') ? Functions::cfg('APP_NAME') : 'PHPmPohoda'); $tmpFile = sys_get_temp_dir() . '/' . Functions::randomString() . '.xml'; @@ -165,6 +172,11 @@ public function __construct($init = null, $options = []) { } } + /** + * Process and use initial value + * + * @param mixed $init + */ public function processInit($init) { if (is_integer($init)) { $this->loadFromPohoda($init); @@ -223,7 +235,7 @@ public function setUp($options = []) { $this->setCheckDuplicity($options['duplicity']); } - $this->setupProperty($options, 'debug'); + $this->setupProperty($options, 'debug', 'POHODA_DEBUG'); } /** @@ -236,14 +248,29 @@ public function setAuth() { return strlen($this->user) && strlen($this->password); } + /** + * Set Instance http header + * + * @param string $instance + */ public function setInstance(string $instance) { $this->defaultHttpHeaders['STW-Instance'] = $instance; } + /** + * Set Application http header + * + * @param string $application + */ public function setApplication(string $application) { $this->defaultHttpHeaders['STW-Application'] = $application; } + /** + * Set "Check Duplicity" http header enabler + * + * @param bool $flag + */ public function setCheckDuplicity(bool $flag) { if ($flag) { $this->defaultHttpHeaders['STW-Check-Duplicity'] = 'true'; @@ -341,6 +368,13 @@ public function performRequest($urlSuffix = null, $method = 'POST') { return $this->processResponse($this->doCurlRequest($url, $method)); } + /** + * Response processing handler + * + * @param int $httpCode + * + * @return boolean + */ public function processResponse($httpCode) { switch ($httpCode) { @@ -396,7 +430,7 @@ public function processResponse($httpCode) { } foreach ($this->response->messages as $type => $messages) { foreach ($messages as $message) { - $this->addStatusMessage($message['state'].' '.$message['errno'].': '.$message['note'].(array_key_exists('XPath', $message)?' ('.$message['XPath'].')':''), $type); + $this->addStatusMessage($message['state'] . ' ' . $message['errno'] . ': ' . $message['note'] . (array_key_exists('XPath', $message) ? ' (' . $message['XPath'] . ')' : ''), $type); } } } @@ -412,19 +446,9 @@ public function processResponse($httpCode) { * @return boolean */ public function getStatus() { - $this->performRequest('/status'); - return ($this->lastResponseCode == 200) && $this->lastCurlResponse == 'Response from POHODA mServer'; - } - - public function getElementMap($extra = []) { - return array_merge( - [ - '{http://www.stormware.cz/schema/version_2/response.xsd}responsePackItem' => function(\Sabre\Xml\Reader $reader) { - return \Sabre\Xml\Deserializer\keyValue($reader, 'http://www.stormware.cz/schema/version_2/response.xsd'); - } - ] - , $extra - ); + $this->responseStats = []; + $this->errors = []; + return ($this->doCurlRequest($this->url . '/status','POST') == 200) && $this->lastCurlResponse == 'Response from POHODA mServer'; } /** @@ -434,7 +458,7 @@ public function getElementMap($extra = []) { * @param boolean $reset replace current content */ public function takeData($data, $reset = false) { - parent::takeData(\Ease\Functions::recursiveIconv('UTF-8', 'Windows-1250', $data), $reset); + parent::takeData($data, $reset); $this->create($this->getData()); } @@ -454,20 +478,69 @@ public function create($data) { * * @return int */ - public function insertToPohoda($data = []) { - if (count($data)) { + public function addToPohoda($data = []) { + if (!empty($data)) { + $this->takeData($data); + } + if ($this->requestXml) { + if (method_exists($this->requestXml, 'addActionType')) { + $this->requestXml->addActionType('add'); // "add", "add/update", "update", "delete" + } + $this->pohoda->addItem(2, $this->requestXml); + } + + $this->setPostFields($this->pohoda->close()); + return $this->performRequest('/xml'); + } + + /** + * Insert prepared record to Pohoda + * + * @param array $data extra data + * + * @return int + */ + public function updateInPohoda($data = [], $filter = null) { + if (!empty($data)) { $this->takeData($data); } if ($this->requestXml) { if (method_exists($this->requestXml, 'addActionType')) { - $this->requestXml->addActionType('add/update'); // "add", "add/update", "update", "delete" + $this->requestXml->addActionType('update', empty($filter) ? $this->filterToMe() : $filter); // "add", "add/update", "update", "delete" } $this->pohoda->addItem(2, $this->requestXml); } + $this->setPostFields($this->pohoda->close()); return $this->performRequest('/xml'); } + /** + * Filter to select only "current" record + * + * @return array + */ + public function filterToMe() { + if ($this->nameColumn) { + if (strstr($this->nameColumn, ':')) { + $data = $this->getData(); + foreach (explode(':', $this->nameColumn) as $key) { + if (array_key_exists($data, $data)) { + $data = $data[$key]; + } else { + throw new \Exception('Data Path ' . $this->nameColumn . 'does not exist'); + } + } + $filter = [$key => $data]; + } else { + $filter = [$this->nameColumn => $this->getDataValue($this->nameColumn)]; + } + } else { + $filter = [$this->getKeyColumn() => $this->getMyKey()]; + } + return $filter; + } + /** * * @param type $columns diff --git a/src/mServer/Invoice.php b/src/mServer/Invoice.php index e4e08e3..ad11dec 100644 --- a/src/mServer/Invoice.php +++ b/src/mServer/Invoice.php @@ -1,9 +1,10 @@ + * @copyright (C) 2020 Vitex Software */ namespace mServer; @@ -13,7 +14,7 @@ * * @author vitex */ -class Invoice extends Client /* implements \Sabre\Xml\XmlDeserializable */ { +class Invoice extends Client { /** * Current Object's agenda @@ -82,6 +83,37 @@ public function create($data) { } } + /** + * Add Item into invoice + * + * @param array $itemRecord Item properties + * + * @return \Riesenia\Pohoda\Invoice Invoice with item added + */ + public function addItem($itemRecord) { + + if (array_key_exists('stockItemIDS', $itemRecord)) { //TODO: Finalize + $stockItemIDS = $itemRecord['stockItemIDS']; + unset($itemRecord['stockItemIDS']); + $itemRecord['stockItem']['stockItem']['ids'] = $stockItemIDS; + } + + +// $itemRecord['homeCurrency']; +// $itemRecord['foreignCurrency']; +// $itemRecord['stockItem']; + + + return $this->requestXml->addItem($itemRecord); + } + + /** + * + * @param type $ids + * @param type $exSystemName + * @param type $exSystemText + * @return \SimpleXMLElement + */ public static function extId($ids, $exSystemName = '', $exSystemText = '') { $node = new \SimpleXMLElement('', 0, false, \Riesenia\Pohoda::$namespaces['typ']); diff --git a/src/mServer/Response.php b/src/mServer/Response.php index 15dc82e..26483d1 100644 --- a/src/mServer/Response.php +++ b/src/mServer/Response.php @@ -1,5 +1,12 @@ + * @copyright (C) 2020 Vitex Software + */ + namespace mServer; /** @@ -22,11 +29,6 @@ class Response extends \Ease\Sand { */ const STATE_OK = 'ok'; - /** - * @var Description\Sabre\Xml\Service XML Reader - */ - private $service; - /** * Parsed Result * @var array @@ -51,13 +53,24 @@ class Response extends \Ease\Sand { */ public $producedDetails; + /** + * Operation status + * @var string + */ + private $state; + + /** + * Operation status note + * @var string + */ + private $note; + /** * Create a new Response Instance * * @param string $xml path to file */ public function __construct(Client $caller) { - $this->service = new \Sabre\Xml\Service(); $this->useCaller($caller); } @@ -68,38 +81,82 @@ public function __construct(Client $caller) { public function useCaller(Client $caller) { $this->caller = $caller; if ($caller->lastCurlResponse) { - $this->service->elementMap = $caller->getElementMap(); - $this->parsed = $this->service->parse($caller->lastCurlResponse); - $addressbookResponse = $this->parsed[0]['value']['{http://www.stormware.cz/schema/version_2/addressbook.xsd}addressbookResponse']; - - foreach ($addressbookResponse as $responseData) { - switch (self::stripNsUri($responseData['name'])) { - case 'producedDetails' : - $this->producedDetails = self::typesToArray($responseData['value']); - if (array_key_exists('extId', $this->producedDetails)) { - $this->producedDetails['extId'] = self::typesToArray($this->producedDetails['extId']); - } - break; - case 'importDetails': - foreach ($addressbookResponse[0]['value'] as $response) { - $importDetails = self::typesToArray($response['value']); - $this->messages[$importDetails['state']][] = $importDetails; - } - - if (count($this->messages['error'])) { - $this->parsed[0]['attributes']['state'] = 'error'; - } else if (count($this->messages['warning'])) { - $this->parsed[0]['attributes']['state'] = 'warning'; - } - break; - default: - $this->addStatusMessage(_('Unknown response section') . ': ' . $responseData['name'], 'debug'); - break; - } + $parsed = $this->parse($this->caller->lastCurlResponse, []); + $this->processResponsePack($parsed['responsePack']); + } else { + $this->state = 'error'; + $this->note = $caller->lastCurlError; + } + } + + public function processResponsePack($responsePackData) { + if (array_key_exists('rsp:responsePackItem', $responsePackData)) { + $this->processResponsePackItem($responsePackData['rsp:responsePackItem']); + } else { + $this->state = $responsePackData['@state']; + $this->note = $responsePackData['@note']; + } + } + + public function processResponsePackItem($responsePackItem) { + + foreach ($responsePackItem as $name => $responsePackSubitem) { + + switch ($name) { + case 'adb:addressbookResponse': + $this->processResponseData($responsePackSubitem); + break; + case '@state': + $this->state = $responsePackSubitem; + break; + case '': + break; + + default: +// throw new Exception('Unknown element to process: ' . $name); + break; } + } + } + + public function processProducedDetails($productDetails) { + $this->producedDetails = self::typeToArray($productDetails); + } + + public function processImportDetails($importDetails) { + if (array_key_exists('rdc:state', $importDetails['rdc:detail'])) { + $importDetail = self::typeToArray($importDetails['rdc:detail']); + $this->messages[$importDetail['state']][] = $importDetail; } else { - $this->parsed[0]['attributes']['state'] = 'error'; - $this->parsed[0]['attributes']['note'] = $caller->lastCurlError; + foreach (self::typesToArray($importDetails['rdc:detail']) as $importDetail) { + $this->messages[$importDetail['state']][] = $importDetail; + } + } + + if (count($this->messages['error'])) { + $this->state = 'error'; + } else if (count($this->messages['warning'])) { + $this->state = 'warning'; + } + } + + /** + * + * @param array $responseData + */ + function processResponseData($responseData) { + foreach ($responseData as $key => $value) { + switch ($key) { + case 'rdc:producedDetails' : + $this->processProducedDetails($value); + break; + case 'rdc:importDetails': + $this->processImportDetails($value); + break; + default: +// $this->addStatusMessage(_('Unknown response section') . ': ' . $responseData['name'], 'debug'); + break; + } } } @@ -111,8 +168,22 @@ public function useCaller(Client $caller) { */ public static function typesToArray(array $source) { $details = []; - foreach ($source as $detail) { - $details[self::stripNsUri($detail['name'])] = $detail['value']; + foreach ($source as $did => $detail) { + $details[$did] = self::typeToArray($detail); + } + return $details; + } + + /** + * + * @param array $type + * + * @return array + */ + public static function typeToArray(array $type) { + $details = []; + foreach ($type as $key => $value) { + $details[str_replace(['rdc:', 'typ:'], '', $key)] = is_array($value) ? (array_key_exists('$', $value) ? $value['$'] : self::typeToArray($value)) : $value; } return $details; } @@ -122,7 +193,7 @@ public static function typesToArray(array $source) { * @return string */ public function getNote() { - return isset($this->parsed[0]['attributes']['note']) ? $this->parsed[0]['attributes']['note'] : null; + return $this->note; } /** @@ -140,7 +211,7 @@ public function isOk() { * @return string */ public function getState() { - return (string) $this->parsed[0]['attributes']['state']; + return $this->state; } public function getAgendaData($agenda) { @@ -151,32 +222,21 @@ public function getAgendaData($agenda) { return $data; } - static public function stripNsUri($parsedName) { - return \Sabre\Xml\Service::parseClarkNotation($parsedName)[1]; - } - - /** - * Convert XML to array. - * - * @param string $xml - * - * @return array - */ - public static function xml2array($xml) { - $arr = []; - if (!empty($xml)) { - foreach ($xml->attributes() as $a) { - $arr['@' . $a->getName()] = strval($a); - } - foreach ($xml->children() as $r) { - if (count($r->children()) == 0) { - $arr[$r->getName()] = strval($r); + public static function prepareElement($elementData) { + $name = self::stripNsUri($elementData['name']); + $data = []; + foreach ($elementData['value'] as $subitems) { + foreach ($subitems as $subitem) { + if (is_array($subitem)) { + if (array_key_exists('name', $subitem)) { + $data = array_merge($data, self::prepareElement($subitem)); + } } else { - $arr[$r->getName()][] = self::xml2array($r); + $data[] = $subitem; } } } - return $arr; + return [$name => $data]; } /** @@ -190,4 +250,107 @@ public function anyXmlToArray($xml) { return self::xml2array(is_string($xml) ? simplexml_load_string($xml) : $xml); } + /** + * Convert XML to Array + * + * @param string $xml + * @param array $alwaysArrayElements + * + * @return array + */ + public static function parse($xml, array $alwaysArrayElements) { + $xmlNode = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); + $arrayData = self::xmlToArray($xmlNode, array( + 'alwaysArray' => $alwaysArrayElements, + 'autoText' => false, + )); + + return $arrayData; + } + + /** + * @param \SimpleXMLElement $xml + * @param array $options + * + * @return array + * + * @author Tamlyn Rhodes + * @link http://outlandish.com/blog/xml-to-json/ + */ + public static function xmlToArray(\SimpleXMLElement $xml, $options = array()) { + $defaults = array( + 'namespaceSeparator' => ':', //you may want this to be something other than a colon + 'attributePrefix' => '@', //to distinguish between attributes and nodes with the same name + 'alwaysArray' => array(), //array of xml tag names which should always become arrays + 'autoArray' => true, //only create arrays for tags which appear more than once + 'textContent' => '$', //key used for the text content of elements + 'autoText' => true, //skip textContent key if node has no attributes or child nodes + 'keySearch' => false, //optional search and replace on tag and attribute names + 'keyReplace' => false //replace values for above search values (as passed to str_replace()) + ); + $options = array_merge($defaults, $options); + $namespaces = $xml->getDocNamespaces(); + $namespaces[''] = null; //add base (empty) namespace + //get attributes from all namespaces + $attributesArray = array(); + foreach ($namespaces as $prefix => $namespace) { + foreach ($xml->attributes($namespace) as $attributeName => $attribute) { + //replace characters in attribute name + if ($options['keySearch']) + $attributeName = str_replace($options['keySearch'], $options['keyReplace'], $attributeName); + $attributeKey = $options['attributePrefix'] + . ($prefix ? $prefix . $options['namespaceSeparator'] : '') + . $attributeName; + $attributesArray[$attributeKey] = (string) $attribute; + } + } + + //get child nodes from all namespaces + $tagsArray = array(); + foreach ($namespaces as $prefix => $namespace) { + foreach ($xml->children($namespace) as $childXml) { + //recurse into child nodes + $childArray = self::xmlToArray($childXml, $options); + $childTagName = key($childArray); + $childProperties = current($childArray); + + //list($childTagName, $childProperties) = each($childArray); + //replace characters in tag name + if ($options['keySearch']) + $childTagName = str_replace($options['keySearch'], $options['keyReplace'], $childTagName); + //add namespace prefix, if any + if ($prefix) + $childTagName = $prefix . $options['namespaceSeparator'] . $childTagName; + + if (!isset($tagsArray[$childTagName])) { + //only entry with this key + //test if tags of this type should always be arrays, no matter the element count + $tagsArray[$childTagName] = in_array($childTagName, $options['alwaysArray']) || !$options['autoArray'] ? array($childProperties) : $childProperties; + } elseif ( + is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName]) === range(0, count($tagsArray[$childTagName]) - 1) + ) { + //key already exists and is integer indexed array + $tagsArray[$childTagName][] = $childProperties; + } else { + //key exists so convert to integer indexed array with previous value in position 0 + $tagsArray[$childTagName] = array($tagsArray[$childTagName], $childProperties); + } + } + } + + //get text content of node + $textContentArray = array(); + $plainText = trim((string) $xml); + if ($plainText !== '') + $textContentArray[$options['textContent']] = $plainText; + + //stick it all together + $propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || ($plainText === '') ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText; + + //return node as array + return array( + $xml->getName() => $propertiesArray + ); + } + } diff --git a/tests/.env b/tests/.env index 224b645..a2a2e99 100644 --- a/tests/.env +++ b/tests/.env @@ -3,6 +3,7 @@ EASE_LOGGER=console|syslog POHODA_URL=http://10.11.25.23:2323 POHODA_USERNAME=api POHODA_PASSWORD=api -POHODA_ICO=02745976 +POHODA_ICO=12345678 + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..159eb4f --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,12 @@ + + * @copyright 2015-2020 Spoje.Net + */ + +require_once file_exists('../vendor/autoload.php') ? '../vendor/autoload.php' : 'vendor/autoload.php'; + +\Ease\Shared::instanced()->loadConfig(__DIR__ . '/.env',true); diff --git a/tests/insert-address.php b/tests/insert-address.php index 6c89fbc..643de9a 100644 --- a/tests/insert-address.php +++ b/tests/insert-address.php @@ -11,8 +11,6 @@ require_once __DIR__ . '/../vendor/autoload.php'; - - $addressBookRecord = [ 'GPS' => '', // GPS souřadnice. 'ICQ' => '', // ICQ adresa. @@ -45,8 +43,6 @@ // 'exSystemName' => 'appslug', // 'exSystemText' => 'app name' // ], // - - 'shipToAddress' => [// Dodací adresa. // 'actionType' => '', //Typ práce s dodací adresou. Výchozí hodnota je přidání nového dodací adresy. // 'extId' => '', // @@ -82,12 +78,6 @@ 'web' => 'https://www.vitexsoftware.cz', //Adresa www stránek. ]; - - - - - $addresser = new Adressbook($addressBookRecord, \Ease\Shared::instanced()->loadConfig(__DIR__ . '/.env')); -$addresser->insertToPohoda(); - +$addresser->addToPohoda(); diff --git a/tests/insert-invoice.php b/tests/insert-invoice.php index 71ecc7d..65b3f9a 100644 --- a/tests/insert-invoice.php +++ b/tests/insert-invoice.php @@ -96,7 +96,7 @@ $invoicer->addItem($itemRecord); -$invoicer->insertToPohoda(); +$invoicer->addToPohoda(); $invoicer->getIDS(); diff --git a/tests/src/mServer/AdressbookTest.php b/tests/src/mServer/AdressbookTest.php new file mode 100644 index 0000000..3da81f5 --- /dev/null +++ b/tests/src/mServer/AdressbookTest.php @@ -0,0 +1,43 @@ +object = new Adressbook(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void { + + } + + /** + * @covers mServer\Adressbook::create + * @todo Implement testcreate(). + */ + public function testcreate() { + $this->assertEquals('', $this->object->create()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + +} diff --git a/tests/src/mServer/ClientTest.php b/tests/src/mServer/ClientTest.php new file mode 100644 index 0000000..458ee44 --- /dev/null +++ b/tests/src/mServer/ClientTest.php @@ -0,0 +1,209 @@ +object = new Client(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void { + + } + + /** + * @covers mServer\Client::processInit + * @todo Implement testprocessInit(). + */ + public function testprocessInit() { + $this->assertEquals('', $this->object->processInit()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::logBanner + */ + public function testlogBanner() { + $this->object->logBanner(); + } + + /** + * @covers mServer\Client::setUp + */ + public function testsetUp() { + $this->object->setUp(['url' => 'http://localhost:12345']); + $this->assertEquals('http://localhost:12345', $this->object->url); + } + + /** + * @covers mServer\Client::setAuth + */ + public function testsetAuth() { + $this->assertTrue($this->object->setAuth()); + } + + /** + * @covers mServer\Client::setInstance + * @todo Implement testsetInstance(). + */ + public function testsetInstance() { + $this->assertEquals('', $this->object->setInstance()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::setApplication + * @todo Implement testsetApplication(). + */ + public function testsetApplication() { + $this->assertEquals('', $this->object->setApplication()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::setCheckDuplicity + * @todo Implement testsetCheckDuplicity(). + */ + public function testsetCheckDuplicity() { + $this->assertEquals('', $this->object->setCheckDuplicity()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::curlInit + */ + public function testcurlInit() { + $this->assertTrue($this->object->curlInit()); + } + + /** + * @covers mServer\Client::setPostFields + */ + public function testsetPostFields() { + $this->object->setPostFields(['test']); + $this->assertEquals(['test'], $this->object->postFields,); + } + + /** + * @covers mServer\Client::doCurlRequest + * @todo Implement testdoCurlRequest(). + */ + public function testdoCurlRequest() { + $this->assertEquals('', $this->object->doCurlRequest()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::performRequest + * @todo Implement testperformRequest(). + */ + public function testperformRequest() { + $this->assertEquals('', $this->object->performRequest()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::processResponse + * @todo Implement testprocessResponse(). + */ + public function testprocessResponse() { + $this->assertEquals('', $this->object->processResponse()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::getStatus + */ + public function testgetStatus() { + $this->assertTrue($this->object->getStatus()); + } + + /** + * @covers mServer\Client::takeData + */ + public function testtakeData() { + $this->object->takeData(['test' => 'true'], true); + $this->assertEquals(['test' => 'true'], $this->object->getData()); + } + + /** + * @covers mServer\Client::create + * @todo Implement testcreate(). + */ + public function testcreate() { + $this->assertEquals('', $this->object->create()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::addToPohoda + * @todo Implement testaddToPohoda(). + */ + public function testaddToPohoda() { + $this->assertEquals('', $this->object->addToPohoda()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::updateInPohoda + * @todo Implement testupdateInPohoda(). + */ + public function testupdateInPohoda() { + $this->assertEquals('', $this->object->updateInPohoda()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::filterToMe + */ + public function testfilterToMe() { + $this->assertEquals('id', $this->object->filterToMe()); + } + + /** + * @covers mServer\Client::getColumnsFromPohoda + * @todo Implement testgetColumnsFromPohoda(). + */ + public function testgetColumnsFromPohoda() { + $this->assertEquals('', $this->object->getColumnsFromPohoda()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Client::__wakeup + */ + public function test__wakeup() { + $this->assertEquals('', $this->object->__wakeup()); + } + +} diff --git a/tests/src/mServer/InvoiceTest.php b/tests/src/mServer/InvoiceTest.php new file mode 100644 index 0000000..fd05ea8 --- /dev/null +++ b/tests/src/mServer/InvoiceTest.php @@ -0,0 +1,63 @@ +object = new Invoice(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void { + + } + + /** + * @covers mServer\Invoice::create + * @todo Implement testcreate(). + */ + public function testcreate() { + $this->assertEquals('', $this->object->create()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Invoice::addItem + * @todo Implement testaddItem(). + */ + public function testaddItem() { + $this->assertEquals('', $this->object->addItem()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Invoice::extId + * @todo Implement testextId(). + */ + public function testextId() { + $this->assertEquals('', $this->object->extId()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + +} diff --git a/tests/src/mServer/ResponseTest.php b/tests/src/mServer/ResponseTest.php new file mode 100644 index 0000000..bcbcc24 --- /dev/null +++ b/tests/src/mServer/ResponseTest.php @@ -0,0 +1,193 @@ +object = new Response(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void { + + } + + /** + * @covers mServer\Response::useCaller + * @todo Implement testuseCaller(). + */ + public function testuseCaller() { + $this->assertEquals('', $this->object->useCaller()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::processResponsePack + * @todo Implement testprocessResponsePack(). + */ + public function testprocessResponsePack() { + $this->assertEquals('', $this->object->processResponsePack()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::processResponsePackItem + * @todo Implement testprocessResponsePackItem(). + */ + public function testprocessResponsePackItem() { + $this->assertEquals('', $this->object->processResponsePackItem()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::processProducedDetails + * @todo Implement testprocessProducedDetails(). + */ + public function testprocessProducedDetails() { + $this->assertEquals('', $this->object->processProducedDetails()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::processImportDetails + * @todo Implement testprocessImportDetails(). + */ + public function testprocessImportDetails() { + $this->assertEquals('', $this->object->processImportDetails()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::processResponseData + * @todo Implement testprocessResponseData(). + */ + public function testprocessResponseData() { + $this->assertEquals('', $this->object->processResponseData()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::typesToArray + * @todo Implement testtypesToArray(). + */ + public function testtypesToArray() { + $this->assertEquals('', $this->object->typesToArray()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::typeToArray + * @todo Implement testtypeToArray(). + */ + public function testtypeToArray() { + $this->assertEquals('', $this->object->typeToArray()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::getNote + * @todo Implement testgetNote(). + */ + public function testgetNote() { + $this->assertEquals('', $this->object->getNote()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::isOk + * @todo Implement testisOk(). + */ + public function testisOk() { + $this->assertEquals('', $this->object->isOk()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::getState + * @todo Implement testgetState(). + */ + public function testgetState() { + $this->assertEquals('', $this->object->getState()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::getAgendaData + * @todo Implement testgetAgendaData(). + */ + public function testgetAgendaData() { + $this->assertEquals('', $this->object->getAgendaData()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::prepareElement + * @todo Implement testprepareElement(). + */ + public function testprepareElement() { + $this->assertEquals('', $this->object->prepareElement()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::anyXmlToArray + * @todo Implement testanyXmlToArray(). + */ + public function testanyXmlToArray() { + $this->assertEquals('', $this->object->anyXmlToArray()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::parse + * @todo Implement testparse(). + */ + public function testparse() { + $this->assertEquals('', $this->object->parse()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + + /** + * @covers mServer\Response::xmlToArray + * @todo Implement testxmlToArray(). + */ + public function testxmlToArray() { + $this->assertEquals('', $this->object->xmlToArray()); + // Remove the following lines when you implement this test. + $this->markTestIncomplete('This test has not been implemented yet.'); + } + +} diff --git a/tests/update-address.php b/tests/update-address.php new file mode 100644 index 0000000..5060169 --- /dev/null +++ b/tests/update-address.php @@ -0,0 +1,85 @@ + + * @copyright (C) 2020 Vitex Software + */ + +namespace mServer; + +require_once __DIR__ . '/../vendor/autoload.php'; + +$extID = 234; + +$addressBookRecord = [ + 'GPS' => '', // GPS souřadnice. + 'ICQ' => '', // ICQ adresa. + 'Skype' => '', // Skype adresa. + 'activity' => '', // Činnost. + 'agreement' => '', // Číslo obchodní smlouvy (nesmí být povoleno v Globálním nastavení - Číslování zákazníků). + 'centre' => '', // Středisko. + 'contract' => '', // Zakázka. + 'credit' => '', // Kredit, tolerovaná výše pohledávek odběratele. + 'email' => '', // Email. + 'fax' => '', // Fax. + 'identity' => [// Základní údaje +// 'id' => '', // + 'address' => [// Adresa. + 'company' => 'Vitex Software', // + 'division' => '', // + 'name' => 'Vítězslav Dvořák', // + 'city' => 'Prague', // + 'street' => 'Long', // + 'zip' => '15800', // + 'ico' => '69438676', // + 'dic' => 'CZ7808072811', // + 'VATPayerType' => '', //Typ plátce DPH: payer Plátce DPH., non-payer Neplátce DPH., "" Neuvedeno (výchozí hodnota) + 'icDph' => '', // + 'country' => '', // + ], // + 'addressLinkToAddress' => '', // +// 'extId' => [ +// 'ids' => 'EXT-001', +// 'exSystemName' => 'appslug', +// 'exSystemText' => 'app name' +// ], // + 'shipToAddress' => [// Dodací adresa. +// 'actionType' => '', //Typ práce s dodací adresou. Výchozí hodnota je přidání nového dodací adresy. +// 'extId' => '', // + 'company' => '', // + 'division' => '', // + 'name' => '', // + 'city' => '', // + 'street' => '', // + 'zip' => '', // + 'country' => '', // + 'defaultShipAddress' => '', //Výchozí dodací adresa. + ], + ], + 'intNote' => 'maybe duplicated', // Interní poznámka. + 'maturity' => '', // Splatno. Počet dnů splatnosti faktur. Při vložení adresy do faktury se nastaví datum splatnosti přičtením zde uvedeného počtu dnů k datu vystavení faktury. + 'message' => 'message for ', // Zpráva. + 'mobil' => '739 778 202', // Mobil. + 'note' => 'note', // Poznámka. + 'number' => '', // Číslo dodavatele/odběratele dle zvolené číselné řady (musí být povoleno v Globálním nastavení - Číslování zákazníků). + 'ost1' => '', // Ostatní. + 'ost2' => '', // Ostatní. Používá se také u kontaktní osoby. +// 'funkce' => '', //Název funkce. Používá se jen u kontaktní osoby. + 'p1' => false, // Klíč P1 / Dodavatel. + 'p2' => true, // Klíč P2 / Odběratel. + 'p3' => false, // Klíč P3. + 'p4' => false, // Klíč P4. + 'p5' => false, // Klíč P5. + 'p6' => false, // Klíč P6. +// 'paymentType' => 'cash', // Forma úhrady: draft, cash, postal, delivery, creditcard, advance, encashment, cheque, compensation + 'phone' => '', // Telefon. + 'priceIDS' => '', // Cenová hladina odběratele. + 'region' => '', // Název kraje. + 'web' => 'https://www.vitexsoftware.cz', //Adresa www stránek. +]; + +$addresser = new Adressbook($addressBookRecord, \Ease\Shared::instanced()->loadConfig(__DIR__ . '/.env')); +$addresser->updateInPohoda(null, ['extId' => ['exSystemName' => \Ease\Functions::cfg('APP_NAME'), 'ids' => strval($extID)]]); +