From ba91eb2f5cb1faebd9fe91a931cb810b2d3c61d7 Mon Sep 17 00:00:00 2001 From: Len van Essen Date: Fri, 3 Jan 2020 14:51:55 +0100 Subject: [PATCH 1/5] Add option to select an office in Twinfield --- readme.md | 8 ++++++ src/ApiConnectors/BaseApiConnector.php | 9 +++++++ src/ApiConnectors/OfficeApiConnector.php | 13 ++++++++++ src/Enums/Services.php | 6 +++++ src/Response/Response.php | 1 - src/Services/SelectOfficeService.php | 31 ++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/Services/SelectOfficeService.php diff --git a/readme.md b/readme.md index 64645bb8..28d1028f 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,14 @@ username and password authentication, the `\PhpTwinfield\Secure\WebservicesAuthe $connection = new Secure\WebservicesAuthentication("username", "password", "organization"); ``` +Some endpoints allow you to filter on the Office, but for instance the BrowseData endpoint doesn't. For this you need to switch to the correct office before making the request, you can do this after authentication like so: + +```php +$office = Office::fromCode("someOfficeCode"); +$officeApi = new \PhpTwinfield\ApiConnectors\OfficeApiConnector($connection); +$officeApi->selectCurrentOffice($office); +``` + In order to use OAuth2 to authenticate with Twinfield, one should use the `\PhpTwinfield\Secure\Provider\OAuthProvider` to retrieve an `\League\OAuth2\Client\Token\AccessToken` object, and extract the refresh token from this object. Furthermore, it is required to set up a default `\PhpTwinfield\Office`, that will be used during requests to Twinfield. **Please note:** when a different office is specified when sending a request through one of the `ApiConnectors`, this Office will override the default. Using this information, we can create an instance of the `\PhpTwinfield\Secure\OpenIdConnectAuthentication` class, as follows: diff --git a/src/ApiConnectors/BaseApiConnector.php b/src/ApiConnectors/BaseApiConnector.php index 6f75bc66..ff3bf6c2 100644 --- a/src/ApiConnectors/BaseApiConnector.php +++ b/src/ApiConnectors/BaseApiConnector.php @@ -8,6 +8,7 @@ use PhpTwinfield\Secure\AuthenticatedConnection; use PhpTwinfield\Services\FinderService; use PhpTwinfield\Services\ProcessXmlService; +use PhpTwinfield\Services\SelectOfficeService; use PhpTwinfield\Util; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; @@ -164,4 +165,12 @@ protected function getFinderService(): FinderService { return $this->connection->getAuthenticatedClient(Services::FINDER()); } + + /** + * @throws Exception + */ + protected function getSelectOfficeService(): SelectOfficeService + { + return $this->connection->getAuthenticatedClient(Services::SELECTOFFICE()); + } } diff --git a/src/ApiConnectors/OfficeApiConnector.php b/src/ApiConnectors/OfficeApiConnector.php index cf8bae4a..604ff786 100644 --- a/src/ApiConnectors/OfficeApiConnector.php +++ b/src/ApiConnectors/OfficeApiConnector.php @@ -87,4 +87,17 @@ public function listAll( return $offices; } + + /** + * Selects the current office + * @param Office $office + * @return bool + * @throws \PhpTwinfield\Exception + */ + public function selectCurrentOffice(Office $office) + { + $response = $this->getSelectOfficeService()->updateOffice($office); + + return $response; + } } diff --git a/src/Enums/Services.php b/src/Enums/Services.php index 7df22078..33ba6f88 100644 --- a/src/Enums/Services.php +++ b/src/Enums/Services.php @@ -4,6 +4,7 @@ use PhpTwinfield\Services\FinderService; use PhpTwinfield\Services\ProcessXmlService; +use PhpTwinfield\Services\SelectOfficeService; /** * All web services offered by Twinfield. @@ -28,4 +29,9 @@ class Services extends \MyCLabs\Enum\Enum * Twinfield Process XML web service methods. See below for an overview of the supported XML messages. */ protected const PROCESSXML = ProcessXmlService::class; + + /** + * The service that selects the current office in Twinfield + */ + protected const SELECTOFFICE = SelectOfficeService::class; } \ No newline at end of file diff --git a/src/Response/Response.php b/src/Response/Response.php index 5694646b..b3ef96e0 100644 --- a/src/Response/Response.php +++ b/src/Response/Response.php @@ -94,7 +94,6 @@ public function assertSuccessful(): void throw new Exception("Not all items were processed successfully by Twinfield: {$successful} success / {$failed} failed."); } - if ("1" !== $responseValue) { throw new Exception(implode(", ", array_merge($this->getErrorMessages(), $this->getWarningMessages()))); } diff --git a/src/Services/SelectOfficeService.php b/src/Services/SelectOfficeService.php new file mode 100644 index 00000000..15325e84 --- /dev/null +++ b/src/Services/SelectOfficeService.php @@ -0,0 +1,31 @@ +SelectCompany( + ['company' => $office->getCode()] + ); + + return self::CHANGE_OK === $result->SelectCompanyResult; + } +} \ No newline at end of file From dc10f3644db52100a453d54bb12e22da69a8e8f5 Mon Sep 17 00:00:00 2001 From: Len van Essen Date: Tue, 4 Feb 2020 15:38:01 +0100 Subject: [PATCH 2/5] Refactor to SessionService --- src/ApiConnectors/BaseApiConnector.php | 5 +- src/ApiConnectors/OfficeApiConnector.php | 4 +- src/Enums/Services.php | 4 +- src/Secure/WebservicesAuthentication.php | 53 +------------- src/Services/SelectOfficeService.php | 31 --------- src/Services/SessionService.php | 88 ++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 87 deletions(-) delete mode 100644 src/Services/SelectOfficeService.php create mode 100644 src/Services/SessionService.php diff --git a/src/ApiConnectors/BaseApiConnector.php b/src/ApiConnectors/BaseApiConnector.php index ff3bf6c2..2c2fe763 100644 --- a/src/ApiConnectors/BaseApiConnector.php +++ b/src/ApiConnectors/BaseApiConnector.php @@ -9,6 +9,7 @@ use PhpTwinfield\Services\FinderService; use PhpTwinfield\Services\ProcessXmlService; use PhpTwinfield\Services\SelectOfficeService; +use PhpTwinfield\Services\SessionService; use PhpTwinfield\Util; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; @@ -169,8 +170,8 @@ protected function getFinderService(): FinderService /** * @throws Exception */ - protected function getSelectOfficeService(): SelectOfficeService + protected function getSessionService(): SessionService { - return $this->connection->getAuthenticatedClient(Services::SELECTOFFICE()); + return $this->connection->getAuthenticatedClient(Services::SESSION()); } } diff --git a/src/ApiConnectors/OfficeApiConnector.php b/src/ApiConnectors/OfficeApiConnector.php index 604ff786..ce60af30 100644 --- a/src/ApiConnectors/OfficeApiConnector.php +++ b/src/ApiConnectors/OfficeApiConnector.php @@ -94,9 +94,9 @@ public function listAll( * @return bool * @throws \PhpTwinfield\Exception */ - public function selectCurrentOffice(Office $office) + public function setOffice(Office $office) { - $response = $this->getSelectOfficeService()->updateOffice($office); + $response = $this->getSessionService()->setOffice($office); return $response; } diff --git a/src/Enums/Services.php b/src/Enums/Services.php index 33ba6f88..e6bb9c5c 100644 --- a/src/Enums/Services.php +++ b/src/Enums/Services.php @@ -4,7 +4,7 @@ use PhpTwinfield\Services\FinderService; use PhpTwinfield\Services\ProcessXmlService; -use PhpTwinfield\Services\SelectOfficeService; +use PhpTwinfield\Services\SessionService; /** * All web services offered by Twinfield. @@ -33,5 +33,5 @@ class Services extends \MyCLabs\Enum\Enum /** * The service that selects the current office in Twinfield */ - protected const SELECTOFFICE = SelectOfficeService::class; + protected const SESSION = SessionService::class; } \ No newline at end of file diff --git a/src/Secure/WebservicesAuthentication.php b/src/Secure/WebservicesAuthentication.php index 63ac4ca4..80cb07e9 100644 --- a/src/Secure/WebservicesAuthentication.php +++ b/src/Secure/WebservicesAuthentication.php @@ -5,6 +5,7 @@ use PhpTwinfield\Enums\Services; use PhpTwinfield\Exception; use PhpTwinfield\Services\BaseService; +use PhpTwinfield\Services\SessionService; class WebservicesAuthentication extends AuthenticatedConnection { @@ -56,57 +57,9 @@ protected function login(): void return; } - $loginService = new class extends BaseService { + $sessionService = new SessionService(); - private const LOGIN_OK = "Ok"; - - /** - * @param string $username - * @param string $password - * @param string $organization - * @return string[] - * @throws Exception - */ - public function getSessionIdAndCluster(string $username, string $password, string $organization): array - { - $response = $this->Logon([ - "user" => $username, - "password" => $password, - "organisation" => $organization, - ]); - - $result = $response->LogonResult; - - // Check response is successful - if ($result !== self::LOGIN_OK) { - throw new Exception("Failed logging in using the credentials, result was \"{$result}\"."); - } - - // Response from the logon request - $loginResponse = $this->__getLastResponse(); - - // Make a new DOM and load the response XML - $envelope = new \DOMDocument(); - $envelope->loadXML($loginResponse); - - // Gets SessionID - $sessionIdElements = $envelope->getElementsByTagName('SessionID'); - $sessionId = $sessionIdElements->item(0)->textContent; - - // Gets Cluster URL - $clusterElements = $envelope->getElementsByTagName('cluster'); - $cluster = $clusterElements->item(0)->textContent; - - return [$sessionId, $cluster]; - } - - final protected function WSDL(): string - { - return "https://login.twinfield.com/webservices/session.asmx?wsdl"; - } - }; - - [$this->sessionID, $this->cluster] = $loginService->getSessionIdAndCluster($this->username, $this->password, $this->organization); + [$this->sessionID, $this->cluster] = $sessionService->getSessionIdAndCluster($this->username, $this->password, $this->organization); } protected function getSoapHeaders() diff --git a/src/Services/SelectOfficeService.php b/src/Services/SelectOfficeService.php deleted file mode 100644 index 15325e84..00000000 --- a/src/Services/SelectOfficeService.php +++ /dev/null @@ -1,31 +0,0 @@ -SelectCompany( - ['company' => $office->getCode()] - ); - - return self::CHANGE_OK === $result->SelectCompanyResult; - } -} \ No newline at end of file diff --git a/src/Services/SessionService.php b/src/Services/SessionService.php new file mode 100644 index 00000000..33d724b8 --- /dev/null +++ b/src/Services/SessionService.php @@ -0,0 +1,88 @@ +Logon([ + "user" => $username, + "password" => $password, + "organisation" => $organization, + ]); + + $result = $response->LogonResult; + + // Check response is successful + if ($result !== self::CHANGE_OK) { + throw new Exception("Failed logging in using the credentials, result was \"{$result}\"."); + } + + // Response from the logon request + $loginResponse = $this->__getLastResponse(); + + // Make a new DOM and load the response XML + $envelope = new \DOMDocument(); + $envelope->loadXML($loginResponse); + + // Gets SessionID + $sessionIdElements = $envelope->getElementsByTagName('SessionID'); + $sessionId = $sessionIdElements->item(0)->textContent; + + // Gets Cluster URL + $clusterElements = $envelope->getElementsByTagName('cluster'); + $cluster = $clusterElements->item(0)->textContent; + + return [$sessionId, $cluster]; + } + + /** + * Sets the current company in the API + * + * @param Office $office + * @return bool + */ + public function setOffice(Office $office): bool + { + $result = $this->SelectCompany( + ['company' => $office->getCode()] + ); + + return self::CHANGE_OK === $result->SelectCompanyResult; + } + + final protected function WSDL(): string + { + return "/webservices/session.asmx?wsdl"; + } +}; \ No newline at end of file From c7fda40a39a43f5841509615456614ede525f8e9 Mon Sep 17 00:00:00 2001 From: Len van Essen Date: Tue, 4 Feb 2020 15:38:44 +0100 Subject: [PATCH 3/5] Change documentation --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 28d1028f..25fda149 100644 --- a/readme.md +++ b/readme.md @@ -28,7 +28,7 @@ Some endpoints allow you to filter on the Office, but for instance the BrowseDat ```php $office = Office::fromCode("someOfficeCode"); $officeApi = new \PhpTwinfield\ApiConnectors\OfficeApiConnector($connection); -$officeApi->selectCurrentOffice($office); +$officeApi->setOffice($office); ``` In order to use OAuth2 to authenticate with Twinfield, one should use the `\PhpTwinfield\Secure\Provider\OAuthProvider` to retrieve an `\League\OAuth2\Client\Token\AccessToken` object, and extract the refresh token from this object. Furthermore, it is required to set up a default `\PhpTwinfield\Office`, that will be used during requests to Twinfield. **Please note:** when a different office is specified when sending a request through one of the `ApiConnectors`, this Office will override the default. From 848442f2ebc7b25e5b99ce12ff5d9fbf89a743cf Mon Sep 17 00:00:00 2001 From: Len van Essen Date: Tue, 4 Feb 2020 16:09:38 +0100 Subject: [PATCH 4/5] Base test setup --- tests/IntegrationTests/BaseIntegrationTest.php | 10 ++++++++++ tests/IntegrationTests/OfficeIntegrationTest.php | 2 ++ 2 files changed, 12 insertions(+) diff --git a/tests/IntegrationTests/BaseIntegrationTest.php b/tests/IntegrationTests/BaseIntegrationTest.php index 1d057088..db02582f 100644 --- a/tests/IntegrationTests/BaseIntegrationTest.php +++ b/tests/IntegrationTests/BaseIntegrationTest.php @@ -8,6 +8,7 @@ use PhpTwinfield\Secure\AuthenticatedConnection; use PhpTwinfield\Services\FinderService; use PhpTwinfield\Services\ProcessXmlService; +use PhpTwinfield\Services\SessionService; use PHPUnit\Framework\TestCase; abstract class BaseIntegrationTest extends TestCase @@ -32,6 +33,11 @@ abstract class BaseIntegrationTest extends TestCase */ protected $finderService; + /** + * @var FinderService|\PHPUnit_Framework_MockObject_MockObject + */ + protected $sessionService; + protected function setUp() { parent::setUp(); @@ -41,6 +47,7 @@ protected function setUp() $this->processXmlService = $this->createPartialMock(ProcessXmlService::class, ['sendDocument']); $this->finderService = $this->createPartialMock(FinderService::class, ['searchFinder']); + $this->sessionService = $this->createPartialMock(SessionService::class, ['setOffice']); $this->connection = $this->createMock(AuthenticatedConnection::class); $this->connection->expects($this->any()) @@ -52,6 +59,9 @@ protected function setUp() case Services::FINDER()->getValue(): return $this->finderService; + + case Services::SESSION()->getValue(): + return $this->sessionService; } throw new \InvalidArgumentException("Unknown service {$service->getValue()}"); diff --git a/tests/IntegrationTests/OfficeIntegrationTest.php b/tests/IntegrationTests/OfficeIntegrationTest.php index f35e08f3..fbcab9d6 100644 --- a/tests/IntegrationTests/OfficeIntegrationTest.php +++ b/tests/IntegrationTests/OfficeIntegrationTest.php @@ -3,6 +3,7 @@ namespace PhpTwinfield\IntegrationTests; use PhpTwinfield\ApiConnectors\OfficeApiConnector; +use PhpTwinfield\Office; use PhpTwinfield\Response\Response; class OfficeIntegrationTest extends BaseIntegrationTest @@ -30,6 +31,7 @@ public function testListOfficesWithoutCompanyId() ->willReturn($response); $offices = $this->officeApiConnector->listAllWithoutOfficeCode(); + $this->assertCount(2, $offices); $this->assertSame('001', $offices[0]->getCode()); From ed7f22b5bc1d5a433d5ab2242f7387a3b820da8e Mon Sep 17 00:00:00 2001 From: Len van Essen Date: Tue, 4 Feb 2020 16:28:36 +0100 Subject: [PATCH 5/5] Update office test --- tests/IntegrationTests/OfficeIntegrationTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/IntegrationTests/OfficeIntegrationTest.php b/tests/IntegrationTests/OfficeIntegrationTest.php index fbcab9d6..2986db66 100644 --- a/tests/IntegrationTests/OfficeIntegrationTest.php +++ b/tests/IntegrationTests/OfficeIntegrationTest.php @@ -40,4 +40,16 @@ public function testListOfficesWithoutCompanyId() $this->assertSame('010', $offices[1]->getCode()); $this->assertSame('More&Zo Holding', $offices[1]->getName()); } + + public function testSetOffice() + { + $this->sessionService + ->expects($this->once()) + ->method("setOffice") + ->with($this->office) + ->willReturn(true); + + $response = $this->officeApiConnector->setOffice($this->office); + $this->assertTrue($response); + } } \ No newline at end of file