diff --git a/Classes/Domain/Import/Importer/FetchData.php b/Classes/Domain/Import/Importer/FetchData.php
index 35aed36..0618f35 100644
--- a/Classes/Domain/Import/Importer/FetchData.php
+++ b/Classes/Domain/Import/Importer/FetchData.php
@@ -25,7 +25,10 @@ namespace WerkraumMedia\ThueCat\Domain\Import\Importer;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface as CacheFrontendInterface;
+use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseException;
class FetchData
{
@@ -44,6 +47,16 @@ class FetchData
*/
private $cache;
+ /**
+ * @var string
+ */
+ private $databaseUrlPrefix = 'https://cdb.thuecat.org';
+
+ /**
+ * @var string
+ */
+ private $urlPrefix = 'https://thuecat.org';
+
public function __construct(
RequestFactoryInterface $requestFactory,
ClientInterface $httpClient,
@@ -54,6 +67,15 @@ class FetchData
$this->cache = $cache;
}
+ public function updatedNodes(string $scopeId): array
+ {
+ return $this->jsonLDFromUrl(
+ $this->databaseUrlPrefix
+ . '/api/ext-sync/get-updated-nodes?syncScopeId='
+ . urlencode($scopeId)
+ );
+ }
+
public function jsonLDFromUrl(string $url): array
{
$cacheIdentifier = sha1($url);
@@ -65,6 +87,8 @@ class FetchData
$request = $this->requestFactory->createRequest('GET', $url);
$response = $this->httpClient->sendRequest($request);
+ $this->handleInvalidResponse($response, $request);
+
$jsonLD = json_decode((string) $response->getBody(), true);
if (is_array($jsonLD)) {
$this->cache->set($cacheIdentifier, $jsonLD);
@@ -73,4 +97,35 @@ class FetchData
return [];
}
+
+ public function getResourceEndpoint(): string
+ {
+ return $this->urlPrefix . '/resources/';
+ }
+
+ private function handleInvalidResponse(
+ ResponseInterface $response,
+ RequestInterface $request
+ ): void {
+ if ($response->getStatusCode() === 200) {
+ return;
+ }
+
+ if ($response->getStatusCode() === 401) {
+ throw new InvalidResponseException(
+ 'Unauthorized API request, ensure apiKey is properly configured.',
+ 1622461709
+ );
+ }
+
+ if ($response->getStatusCode() === 404) {
+ throw new InvalidResponseException(
+ sprintf(
+ 'Not found, given resource could not be found: "%s".',
+ $request->getUri()
+ ),
+ 1622461820
+ );
+ }
+ }
}
diff --git a/Classes/Domain/Import/Importer/FetchData/InvalidResponseException.php b/Classes/Domain/Import/Importer/FetchData/InvalidResponseException.php
new file mode 100644
index 0000000..f9f4588
--- /dev/null
+++ b/Classes/Domain/Import/Importer/FetchData/InvalidResponseException.php
@@ -0,0 +1,26 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+class InvalidResponseException extends \RuntimeException
+{
+}
diff --git a/Classes/Domain/Import/RequestFactory.php b/Classes/Domain/Import/RequestFactory.php
index a2773f0..2a6d3c8 100644
--- a/Classes/Domain/Import/RequestFactory.php
+++ b/Classes/Domain/Import/RequestFactory.php
@@ -24,17 +24,41 @@ namespace WerkraumMedia\ThueCat\Domain\Import;
*/
use Psr\Http\Message\RequestInterface;
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Http\RequestFactory as Typo3RequestFactory;
use TYPO3\CMS\Core\Http\Uri;
class RequestFactory extends Typo3RequestFactory
{
+ /**
+ * @var ExtensionConfiguration
+ */
+ private $extensionConfiguration;
+
+ public function __construct(
+ ExtensionConfiguration $extensionConfiguration
+ ) {
+ $this->extensionConfiguration = $extensionConfiguration;
+ }
+
public function createRequest(string $method, $uri): RequestInterface
{
$uri = new Uri((string) $uri);
- $uri = $uri->withQuery('?format=jsonld');
- // TODO: Add api key from site
+ $query = [];
+ parse_str($uri->getQuery(), $query);
+ $query = array_merge($query, [
+ 'format' => 'jsonld',
+ ]);
+
+ try {
+ $query['api_key'] = $this->extensionConfiguration->get('thuecat', 'apiKey');
+ } catch (ExtensionConfigurationExtensionNotConfiguredException $e) {
+ // Nothing todo, not configured, don't add.
+ }
+
+ $uri = $uri->withQuery(http_build_query($query));
return parent::createRequest($method, $uri);
}
diff --git a/Classes/Domain/Import/UrlProvider/StaticUrlProvider.php b/Classes/Domain/Import/UrlProvider/StaticUrlProvider.php
index 60eb5ae..165ecfc 100644
--- a/Classes/Domain/Import/UrlProvider/StaticUrlProvider.php
+++ b/Classes/Domain/Import/UrlProvider/StaticUrlProvider.php
@@ -23,7 +23,6 @@ namespace WerkraumMedia\ThueCat\Domain\Import\UrlProvider;
* 02110-1301, USA.
*/
-use TYPO3\CMS\Core\Utility\GeneralUtility;
use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration;
class StaticUrlProvider implements UrlProvider
@@ -33,14 +32,6 @@ class StaticUrlProvider implements UrlProvider
*/
private $urls = [];
- public function __construct(
- ImportConfiguration $configuration
- ) {
- if ($configuration instanceof ImportConfiguration) {
- $this->urls = $configuration->getUrls();
- }
- }
-
public function canProvideForConfiguration(
ImportConfiguration $configuration
): bool {
@@ -50,7 +41,10 @@ class StaticUrlProvider implements UrlProvider
public function createWithConfiguration(
ImportConfiguration $configuration
): UrlProvider {
- return GeneralUtility::makeInstance(self::class, $configuration);
+ $instance = clone $this;
+ $instance->urls = $configuration->getUrls();
+
+ return $instance;
}
public function getUrls(): array
diff --git a/Classes/Domain/Import/UrlProvider/SyncScopeUrlProvider.php b/Classes/Domain/Import/UrlProvider/SyncScopeUrlProvider.php
new file mode 100644
index 0000000..c6739a4
--- /dev/null
+++ b/Classes/Domain/Import/UrlProvider/SyncScopeUrlProvider.php
@@ -0,0 +1,74 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
+use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration;
+
+class SyncScopeUrlProvider implements UrlProvider
+{
+ /**
+ * @var FetchData
+ */
+ private $fetchData;
+
+ /**
+ * @var string
+ */
+ private $syncScopeId = '';
+
+ public function __construct(
+ FetchData $fetchData
+ ) {
+ $this->fetchData = $fetchData;
+ }
+
+ public function canProvideForConfiguration(
+ ImportConfiguration $configuration
+ ): bool {
+ return $configuration->getType() === 'syncScope';
+ }
+
+ public function createWithConfiguration(
+ ImportConfiguration $configuration
+ ): UrlProvider {
+ $instance = clone $this;
+ $instance->syncScopeId = $configuration->getSyncScopeId();
+
+ return $instance;
+ }
+
+ public function getUrls(): array
+ {
+ $response = $this->fetchData->updatedNodes($this->syncScopeId);
+
+ $resourceIds = array_values($response['data']['createdOrUpdated'] ?? []);
+
+ $urls = array_map(function (string $id) {
+ return $this->fetchData->getResourceEndpoint() . $id;
+ }, $resourceIds);
+
+ return $urls;
+ }
+}
diff --git a/Classes/Domain/Model/Backend/ImportConfiguration.php b/Classes/Domain/Model/Backend/ImportConfiguration.php
index 400ab1b..cda9083 100644
--- a/Classes/Domain/Model/Backend/ImportConfiguration.php
+++ b/Classes/Domain/Model/Backend/ImportConfiguration.php
@@ -97,14 +97,46 @@ class ImportConfiguration extends AbstractEntity
return ArrayUtility::getValueByPath($urlEntry, 'url/el/url/vDEF');
}, $this->getEntries());
+ $entries = array_filter($entries);
+
return array_values($entries);
}
+ public function getSyncScopeId(): string
+ {
+ if ($this->configuration === '') {
+ return '';
+ }
+
+ $configurationAsArray = $this->getConfigurationAsArray();
+ $arrayPath = 'data/sDEF/lDEF/syncScopeId/vDEF';
+
+ if (ArrayUtility::isValidPath($configurationAsArray, $arrayPath) === false) {
+ return '';
+ }
+
+ return ArrayUtility::getValueByPath(
+ $configurationAsArray,
+ $arrayPath
+ );
+ }
+
private function getEntries(): array
{
+ $configurationAsArray = $this->getConfigurationAsArray();
+
+ if (ArrayUtility::isValidPath($configurationAsArray, 'data/sDEF/lDEF/urls/el') === false) {
+ return [];
+ }
+
return ArrayUtility::getValueByPath(
- GeneralUtility::xml2array($this->configuration),
+ $configurationAsArray,
'data/sDEF/lDEF/urls/el'
);
}
+
+ private function getConfigurationAsArray(): array
+ {
+ return GeneralUtility::xml2array($this->configuration);
+ }
}
diff --git a/Configuration/FlexForm/ImportConfiguration/SyncScope.xml b/Configuration/FlexForm/ImportConfiguration/SyncScope.xml
new file mode 100644
index 0000000..59e715c
--- /dev/null
+++ b/Configuration/FlexForm/ImportConfiguration/SyncScope.xml
@@ -0,0 +1,35 @@
+
+
+ 1
+
+
+
+
+
+ LLL:EXT:thuecat/Resources/Private/Language/locallang_flexform.xlf:importConfiguration.syncScope.sheetTitle
+
+ array
+
+
+
+
+
+ input
+ int,required
+
+
+
+
+
+
+
+ input
+ trim,required
+
+
+
+
+
+
+
+
diff --git a/Configuration/TCA/tx_thuecat_import_configuration.php b/Configuration/TCA/tx_thuecat_import_configuration.php
index d379b94..d001748 100644
--- a/Configuration/TCA/tx_thuecat_import_configuration.php
+++ b/Configuration/TCA/tx_thuecat_import_configuration.php
@@ -10,6 +10,7 @@ return (static function (string $extensionKey, string $tableName) {
'ctrl' => [
'label' => 'title',
'iconfile' => \WerkraumMedia\ThueCat\Extension::getIconPath() . $tableName . '.svg',
+ 'type' => 'type',
'default_sortby' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
@@ -40,6 +41,10 @@ return (static function (string $extensionKey, string $tableName) {
$languagePath . '.type.static',
'static',
],
+ [
+ $languagePath . '.type.syncScope',
+ 'syncScope',
+ ],
],
],
],
@@ -51,6 +56,7 @@ return (static function (string $extensionKey, string $tableName) {
'ds' => [
'default' => $flexFormConfigurationPath . 'ImportConfiguration/Static.xml',
'static' => $flexFormConfigurationPath . 'ImportConfiguration/Static.xml',
+ 'syncScope' => $flexFormConfigurationPath . 'ImportConfiguration/SyncScope.xml',
],
],
],
diff --git a/README.md b/README.md
index 2bc98ee..e7f5504 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
# ThüCAT integration into TYPO3 CMS
-
ThüCAT is ¨Thüringer Content Architektur Tourismus¨.
This is an extension for TYPO3 CMS (https://typo3.org/) to integrate ThüCAT.
The existing API is integrated and allows importing data into the system.
@@ -9,8 +8,12 @@ The existing API is integrated and allows importing data into the system.
The extension already allows:
-* Create static configuration to import specified resources,
- e.g. defined organisation or towns.
+* Create configuration to import:
+
+ * specified resources via static configuration,
+ e.g. defined organisation or towns.
+
+ * sync scope, a syncScopeId to always update delivered resources.
* Support multiple languages
@@ -47,3 +50,10 @@ The extension already allows:
* Content element to display town, tourist information and organisation.
* Extending import to include further properties
+
+## Installation
+
+Please configure API Key via Extension Configuration.
+
+Configuration records need to be created, e.g. by visiting the ThüCAT module.
+Those can then be imported via the same module.
diff --git a/Resources/Private/Language/locallang_conf.xlf b/Resources/Private/Language/locallang_conf.xlf
new file mode 100644
index 0000000..e5ac0f1
--- /dev/null
+++ b/Resources/Private/Language/locallang_conf.xlf
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/Private/Language/locallang_flexform.xlf b/Resources/Private/Language/locallang_flexform.xlf
index a3d39a7..84bc748 100644
--- a/Resources/Private/Language/locallang_flexform.xlf
+++ b/Resources/Private/Language/locallang_flexform.xlf
@@ -3,6 +3,7 @@
+
@@ -16,6 +17,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf
index b1a7bc0..b5d0c9d 100644
--- a/Resources/Private/Language/locallang_tca.xlf
+++ b/Resources/Private/Language/locallang_tca.xlf
@@ -130,6 +130,9 @@
+
+
+
diff --git a/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_51cb06caa2f1ca6e989e10b0ee7d9b0f.txt b/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_51cb06caa2f1ca6e989e10b0ee7d9b0f.txt
new file mode 100644
index 0000000..2cba660
--- /dev/null
+++ b/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_51cb06caa2f1ca6e989e10b0ee7d9b0f.txt
@@ -0,0 +1,19 @@
+HTTP/1.1 200 OK
+Date: Mon, 31 May 2021 07:45:26 GMT
+Content-Type: application/json; charset=utf-8
+Content-Length: 34
+Connection: keep-alive
+access-control-allow-origin: https://cdb.thuecat.org
+content-security-policy: default-src 'self'; script-src 'self' 'sha256-xfTbtWk8kVI65iLJs8LB3lWf2g0g10DS71pDdoutFHc='; style-src 'self' 'unsafe-inline' https://stackpath.bootstrapcdn.com; img-src 'self' data: blob: *
+feature-policy: microphone 'none'; camera 'none'; payment 'none'
+referrer-policy: same-origin
+x-content-type-options: nosniff
+x-xss-protection: 1; mode=block
+x-frame-options: deny
+access-control-allow-credentials: true
+strict-transport-security: max-age=15724800; includeSubDomains
+access-control-allow-headers: Authorization, Content-Type
+access-control-allow-methods: HEAD, GET, POST, DELETE, OPTIONS
+set-cookie: ahSession=3d594be0a8f63b6e5aa0683d86c33f0014462fff;path=/;expires=Thu, 01 Jul 2021 07:45:26 GMT;httpOnly=true;
+
+{"data":{"createdOrUpdated":["835224016581-dara","165868194223-zmqf","215230952334-yyno"],"removed":["319489049949-yzpe","440865870518-kcka","057564926026-ambc","502105041571-gtmz","956950809461-mkyx","505212346932-dgdj","304166137220-qegp","052993102595-yytg","008779699609-ettg","992865433390-jqcw","678174286034-dpza","473249269683-mxjj","r_20704386-oapoi","121412224073-roqx","067447662224-fhpb","103385129122-pypq","764328419582-bdhj","303605630412-cygb","891743863902-bkeb"]}}
diff --git a/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_c4796659430e72ef994a68ca29ac5e8c.txt b/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_c4796659430e72ef994a68ca29ac5e8c.txt
new file mode 100644
index 0000000..2cba660
--- /dev/null
+++ b/Tests/Functional/Fixtures/Import/Guzzle/cdb.thuecat.org/api/ext-sync/get-updated-nodes/GET_c4796659430e72ef994a68ca29ac5e8c.txt
@@ -0,0 +1,19 @@
+HTTP/1.1 200 OK
+Date: Mon, 31 May 2021 07:45:26 GMT
+Content-Type: application/json; charset=utf-8
+Content-Length: 34
+Connection: keep-alive
+access-control-allow-origin: https://cdb.thuecat.org
+content-security-policy: default-src 'self'; script-src 'self' 'sha256-xfTbtWk8kVI65iLJs8LB3lWf2g0g10DS71pDdoutFHc='; style-src 'self' 'unsafe-inline' https://stackpath.bootstrapcdn.com; img-src 'self' data: blob: *
+feature-policy: microphone 'none'; camera 'none'; payment 'none'
+referrer-policy: same-origin
+x-content-type-options: nosniff
+x-xss-protection: 1; mode=block
+x-frame-options: deny
+access-control-allow-credentials: true
+strict-transport-security: max-age=15724800; includeSubDomains
+access-control-allow-headers: Authorization, Content-Type
+access-control-allow-methods: HEAD, GET, POST, DELETE, OPTIONS
+set-cookie: ahSession=3d594be0a8f63b6e5aa0683d86c33f0014462fff;path=/;expires=Thu, 01 Jul 2021 07:45:26 GMT;httpOnly=true;
+
+{"data":{"createdOrUpdated":["835224016581-dara","165868194223-zmqf","215230952334-yyno"],"removed":["319489049949-yzpe","440865870518-kcka","057564926026-ambc","502105041571-gtmz","956950809461-mkyx","505212346932-dgdj","304166137220-qegp","052993102595-yytg","008779699609-ettg","992865433390-jqcw","678174286034-dpza","473249269683-mxjj","r_20704386-oapoi","121412224073-roqx","067447662224-fhpb","103385129122-pypq","764328419582-bdhj","303605630412-cygb","891743863902-bkeb"]}}
diff --git a/Tests/Functional/Fixtures/Import/ImportsSyncScope.xml b/Tests/Functional/Fixtures/Import/ImportsSyncScope.xml
new file mode 100644
index 0000000..25816f4
--- /dev/null
+++ b/Tests/Functional/Fixtures/Import/ImportsSyncScope.xml
@@ -0,0 +1,91 @@
+
+
+
+ 1
+ 0
+ 1613400587
+ 1613400558
+ 1
+ 4
+ Rootpage
+ 1
+
+
+ 10
+ 1
+ 1613400587
+ 1613400558
+ 1
+ 254
+ Storage folder
+
+
+
+ 1
+ 0
+ English
+ en-us-gb
+ en
+
+
+
+ 2
+ 0
+ French
+ fr
+ fr
+
+
+
+ 1
+ 0
+ 1613400587
+ 1613400558
+ 1
+ 0
+ Sync Scope ID
+ syncScope
+
+
+
+
+
+
+ 10
+
+
+ dd4615dc-58a6-4648-a7ce-4950293a06db
+
+
+
+
+ ]]>
+
+
+
+ 1
+ 10
+ 1613401129
+ 1613401129
+ 1
+ 0
+ https://thuecat.org/resources/043064193523-jcyt
+ 1
+ 0
+ Erfurt
+
+
+
+ 1
+ 10
+ 1613400969
+ 1613400969
+ 1
+ 0
+ https://thuecat.org/resources/018132452787-ngbe
+ Erfurt Tourismus und Marketing GmbH
+ Die Erfurt Tourismus & Marketing GmbH (ETMG) wurde 1997 als offizielle Organisation zur Tourismusförderung in der Landeshauptstadt Erfurt gegründet und nahm am 01.0 1.1998 die Geschäftstätigkeit auf.
+ 0
+ 0
+
+
diff --git a/Tests/Functional/Fixtures/Import/ImportsSyncScopeResult.csv b/Tests/Functional/Fixtures/Import/ImportsSyncScopeResult.csv
new file mode 100644
index 0000000..aae1312
--- /dev/null
+++ b/Tests/Functional/Fixtures/Import/ImportsSyncScopeResult.csv
@@ -0,0 +1,10 @@
+tx_thuecat_tourist_attraction
+,"uid","pid","sys_language_uid","l18n_parent","l10n_source","l10n_state","remote_id","title","managed_by","town","address","offers"
+,1,10,0,0,0,\NULL,"https://thuecat.org/resources/835224016581-dara","Dom St. Marien",1,1,"{""street"":""Domstufen 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""dominformation@domberg-erfurt.de"",""phone"":""+49 361 6461265"",""fax"":"""",""geo"":{""latitude"":50.975955358589545,""longitude"":11.023667024961856}}","[]"
+,2,10,1,1,1,\NULL,"https://thuecat.org/resources/835224016581-dara","Cathedral of St. Mary",1,1,"{""street"":""Domstufen 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""dominformation@domberg-erfurt.de"",""phone"":""+49 361 6461265"",""fax"":"""",""geo"":{""latitude"":50.975955358589545,""longitude"":11.023667024961856}}","[]"
+,3,10,0,0,0,\NULL,"https://thuecat.org/resources/165868194223-zmqf","Alte Synagoge",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}","[{""title"":""F\u00fchrungen"",""description"":""Immer samstags, um 11:15 Uhr findet eine \u00f6ffentliche F\u00fchrung durch das Museum statt. Dauer etwa 90 Minuten"",""prices"":[{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""}]},{""title"":""Eintritt"",""description"":""Schulklassen und Kitagruppen im Rahmen des Unterrichts: Eintritt frei\nAn jedem ersten Dienstag im Monat: Eintritt frei"",""prices"":[{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Familienkarte"",""description"":"""",""price"":17,""currency"":""EUR"",""rule"":""PerGroup""},{""title"":""ErfurtCard"",""description"":"""",""price"":14.9,""currency"":""EUR"",""rule"":""PerPackage""},{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""}]}]"
+,4,10,1,3,3,\NULL,"https://thuecat.org/resources/165868194223-zmqf","Old Synagogue",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}","[{""title"":""F\u00fchrungen"",""description"":""Immer samstags, um 11:15 Uhr findet eine \u00f6ffentliche F\u00fchrung durch das Museum statt. Dauer etwa 90 Minuten"",""prices"":[{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""}]},{""title"":""Eintritt"",""description"":""Schulklassen und Kitagruppen im Rahmen des Unterrichts: Eintritt frei\nAn jedem ersten Dienstag im Monat: Eintritt frei"",""prices"":[{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Familienkarte"",""description"":"""",""price"":17,""currency"":""EUR"",""rule"":""PerGroup""},{""title"":""ErfurtCard"",""description"":"""",""price"":14.9,""currency"":""EUR"",""rule"":""PerPackage""},{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""}]}]"
+,5,10,2,3,3,\NULL,"https://thuecat.org/resources/165868194223-zmqf","La vieille synagogue",1,1,"{""street"":""Waagegasse 8"",""zip"":""99084"",""city"":""Erfurt"",""email"":""altesynagoge@erfurt.de"",""phone"":""+49 361 6551520"",""fax"":""+49 361 6551669"",""geo"":{""latitude"":50.978765,""longitude"":11.029133}}","[{""title"":""F\u00fchrungen"",""description"":""Immer samstags, um 11:15 Uhr findet eine \u00f6ffentliche F\u00fchrung durch das Museum statt. Dauer etwa 90 Minuten"",""prices"":[{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""}]},{""title"":""Eintritt"",""description"":""Schulklassen und Kitagruppen im Rahmen des Unterrichts: Eintritt frei\nAn jedem ersten Dienstag im Monat: Eintritt frei"",""prices"":[{""title"":""Erm\u00e4\u00dfigt"",""description"":""als erm\u00e4\u00dfigt gelten schulpflichtige Kinder, Auszubildende, Studierende, Rentner\/-innen, Menschen mit Behinderungen, Inhaber Sozialausweis der Landeshauptstadt Erfurt"",""price"":5,""currency"":""EUR"",""rule"":""PerPerson""},{""title"":""Familienkarte"",""description"":"""",""price"":17,""currency"":""EUR"",""rule"":""PerGroup""},{""title"":""ErfurtCard"",""description"":"""",""price"":14.9,""currency"":""EUR"",""rule"":""PerPackage""},{""title"":""Erwachsene"",""description"":"""",""price"":8,""currency"":""EUR"",""rule"":""PerPerson""}]}]"
+,6,10,0,0,0,\NULL,"https://thuecat.org/resources/215230952334-yyno","Krämerbrücke",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}","[]"
+,7,10,1,6,6,\NULL,"https://thuecat.org/resources/215230952334-yyno","Merchants' Bridge",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}","[]"
+,8,10,2,6,6,\NULL,"https://thuecat.org/resources/215230952334-yyno","Pont de l'épicier",1,1,"{""street"":""Benediktsplatz 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""service@erfurt-tourismus.de"",""phone"":""+49 361 66 400"",""fax"":"""",""geo"":{""latitude"":50.978772,""longitude"":11.031622}}","[]"
diff --git a/Tests/Functional/ImportTest.php b/Tests/Functional/ImportTest.php
index a7842a2..2ea1fc0 100644
--- a/Tests/Functional/ImportTest.php
+++ b/Tests/Functional/ImportTest.php
@@ -91,6 +91,14 @@ class ImportTest extends TestCase
'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Import/Sites/' => 'typo3conf/sites',
];
+ protected $configurationToUseInTestInstance = [
+ 'EXTENSIONS' => [
+ 'thuecat' => [
+ 'apiKey' => null,
+ ],
+ ],
+ ];
+
protected function setUp(): void
{
parent::setUp();
@@ -212,6 +220,24 @@ class ImportTest extends TestCase
$this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelationsResult.csv');
}
+ /**
+ * @test
+ */
+ public function importsBasedOnSyncScope(): void
+ {
+ $this->importDataSet(__DIR__ . '/Fixtures/Import/ImportsSyncScope.xml');
+
+ $serverRequest = $this->getServerRequest();
+
+ $extbaseBootstrap = $this->getContainer()->get(Bootstrap::class);
+ $extbaseBootstrap->handleBackendRequest($serverRequest->reveal());
+
+ $touristAttractions = $this->getAllRecords('tx_thuecat_tourist_attraction');
+ self::assertCount(8, $touristAttractions);
+
+ $this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsSyncScopeResult.csv');
+ }
+
/**
* @return ObjectProphecy
*/
diff --git a/Tests/Unit/Domain/Import/Importer/FetchDataTest.php b/Tests/Unit/Domain/Import/Importer/FetchDataTest.php
index a9ffd82..1276017 100644
--- a/Tests/Unit/Domain/Import/Importer/FetchDataTest.php
+++ b/Tests/Unit/Domain/Import/Importer/FetchDataTest.php
@@ -31,6 +31,7 @@ use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData;
+use WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData\InvalidResponseException;
/**
* @covers WerkraumMedia\ThueCat\Domain\Import\Importer\FetchData
@@ -75,6 +76,7 @@ class FetchDataTest extends TestCase
$httpClient->sendRequest($request->reveal())
->willReturn($response->reveal());
+ $response->getStatusCode()->willReturn(200);
$response->getBody()->willReturn('{"@graph":[{"@id":"https://example.com/resources/018132452787-ngbe"}]}');
$subject = new FetchData(
@@ -111,6 +113,7 @@ class FetchDataTest extends TestCase
$httpClient->sendRequest($request->reveal())
->willReturn($response->reveal());
+ $response->getStatusCode()->willReturn(200);
$response->getBody()->willReturn('');
$subject = new FetchData(
@@ -155,4 +158,74 @@ class FetchDataTest extends TestCase
],
], $result);
}
+
+ /**
+ * @test
+ */
+ public function throwsExceptionOn404(): void
+ {
+ $requestFactory = $this->prophesize(RequestFactoryInterface::class);
+ $httpClient = $this->prophesize(ClientInterface::class);
+ $cache = $this->prophesize(FrontendInterface::class);
+
+ $request = $this->prophesize(RequestInterface::class);
+ $response = $this->prophesize(ResponseInterface::class);
+
+ $request->getUri()->willReturn('https://example.com/resources/018132452787-ngbe');
+
+ $requestFactory->createRequest('GET', 'https://example.com/resources/018132452787-ngbe')
+ ->willReturn($request->reveal());
+
+ $httpClient->sendRequest($request->reveal())
+ ->willReturn($response->reveal());
+
+
+ $response->getStatusCode()->willReturn(404);
+ $response->getBody()->willReturn('{"error":"404"}');
+
+ $subject = new FetchData(
+ $requestFactory->reveal(),
+ $httpClient->reveal(),
+ $cache->reveal()
+ );
+
+ $this->expectException(InvalidResponseException::class);
+ $this->expectExceptionCode(1622461820);
+ $this->expectExceptionMessage('Not found, given resource could not be found: "https://example.com/resources/018132452787-ngbe".');
+
+ $subject->jsonLDFromUrl('https://example.com/resources/018132452787-ngbe');
+ }
+
+ /**
+ * @test
+ */
+ public function throwsExceptionOn401(): void
+ {
+ $requestFactory = $this->prophesize(RequestFactoryInterface::class);
+ $httpClient = $this->prophesize(ClientInterface::class);
+ $cache = $this->prophesize(FrontendInterface::class);
+
+ $request = $this->prophesize(RequestInterface::class);
+ $response = $this->prophesize(ResponseInterface::class);
+
+ $requestFactory->createRequest('GET', 'https://example.com/resources/018132452787-ngbe')
+ ->willReturn($request->reveal());
+
+ $httpClient->sendRequest($request->reveal())
+ ->willReturn($response->reveal());
+
+ $response->getStatusCode()->willReturn(401);
+
+ $subject = new FetchData(
+ $requestFactory->reveal(),
+ $httpClient->reveal(),
+ $cache->reveal()
+ );
+
+ $this->expectException(InvalidResponseException::class);
+ $this->expectExceptionCode(1622461709);
+ $this->expectExceptionMessage('Unauthorized API request, ensure apiKey is properly configured.');
+
+ $subject->jsonLDFromUrl('https://example.com/resources/018132452787-ngbe');
+ }
}
diff --git a/Tests/Unit/Domain/Import/RequestFactoryTest.php b/Tests/Unit/Domain/Import/RequestFactoryTest.php
index e222263..9053338 100644
--- a/Tests/Unit/Domain/Import/RequestFactoryTest.php
+++ b/Tests/Unit/Domain/Import/RequestFactoryTest.php
@@ -24,6 +24,9 @@ namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Import;
*/
use PHPUnit\Framework\TestCase;
+use Prophecy\PhpUnit\ProphecyTrait;
+use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException;
+use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use WerkraumMedia\ThueCat\Domain\Import\RequestFactory;
/**
@@ -31,12 +34,18 @@ use WerkraumMedia\ThueCat\Domain\Import\RequestFactory;
*/
class RequestFactoryTest extends TestCase
{
+ use ProphecyTrait;
+
/**
* @test
*/
public function canBeCreated(): void
{
- $subject = new RequestFactory();
+ $extensionConfiguration = $this->prophesize(ExtensionConfiguration::class);
+
+ $subject = new RequestFactory(
+ $extensionConfiguration->reveal()
+ );
self::assertInstanceOf(RequestFactory::class, $subject);
}
@@ -46,9 +55,48 @@ class RequestFactoryTest extends TestCase
*/
public function returnsRequestWithJsonIdFormat(): void
{
- $subject = new RequestFactory();
- $request = $subject->createRequest('GET', 'https://example.com/resources/333039283321-xxwg');
+ $extensionConfiguration = $this->prophesize(ExtensionConfiguration::class);
- self::assertSame('format=jsonld', $request->getUri()->getQuery());
+ $subject = new RequestFactory(
+ $extensionConfiguration->reveal()
+ );
+
+ $request = $subject->createRequest('GET', 'https://example.com/api/ext-sync/get-updated-nodes?syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db');
+
+ self::assertSame('syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db&format=jsonld', $request->getUri()->getQuery());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsRequestWithApiKeyWhenConfigured(): void
+ {
+ $extensionConfiguration = $this->prophesize(ExtensionConfiguration::class);
+ $extensionConfiguration->get('thuecat', 'apiKey')->willReturn('some-api-key');
+
+ $subject = new RequestFactory(
+ $extensionConfiguration->reveal()
+ );
+
+ $request = $subject->createRequest('GET', 'https://example.com/api/ext-sync/get-updated-nodes?syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db');
+
+ self::assertSame('syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db&format=jsonld&api_key=some-api-key', $request->getUri()->getQuery());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsRequestWithoutApiKeyWhenUnkown(): void
+ {
+ $extensionConfiguration = $this->prophesize(ExtensionConfiguration::class);
+ $extensionConfiguration->get('thuecat', 'apiKey')->willThrow(new ExtensionConfigurationExtensionNotConfiguredException());
+
+ $subject = new RequestFactory(
+ $extensionConfiguration->reveal()
+ );
+
+ $request = $subject->createRequest('GET', 'https://example.com/api/ext-sync/get-updated-nodes?syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db');
+
+ self::assertSame('syncScopeId=dd3738dc-58a6-4748-a6ce-4950293a06db&format=jsonld', $request->getUri()->getQuery());
}
}
diff --git a/Tests/Unit/Domain/Import/UrlProvider/StaticUrlProviderTest.php b/Tests/Unit/Domain/Import/UrlProvider/StaticUrlProviderTest.php
index a9a593b..7d32da0 100644
--- a/Tests/Unit/Domain/Import/UrlProvider/StaticUrlProviderTest.php
+++ b/Tests/Unit/Domain/Import/UrlProvider/StaticUrlProviderTest.php
@@ -40,10 +40,7 @@ class StaticUrlProviderTest extends TestCase
*/
public function canBeCreated(): void
{
- $configuration = $this->prophesize(ImportConfiguration::class);
- $configuration->getUrls()->willReturn([]);
-
- $subject = new StaticUrlProvider($configuration->reveal());
+ $subject = new StaticUrlProvider();
self::assertInstanceOf(StaticUrlProvider::class, $subject);
}
@@ -56,7 +53,7 @@ class StaticUrlProviderTest extends TestCase
$configuration->getUrls()->willReturn([]);
$configuration->getType()->willReturn('static');
- $subject = new StaticUrlProvider($configuration->reveal());
+ $subject = new StaticUrlProvider();
$result = $subject->canProvideForConfiguration($configuration->reveal());
self::assertTrue($result);
@@ -70,7 +67,7 @@ class StaticUrlProviderTest extends TestCase
$configuration = $this->prophesize(ImportConfiguration::class);
$configuration->getUrls()->willReturn(['https://example.com']);
- $subject = new StaticUrlProvider($configuration->reveal());
+ $subject = new StaticUrlProvider();
$result = $subject->createWithConfiguration($configuration->reveal());
self::assertInstanceOf(StaticUrlProvider::class, $subject);
@@ -84,7 +81,7 @@ class StaticUrlProviderTest extends TestCase
$configuration = $this->prophesize(ImportConfiguration::class);
$configuration->getUrls()->willReturn(['https://example.com']);
- $subject = new StaticUrlProvider($configuration->reveal());
+ $subject = new StaticUrlProvider();
$concreteProvider = $subject->createWithConfiguration($configuration->reveal());
$result = $concreteProvider->getUrls();
diff --git a/Tests/Unit/Domain/Model/Backend/ImportConfigurationTest.php b/Tests/Unit/Domain/Model/Backend/ImportConfigurationTest.php
new file mode 100644
index 0000000..9c7bfc4
--- /dev/null
+++ b/Tests/Unit/Domain/Model/Backend/ImportConfigurationTest.php
@@ -0,0 +1,333 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase as TestCase;
+use WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration;
+
+/**
+ * @covers \WerkraumMedia\ThueCat\Domain\Model\Backend\ImportConfiguration
+ */
+class ImportConfigurationTest extends TestCase
+{
+ /**
+ * @test
+ */
+ public function canBeCreated(): void
+ {
+ $subject = new ImportConfiguration();
+
+ self::assertInstanceOf(ImportConfiguration::class, $subject);
+ }
+
+ /**
+ * @test
+ */
+ public function returnsTitle(): void
+ {
+ $subject = new ImportConfiguration();
+ $subject->_setProperty('title', 'Example Title');
+
+ self::assertSame('Example Title', $subject->getTitle());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsType(): void
+ {
+ $subject = new ImportConfiguration();
+ $subject->_setProperty('type', 'static');
+
+ self::assertSame('static', $subject->getType());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsTableName(): void
+ {
+ $subject = new ImportConfiguration();
+
+ self::assertSame('tx_thuecat_import_configuration', $subject->getTableName());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsLastChanged(): void
+ {
+ $lastChanged = new \DateTimeImmutable();
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('tstamp', $lastChanged);
+
+ self::assertSame($lastChanged, $subject->getLastChanged());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsStoragePidWhenSet(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '20',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame(20, $subject->getStoragePid());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsZeroAsStoragePidWhenNoConfigurationExists(): void
+ {
+ $flexForm = '';
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame(0, $subject->getStoragePid());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsZeroAsStoragePidWhenNegativePidIsConfigured(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '-1',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame(0, $subject->getStoragePid());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsZeroAsStoragePidWhenNoneNumericPidIsConfigured(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'abc',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame(0, $subject->getStoragePid());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsUrlsWhenSet(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'https://thuecat.org/resources/942302009360-jopp',
+ '',
+ '',
+ '',
+ '0',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame([
+ 'https://thuecat.org/resources/942302009360-jopp',
+ ], $subject->getUrls());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsEmptyArrayAsUrlsWhenNoConfigurationExists(): void
+ {
+ $flexForm = '';
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame([], $subject->getUrls());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsEmptyArrayAsUrlsWhenNoUrlsAreConfigured(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '10',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame([], $subject->getUrls());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsSyncScopeIdWhenSet(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'dd4639dc-58a7-4648-a6ce-4950293a06db',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame('dd4639dc-58a7-4648-a6ce-4950293a06db', $subject->getSyncScopeId());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsEmptyStringAsSyncScopeIdWhenNoConfigurationExists(): void
+ {
+ $flexForm = '';
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame('', $subject->getSyncScopeId());
+ }
+
+ /**
+ * @test
+ */
+ public function returnsEmptyStringAsSyncScopeIdWhenNoSyncScopeIdAreConfigured(): void
+ {
+ $flexForm = implode(PHP_EOL, [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '10',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ]);
+
+ $subject = new ImportConfiguration();
+
+ $subject->_setProperty('configuration', $flexForm);
+
+ self::assertSame('', $subject->getSyncScopeId());
+ }
+}
diff --git a/ext_conf_template.txt b/ext_conf_template.txt
new file mode 100644
index 0000000..adacf86
--- /dev/null
+++ b/ext_conf_template.txt
@@ -0,0 +1,2 @@
+# cat=API; type=string; label=LLL:EXT:thuecat/Resources/Private/Language/locallang_conf.xlf:apiKey
+apiKey =