From 94e1eba7417689e058866c8ac7440d009f9e93c9 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Wed, 4 Jan 2023 07:33:48 +0100 Subject: [PATCH] Respect givenName and familyName of media authors (#103) Only name was respected until now. Relates: #10247 --- Classes/Domain/Import/Entity/Person.php | 70 ++++++++ .../Import/Typo3Converter/NameExtractor.php | 23 ++- Classes/Domain/Model/Frontend/Media.php | 14 +- Documentation/Changelog/1.3.1.rst | 6 + .../resources/attraction-with-media.json | 70 ++++++++ .../resources/author-with-names.json | 54 +++++++ .../thuecat.org/resources/dms_5197164.json | 3 +- .../thuecat.org/resources/dms_6486108.json | 3 +- .../image-with-author-and-license-author.json | 61 +++++++ .../resources/image-with-author-string.json | 51 ++++++ .../resources/image-with-foreign-author.json | 46 ++++++ .../resources/image-with-license-author.json | 57 +++++++ .../ImportsTouristAttractionWithMedia.csv | 4 + .../ImportsTouristAttractionWithMedia.xml | 76 +++++++++ ...ImportsTouristAttractionsWithRelations.csv | 12 +- Tests/Functional/ImportTest.php | 21 +++ .../Typo3Converter/NameExtractorTest.php | 149 ++++++++++++++++++ .../Unit/Domain/Model/Frontend/MediaTest.php | 128 +++++++++++++++ ext_emconf.php | 2 +- phpstan-baseline.neon | 4 +- 20 files changed, 836 insertions(+), 18 deletions(-) create mode 100644 Classes/Domain/Import/Entity/Person.php create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-media.json create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/author-with-names.json create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-and-license-author.json create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-string.json create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-foreign-author.json create mode 100644 Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-license-author.json create mode 100644 Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.csv create mode 100644 Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.xml create mode 100644 Tests/Unit/Domain/Import/Typo3Converter/NameExtractorTest.php diff --git a/Classes/Domain/Import/Entity/Person.php b/Classes/Domain/Import/Entity/Person.php new file mode 100644 index 0000000..d30c2fb --- /dev/null +++ b/Classes/Domain/Import/Entity/Person.php @@ -0,0 +1,70 @@ + + * + * 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. + */ + +namespace WerkraumMedia\ThueCat\Domain\Import\Entity; + +class Person extends Base implements MapsToType +{ + /** + * @var string + */ + protected $givenName = ''; + + /** + * @var string + */ + protected $familyName = ''; + + public function getGivenName(): string + { + return $this->givenName; + } + + public function getFamilyName(): string + { + return $this->familyName; + } + + /** + * @internal for mapping via Symfony component. + */ + public function setGivenName(string $givenName): void + { + $this->givenName = $givenName; + } + + /** + * @internal for mapping via Symfony component. + */ + public function setFamilyName(string $familyName): void + { + $this->familyName = $familyName; + } + + public static function getSupportedTypes(): array + { + return [ + 'schema:Person', + ]; + } +} diff --git a/Classes/Domain/Import/Typo3Converter/NameExtractor.php b/Classes/Domain/Import/Typo3Converter/NameExtractor.php index 517804b..f87ae6d 100644 --- a/Classes/Domain/Import/Typo3Converter/NameExtractor.php +++ b/Classes/Domain/Import/Typo3Converter/NameExtractor.php @@ -50,13 +50,24 @@ class NameExtractor return $foreignReference; } - if ($foreignReference instanceof ForeignReference) { - $remote = $this->resolveForeignReference->resolve($foreignReference, $language); - if (is_object($remote) && method_exists($remote, 'getName')) { - return $remote->getName(); - } + if (!$foreignReference instanceof ForeignReference) { + return ''; } - return ''; + $remote = $this->resolveForeignReference->resolve($foreignReference, $language); + if (is_object($remote) === false) { + return ''; + } + + $name = ''; + if (method_exists($remote, 'getGivenName') && method_exists($remote, 'getFamilyName')) { + $name = trim($remote->getGivenName() . ' ' . $remote->getFamilyName()); + } + + if ($name === '' && method_exists($remote, 'getName')) { + $name = trim($remote->getName()); + } + + return $name; } } diff --git a/Classes/Domain/Model/Frontend/Media.php b/Classes/Domain/Model/Frontend/Media.php index d5cc36d..de7bba9 100644 --- a/Classes/Domain/Model/Frontend/Media.php +++ b/Classes/Domain/Model/Frontend/Media.php @@ -41,7 +41,7 @@ class Media implements TypeInterface { $this->serialized = $serialized; $data = json_decode($serialized, true); - $this->data = is_array($data) ? $data : []; + $this->data = $this->prepareData(is_array($data) ? $data : []); } public function getMainImage(): array @@ -77,4 +77,16 @@ class Media implements TypeInterface { return $this->serialized; } + + private function prepareData(array $data): array + { + return array_map(function (array $media) { + $copyrightAuthor = $media['author'] ?? $media['license']['author'] ?? ''; + + if ($copyrightAuthor) { + $media['copyrightAuthor'] = $copyrightAuthor; + } + return $media; + }, $data); + } } diff --git a/Documentation/Changelog/1.3.1.rst b/Documentation/Changelog/1.3.1.rst index 4443360..a630082 100644 --- a/Documentation/Changelog/1.3.1.rst +++ b/Documentation/Changelog/1.3.1.rst @@ -12,6 +12,12 @@ Features * Configure EXT:scheduler table garbage collection task to clean up import records. It is now possible to select the tables within the TYPO3 scheduler task to be cleaned up. +* Respect ``schema:givenName`` and ``schema:familyName`` of ``schema:author`` for media. + We only respected ``schema:name`` until now. + +* Provide new key ``copyrightAuthor`` holding the actual author for convenience. + It will pick the ``author`` value falling back to ``license.author``. + Fixes ----- diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-media.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-media.json new file mode 100644 index 0000000..5a4da16 --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-media.json @@ -0,0 +1,70 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/attraction-with-media", + "@type": [ + "schema:Place", + "schema:Thing", + "schema:TouristAttraction", + "ttgds:PointOfInterest", + "thuecat:Building", + "thuecat:ReligiousBuilding", + "thuecat:Cathedral", + "thuecat:CatholicChurch", + "thuecat:Dome" + ], + "schema:availableLanguage": [ + { + "@type": "thuecat:Language", + "@value": "thuecat:German" + }, + { + "@type": "thuecat:Language", + "@value": "thuecat:English" + } + ], + "schema:name": [ + { + "@language": "de", + "@value": "Attraktion mit Bildern" + }, + { + "@language": "en", + "@value": "Attraction with media" + } + ], + "thuecat:contentResponsible": { + "@id": "https://thuecat.org/resources/018132452787-ngbe" + }, + "schema:image": [ + { + "@id": "https://thuecat.org/resources/image-with-foreign-author" + }, + { + "@id": "https://thuecat.org/resources/image-with-author-string" + }, + { + "@id": "https://thuecat.org/resources/image-with-license-author" + }, + { + "@id": "https://thuecat.org/resources/image-with-author-and-license-author" + } + ] + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/author-with-names.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/author-with-names.json new file mode 100644 index 0000000..feeab32 --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/author-with-names.json @@ -0,0 +1,54 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "epapp": "https://thuecat.org/ontology/epapp/1.0/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/author-with-names", + "@type": [ + "schema:Thing", + "schema:Person", + "foaf:Person", + "thuecat:Author", + "ttgds:Author" + ], + "schema:familyName": { + "@language": "de", + "@value": "FamilyName" + }, + "schema:givenName": { + "@language": "de", + "@value": "GivenName" + }, + "schema:honorificPrefix": { + "@type": "thuecat:HonorificPrefix", + "@value": "thuecat:Mr" + }, + "schema:jobTitle": { + "@type": "thuecat:Profession", + "@value": "thuecat:Photographer" + }, + "schema:name": { + "@language": "de", + "@value": "Name" + }, + "schema:url": { + "@type": "schema:URL", + "@value": "https://www.example.com/" + } + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_5197164.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_5197164.json index 79c345b..ee459da 100644 --- a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_5197164.json +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_5197164.json @@ -26,7 +26,8 @@ "http://purl.org/dc/dcmitype/Image" ], "schema:author": { - "@id": "https://thuecat.org/resources/018132452787-ngbe" + "@type": "xsd:string", + "@value": "Florian Trykowski" }, "schema:bitrate": { "@type": "xsd:string", diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_6486108.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_6486108.json index 1242235..d60edfe 100644 --- a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_6486108.json +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/dms_6486108.json @@ -26,7 +26,8 @@ "http://purl.org/dc/dcmitype/Image" ], "schema:author": { - "@id": "https://thuecat.org/resources/018132452787-ngbe" + "@type": "xsd:string", + "@value": "Florian Trykowski" }, "schema:bitrate": { "@type": "xsd:string", diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-and-license-author.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-and-license-author.json new file mode 100644 index 0000000..195103c --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-and-license-author.json @@ -0,0 +1,61 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/image-with-foreign-author", + "@type": [ + "schema:CreativeWork", + "schema:MediaObject", + "schema:Thing", + "schema:ImageObject", + "http://purl.org/dc/dcmitype/Image" + ], + "schema:license": { + "@language": "de", + "@value": "https://creativecommons.org/licenses/by/4.0/" + }, + "schema:name": [ + { + "@language": "de", + "@value": "Bild mit author und license author" + }, + { + "@language": "en", + "@value": "Image with author and license author" + } + ], + "schema:url": { + "@type": "xsd:anyURI", + "@value": "https://cms.thuecat.org/o/adaptive-media/image/5099196/Preview-1280x0/image" + }, + "schema:author": { + "@type": "xsd:string", + "@value": "Full Name" + }, + "thuecat:licenseAuthor": [ + { + "@language": "de", + "@value": "Autor aus Lizenz" + }, + { + "@language": "en", + "@value": "License Author" + } + ] + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-string.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-string.json new file mode 100644 index 0000000..5f7bbef --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-string.json @@ -0,0 +1,51 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/image-with-foreign-author", + "@type": [ + "schema:CreativeWork", + "schema:MediaObject", + "schema:Thing", + "schema:ImageObject", + "http://purl.org/dc/dcmitype/Image" + ], + "schema:author": { + "@type": "xsd:string", + "@value": "Full Name" + }, + "schema:license": { + "@language": "de", + "@value": "https://creativecommons.org/licenses/by/4.0/" + }, + "schema:name": [ + { + "@language": "de", + "@value": "Bild mit author" + }, + { + "@language": "en", + "@value": "Image with author" + } + ], + "schema:url": { + "@type": "xsd:anyURI", + "@value": "https://cms.thuecat.org/o/adaptive-media/image/5099196/Preview-1280x0/image" + } + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-foreign-author.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-foreign-author.json new file mode 100644 index 0000000..4a55e0c --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-foreign-author.json @@ -0,0 +1,46 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/image-with-foreign-author", + "@type": [ + "schema:CreativeWork", + "schema:MediaObject", + "schema:Thing", + "schema:ImageObject", + "http://purl.org/dc/dcmitype/Image" + ], + "schema:author": { + "@id": "https://thuecat.org/resources/author-with-names" + }, + "schema:name": [ + { + "@language": "de", + "@value": "Bild mit externem Autor" + }, + { + "@language": "en", + "@value": "Image with external author" + } + ], + "schema:url": { + "@type": "xsd:anyURI", + "@value": "https://cms.thuecat.org/o/adaptive-media/image/5099196/Preview-1280x0/image" + } + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-license-author.json b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-license-author.json new file mode 100644 index 0000000..51eddf4 --- /dev/null +++ b/Tests/Functional/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-license-author.json @@ -0,0 +1,57 @@ +{ + "@context": { + "cdb": "https://thuecat.org/ontology/cdb/1.0/", + "dachkg": "https://thuecat.org/ontology/dachkg/1.0/", + "dbo": "http://dbpedia.org/ontology/", + "dsv": "http://ontologies.sti-innsbruck.at/dsv/", + "foaf": "http://xmlns.com/foaf/0.1/", + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "http://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "thuecat": "https://thuecat.org/ontology/thuecat/1.0/", + "ttgds": "https://thuecat.org/ontology/ttgds/1.0/", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + "@graph": [ + { + "@id": "https://thuecat.org/resources/image-with-foreign-author", + "@type": [ + "schema:CreativeWork", + "schema:MediaObject", + "schema:Thing", + "schema:ImageObject", + "http://purl.org/dc/dcmitype/Image" + ], + "schema:license": { + "@language": "de", + "@value": "https://creativecommons.org/licenses/by/4.0/" + }, + "schema:name": [ + { + "@language": "de", + "@value": "Bild mit license author" + }, + { + "@language": "en", + "@value": "Image with license author" + } + ], + "schema:url": { + "@type": "xsd:anyURI", + "@value": "https://cms.thuecat.org/o/adaptive-media/image/5099196/Preview-1280x0/image" + }, + "thuecat:licenseAuthor": [ + { + "@language": "de", + "@value": "Autor aus Lizenz" + }, + { + "@language": "en", + "@value": "License Author" + } + ] + } + ] +} diff --git a/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.csv b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.csv new file mode 100644 index 0000000..7d1f60c --- /dev/null +++ b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.csv @@ -0,0 +1,4 @@ +"tx_thuecat_tourist_attraction",,,,, +,"uid","pid","remote_id","title","media" +,1,10,"https://thuecat.org/resources/attraction-with-media","Attraktion mit Bildern","[{""mainImage"":false,""type"":""image"",""title"":""Bild mit externem Autor"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""GivenName FamilyName"",""copyrightYear"":0,""license"":{""type"":"""",""author"":""""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""Full Name"",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit license author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":"""",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""Autor aus Lizenz""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit author und license author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""Full Name"",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""Autor aus Lizenz""}}]" +,2,10,"https://thuecat.org/resources/attraction-with-media","Attraction with media","[{""mainImage"":false,""type"":""image"",""title"":""Bild mit externem Autor"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""GivenName FamilyName"",""copyrightYear"":0,""license"":{""type"":"""",""author"":""""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""Full Name"",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit license author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":"""",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""Autor aus Lizenz""}},{""mainImage"":false,""type"":""image"",""title"":""Bild mit author und license author"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5099196\/Preview-1280x0\/image"",""author"":""Full Name"",""copyrightYear"":0,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""Autor aus Lizenz""}}]" diff --git a/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.xml b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.xml new file mode 100644 index 0000000..d48484f --- /dev/null +++ b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.xml @@ -0,0 +1,76 @@ + + + + 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 + Tourist Attraction + static + + + + + + + 10 + + + + + + + + https://thuecat.org/resources/attraction-with-media + + + + 0 + + + + + + + + ]]> + + diff --git a/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelations.csv b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelations.csv index 76e3c69..67881a0 100644 --- a/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelations.csv +++ b/Tests/Functional/Fixtures/Import/ImportsTouristAttractionsWithRelations.csv @@ -12,12 +12,12 @@ Das Ensemble von Dom und Severikirche bildet eine imposante Kulisse für die jä ,8,10,2,6,6,"\NULL","https://thuecat.org/resources/215230952334-yyno","Pont de l'épicier","Le pont de l’épicier est un des symboles de la ville d’Erfurt, le plus grand pont habité en continu d’Europe. A l’origine, le pont de l’épicier faisait 120 m de long et comptait 62 maisons étroites, qui furent plus tard regroupées en 32 maisons. Sur le pont de l’épicier se trouvent des galeries et des petites échoppes proposant des étoffes à motifs bleu indigo de Thuringe, des céramiques peintes main, du verre de Lauscha, des bijoux et des sculptures en bois.",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}}","[]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Kraemerbruecke-11.jpg"",""description"":""Kr\u00e4merbr\u00fccke in Erfurt"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/134362\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2019,""license"":{""type"":""https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de"",""author"":""https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion""}},{""mainImage"":false,""type"":""image"",""title"":""Erfurt-Kraemerbruecke.jpg"",""description"":""Kr\u00e4merbr\u00fccke in Erfurt"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/134288\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2019,""license"":{""type"":""https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de"",""author"":""https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion""}},{""mainImage"":false,""type"":""image"",""title"":""Erfurt-Kraemerbruecke-13.jpg"",""description"":""Ansicht der Kr\u00e4merbr\u00fccke, Erfurt"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/652340\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2019,""license"":{""type"":""https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.de"",""author"":""https:\/\/home.ttgnet.de\/ttg\/projekte\/10006\/90136\/Projektdokumente\/Vergabe%20Rahmenvertrag%20Fotoproduktion""}}]",,,"ZeroSanitation","Playground,SeatingPossibilitiesRestArea,SouvenirShop,PlayCornerOrPlayArea",,"ZeroInformationArchitecturalStyle","BicycleLockersEnumMem",,"ZeroDigitalOffer","TakingPicturesPermitted","true","true","true","German,English,French","250:MTR","1,4","{}","https://www.erfurt-tourismus.de/sehenswertes/kraemerbruecke" "tx_thuecat_parking_facility",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,"uid","pid","sys_language_uid","l18n_parent","l10n_source","l10n_state","remote_id","title","description","managed_by","address","offers","media","sanitation","other_service","traffic_infrastructure","payment_accepted","distance_to_public_transport",,,,,,,,,,,,,, -,1,10,0,0,0,"\NULL","https://thuecat.org/resources/396420044896-drzt","Parkhaus Domplatz","Das Parkhaus Domplatz befindet sich unmittelbar unterhalb der Zitadelle Petersberg am nördlichen Rand des Domplatzes. Durch die zentrale Lage ist es ein idealer Ausgangspunkt für Stadtbummel und Erkundungen des Zentrums, des Petersbergs und des Andreasviertels.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, -,2,10,1,1,1,"\NULL","https://thuecat.org/resources/396420044896-drzt","Car park Domplatz","The Domplatz multi-storey car park is located directly below the Petersberg Citadel on the northern edge of the Domplatz. Its central location makes it an ideal starting point for strolling through the city and exploring the centre, the Petersberg and the Andreasviertel.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, -,3,10,2,1,1,"\NULL","https://thuecat.org/resources/396420044896-drzt","Parking Domplatz","Le parking à étages de la Domplatz est situé juste en dessous de la citadelle de Petersberg, sur le bord nord de la Domplatz. Son emplacement central en fait un point de départ idéal pour se promener dans la ville et explorer le centre, le Petersberg et l'Andreasviertel.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, -,4,10,0,0,0,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 Parkhaus","Der Q-Park liegt direkt hinter dem Kaufhaus Anger 1 im Erfurter Stadtzentrum und ist über Juri-Gagarin-Ring/Meyfartstraße zu erreichen. Durch die direkte Anbindung an den Stadtring, ist das Parkhaus gut von außerhalb über Schnellstraßen und Autobahnen zu erreichen und befindet sich gleichzeitig im unmittelbaren modernen Zentrum Erfurts.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, -,5,10,1,4,4,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 multi-storey car park","The Q-Park is located directly behind the department store Anger 1 in Erfurt's city centre and can be reached via Juri-Gagarin-Ring/Meyfartstraße.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, -,6,10,2,4,4,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 parking à étages","Le Q-Park est situé directement derrière le grand magasin Anger 1 dans le centre-ville d'Erfurt et peut être atteint par la Juri-Gagarin-Ring/Meyfartstraße.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Erfurt Tourismus und Marketing GmbH"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, +,1,10,0,0,0,"\NULL","https://thuecat.org/resources/396420044896-drzt","Parkhaus Domplatz","Das Parkhaus Domplatz befindet sich unmittelbar unterhalb der Zitadelle Petersberg am nördlichen Rand des Domplatzes. Durch die zentrale Lage ist es ein idealer Ausgangspunkt für Stadtbummel und Erkundungen des Zentrums, des Petersbergs und des Andreasviertels.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, +,2,10,1,1,1,"\NULL","https://thuecat.org/resources/396420044896-drzt","Car park Domplatz","The Domplatz multi-storey car park is located directly below the Petersberg Citadel on the northern edge of the Domplatz. Its central location makes it an ideal starting point for strolling through the city and exploring the centre, the Petersberg and the Andreasviertel.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, +,3,10,2,1,1,"\NULL","https://thuecat.org/resources/396420044896-drzt","Parking Domplatz","Le parking à étages de la Domplatz est situé juste en dessous de la citadelle de Petersberg, sur le bord nord de la Domplatz. Son emplacement central en fait un point de départ idéal pour se promener dans la ville et explorer le centre, le Petersberg et l'Andreasviertel.",1,"{""street"":""Bechtheimer Str. 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""info@stadtwerke-erfurt.de"",""phone"":""+49 361 5640"",""fax"":"""",""geo"":{""latitude"":50.977648905044,""longitude"":11.022127985954299}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":35,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1.5,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":10,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":50,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Erfurt-Parkhaus-Domplatz.jpg"",""description"":"""",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/6486108\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2021,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","ZeroSanitation","ZeroOtherServiceEnumMem","ElectricVehicleCarChargingStationEnumMem",,"240:MTR:CityBus",,,,,,,,,,,,,, +,4,10,0,0,0,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 Parkhaus","Der Q-Park liegt direkt hinter dem Kaufhaus Anger 1 im Erfurter Stadtzentrum und ist über Juri-Gagarin-Ring/Meyfartstraße zu erreichen. Durch die direkte Anbindung an den Stadtring, ist das Parkhaus gut von außerhalb über Schnellstraßen und Autobahnen zu erreichen und befindet sich gleichzeitig im unmittelbaren modernen Zentrum Erfurts.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, +,5,10,1,4,4,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 multi-storey car park","The Q-Park is located directly behind the department store Anger 1 in Erfurt's city centre and can be reached via Juri-Gagarin-Ring/Meyfartstraße.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, +,6,10,2,4,4,"\NULL","https://thuecat.org/resources/440055527204-ocar","Q-Park Anger 1 parking à étages","Le Q-Park est situé directement derrière le grand magasin Anger 1 dans le centre-ville d'Erfurt et peut être atteint par la Juri-Gagarin-Ring/Meyfartstraße.",1,"{""street"":""Anger 1"",""zip"":""99084"",""city"":""Erfurt"",""email"":""servicecenter@q-park.de"",""phone"":""+49 218 18190290"",""fax"":"""",""geo"":{""latitude"":50.977999330565794,""longitude"":11.037503264052475}}","[{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":2.2,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":1,""currency"":""EUR"",""rule"":""PerCar""}]},{""types"":[""ParkingFee""],""title"":"""",""description"":"""",""prices"":[{""title"":"""",""description"":"""",""price"":13,""currency"":""EUR"",""rule"":""PerCar""}]}]","[{""mainImage"":true,""type"":""image"",""title"":""Q-Park-Parkhaus-Anger1-Juri-Gagarin-Ring.JPG"",""description"":""Stra\u00dfenansicht des Parkhauses Q-Park am Kaufhaus Anger 1, schr\u00e4g \u00fcber den Juri-Gagarin-Ring"",""url"":""https:\/\/cms.thuecat.org\/o\/adaptive-media\/image\/5197164\/Preview-1280x0\/image"",""author"":""Florian Trykowski"",""copyrightYear"":2020,""license"":{""type"":""https:\/\/creativecommons.org\/licenses\/by\/4.0\/"",""author"":""""}}]","Toilets","ZeroOtherServiceEnumMem",,,"120:MTR",,,,,,,,,,,,,, "tx_thuecat_organisation",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,"uid","pid","remote_id","title","description",,,,,,,,,,,,,,,,,,,,,,,,,,, ,1,10,"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.01.1998 die Geschäftstätigkeit auf. diff --git a/Tests/Functional/ImportTest.php b/Tests/Functional/ImportTest.php index 742be66..cab73eb 100644 --- a/Tests/Functional/ImportTest.php +++ b/Tests/Functional/ImportTest.php @@ -416,4 +416,25 @@ class ImportTest extends TestCase $this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportWithMultipleReferencesToSameObject.csv'); } + + /** + * @test + */ + public function importsTouristAttractionWithMedia(): void + { + $this->importDataSet(__DIR__ . '/Fixtures/Import/ImportsTouristAttractionWithMedia.xml'); + + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/attraction-with-media.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/018132452787-ngbe.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-foreign-author.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/author-with-names.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-string.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-license-author.json'); + GuzzleClientFaker::appendResponseFromFile(__DIR__ . '/Fixtures/Import/Guzzle/thuecat.org/resources/image-with-author-and-license-author.json'); + + $configuration = $this->get(ImportConfigurationRepository::class)->findByUid(1); + $this->get(Importer::class)->importConfiguration($configuration); + + $this->assertCSVDataSet('EXT:thuecat/Tests/Functional/Fixtures/Import/ImportsTouristAttractionWithMedia.csv'); + } } diff --git a/Tests/Unit/Domain/Import/Typo3Converter/NameExtractorTest.php b/Tests/Unit/Domain/Import/Typo3Converter/NameExtractorTest.php new file mode 100644 index 0000000..50d3e18 --- /dev/null +++ b/Tests/Unit/Domain/Import/Typo3Converter/NameExtractorTest.php @@ -0,0 +1,149 @@ + + * + * 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. + */ + +namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Import\Typo3Converter; + +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use WerkraumMedia\ThueCat\Domain\Import\Entity\Person; +use WerkraumMedia\ThueCat\Domain\Import\Entity\Place; +use WerkraumMedia\ThueCat\Domain\Import\Entity\Properties\ForeignReference; +use WerkraumMedia\ThueCat\Domain\Import\ResolveForeignReference; +use WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\NameExtractor; + +/** + * @covers \WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\NameExtractor + */ +class NameExtractorTest extends TestCase +{ + /** + * @test + */ + public function canBeCreated(): void + { + $resolveForeignReference = $this->createStub(ResolveForeignReference::class); + + $subject = new NameExtractor( + $resolveForeignReference + ); + + self::assertInstanceOf( + NameExtractor::class, + $subject + ); + } + + /** + * @test + */ + public function extractsNameFromString(): void + { + $resolveForeignReference = $this->createStub(ResolveForeignReference::class); + + $subject = new NameExtractor( + $resolveForeignReference + ); + + self::assertSame( + 'Full Name', + $subject->extract('Full Name', 'de') + ); + } + + /** + * @test + */ + public function extractsNameFromForeignReference(): void + { + $place = $this->createStub(Place::class); + $place->method('getName')->willReturn('Full Name'); + $resolveForeignReference = $this->createResolverForObject($place); + + $foreignReference = $this->createStub(ForeignReference::class); + + $subject = new NameExtractor( + $resolveForeignReference + ); + + self::assertSame( + 'Full Name', + $subject->extract($foreignReference, 'de') + ); + } + + /** + * @test + */ + public function extractsCombinedNameFromForeignReference(): void + { + $person = $this->createStub(Person::class); + $person->method('getGivenName')->willReturn('Full'); + $person->method('getFamilyName')->willReturn('Name'); + $resolveForeignReference = $this->createResolverForObject($person); + + $foreignReference = $this->createStub(ForeignReference::class); + + $subject = new NameExtractor( + $resolveForeignReference + ); + + self::assertSame( + 'Full Name', + $subject->extract($foreignReference, 'de') + ); + } + + /** + * @test + */ + public function extractsCombinedNameFromForeignReferenceInsteadOfName(): void + { + $person = $this->createStub(Person::class); + $person->method('getName')->willReturn('Low Priority'); + $person->method('getGivenName')->willReturn('Full'); + $person->method('getFamilyName')->willReturn('Name'); + $resolveForeignReference = $this->createResolverForObject($person); + + $foreignReference = $this->createStub(ForeignReference::class); + + $subject = new NameExtractor( + $resolveForeignReference + ); + + self::assertSame( + 'Full Name', + $subject->extract($foreignReference, 'de') + ); + } + + /** + * @return Stub&ResolveForeignReference + */ + private function createResolverForObject(object $object): Stub + { + $resolveForeignReference = $this->createStub(ResolveForeignReference::class); + $resolveForeignReference->method('resolve')->willReturn($object); + + return $resolveForeignReference; + } +} diff --git a/Tests/Unit/Domain/Model/Frontend/MediaTest.php b/Tests/Unit/Domain/Model/Frontend/MediaTest.php index d9d3846..2e8958c 100644 --- a/Tests/Unit/Domain/Model/Frontend/MediaTest.php +++ b/Tests/Unit/Domain/Model/Frontend/MediaTest.php @@ -101,4 +101,132 @@ class MediaTest extends TestCase ], ], $subject->getExtraImages()); } + + /** + * @test + */ + public function doesNotAddCopyrightAuthorIfItDoesntExist(): void + { + $subject = new Media(json_encode([ + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom-und-Severikirche.jpg', + ], + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom und Severikirche-beleuchtet.jpg', + ], + ]) ?: ''); + + self::assertArrayNotHasKey( + 'copyrightAuthor', + $subject->getExtraImages()[0] + ); + self::assertArrayNotHasKey( + 'copyrightAuthor', + $subject->getExtraImages()[1] + ); + } + + /** + * @test + */ + public function addsCopyrightAuthorFromLicenseAuthor(): void + { + $subject = new Media(json_encode([ + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom-und-Severikirche.jpg', + 'license' => [ + 'author' => 'Full Name 1', + ], + ], + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom und Severikirche-beleuchtet.jpg', + 'license' => [ + 'author' => 'Full Name 2', + ], + ], + ]) ?: ''); + + self::assertSame( + 'Full Name 1', + $subject->getExtraImages()[0]['copyrightAuthor'] + ); + self::assertSame( + 'Full Name 2', + $subject->getExtraImages()[1]['copyrightAuthor'] + ); + } + + /** + * @test + */ + public function addsCopyrightAuthorFromAuthor(): void + { + $subject = new Media(json_encode([ + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom-und-Severikirche.jpg', + 'author' => 'Full Name 1', + ], + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom und Severikirche-beleuchtet.jpg', + 'author' => 'Full Name 2', + ], + ]) ?: ''); + + self::assertSame( + 'Full Name 1', + $subject->getExtraImages()[0]['copyrightAuthor'] + ); + self::assertSame( + 'Full Name 2', + $subject->getExtraImages()[1]['copyrightAuthor'] + ); + } + + /** + * @test + */ + public function addsCopyrightAuthorFromAuthorWithHigherPrio(): void + { + $subject = new Media(json_encode([ + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom-und-Severikirche.jpg', + 'author' => 'Full Name 1', + 'license' => [ + 'author' => 'Full Name 1 license', + ], + ], + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom und Severikirche-beleuchtet.jpg', + 'author' => 'Full Name 2', + 'license' => [ + 'author' => 'Full Name 2 license', + ], + ], + ]) ?: ''); + + self::assertSame( + 'Full Name 1', + $subject->getExtraImages()[0]['copyrightAuthor'] + ); + self::assertSame( + 'Full Name 2', + $subject->getExtraImages()[1]['copyrightAuthor'] + ); + } } diff --git a/ext_emconf.php b/ext_emconf.php index e95ae0e..f3a836a 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -11,7 +11,7 @@ $EM_CONF[$_EXTKEY] = [ 'author' => 'Daniel Siepmann', 'author_email' => 'coding@daniel-siepmann.de', 'author_company' => '', - 'version' => '1.3.0', + 'version' => '1.3.1', 'constraints' => [ 'depends' => [ 'core' => '', diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 723a06f..f2d9eab 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -326,11 +326,11 @@ parameters: - message: "#^Cannot call method findByUid\\(\\) on mixed\\.$#" - count: 12 + count: 13 path: Tests/Functional/ImportTest.php - message: "#^Cannot call method importConfiguration\\(\\) on mixed\\.$#" - count: 12 + count: 13 path: Tests/Functional/ImportTest.php