Skip to content

Commit

Permalink
Merge pull request #164 from lenvanessen/feature/select-office
Browse files Browse the repository at this point in the history
Add option to select an office in Twinfield
  • Loading branch information
Willem Stuursma-Ruwen authored Feb 9, 2020
2 parents bda4167 + ed7f22b commit eb0cb0f
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 51 deletions.
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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->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.

Using this information, we can create an instance of the `\PhpTwinfield\Secure\OpenIdConnectAuthentication` class, as follows:
Expand Down
10 changes: 10 additions & 0 deletions src/ApiConnectors/BaseApiConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use PhpTwinfield\Secure\AuthenticatedConnection;
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;
Expand Down Expand Up @@ -164,4 +166,12 @@ protected function getFinderService(): FinderService
{
return $this->connection->getAuthenticatedClient(Services::FINDER());
}

/**
* @throws Exception
*/
protected function getSessionService(): SessionService
{
return $this->connection->getAuthenticatedClient(Services::SESSION());
}
}
13 changes: 13 additions & 0 deletions src/ApiConnectors/OfficeApiConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,17 @@ public function listAll(

return $offices;
}

/**
* Selects the current office
* @param Office $office
* @return bool
* @throws \PhpTwinfield\Exception
*/
public function setOffice(Office $office)
{
$response = $this->getSessionService()->setOffice($office);

return $response;
}
}
6 changes: 6 additions & 0 deletions src/Enums/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpTwinfield\Services\FinderService;
use PhpTwinfield\Services\ProcessXmlService;
use PhpTwinfield\Services\SessionService;

/**
* All web services offered by Twinfield.
Expand All @@ -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 SESSION = SessionService::class;
}
1 change: 0 additions & 1 deletion src/Response/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -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())));
}
Expand Down
53 changes: 3 additions & 50 deletions src/Secure/WebservicesAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PhpTwinfield\Enums\Services;
use PhpTwinfield\Exception;
use PhpTwinfield\Services\BaseService;
use PhpTwinfield\Services\SessionService;

class WebservicesAuthentication extends AuthenticatedConnection
{
Expand Down Expand Up @@ -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()
Expand Down
88 changes: 88 additions & 0 deletions src/Services/SessionService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace PhpTwinfield\Services;

use PhpTwinfield\Exception;
use PhpTwinfield\Office;

class SessionService extends BaseService {

private const CHANGE_OK = "Ok";

/**
* SessionService constructor.
*
* @param string|null $wsdl
* @param array $options
*/
public function __construct(string $wsdl = null, array $options = [])
{
// If no cluster is set, it means we're dealing with a login call
// Hence we set the cluster to use the authentication url
if(! isset($options['cluster'])) {
$options['cluster'] = 'https://login.twinfield.com';
}

parent::__construct($wsdl, $options);
}

/**
* @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::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";
}
};
10 changes: 10 additions & 0 deletions tests/IntegrationTests/BaseIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +33,11 @@ abstract class BaseIntegrationTest extends TestCase
*/
protected $finderService;

/**
* @var FinderService|\PHPUnit_Framework_MockObject_MockObject
*/
protected $sessionService;

protected function setUp()
{
parent::setUp();
Expand All @@ -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())
Expand All @@ -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()}");
Expand Down
14 changes: 14 additions & 0 deletions tests/IntegrationTests/OfficeIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PhpTwinfield\IntegrationTests;

use PhpTwinfield\ApiConnectors\OfficeApiConnector;
use PhpTwinfield\Office;
use PhpTwinfield\Response\Response;

class OfficeIntegrationTest extends BaseIntegrationTest
Expand Down Expand Up @@ -30,6 +31,7 @@ public function testListOfficesWithoutCompanyId()
->willReturn($response);

$offices = $this->officeApiConnector->listAllWithoutOfficeCode();

$this->assertCount(2, $offices);

$this->assertSame('001', $offices[0]->getCode());
Expand All @@ -38,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);
}
}

0 comments on commit eb0cb0f

Please sign in to comment.