diff --git a/Api/Data/EntryInterface.php b/Api/Data/EntryInterface.php index da6166147a5abe906385b0b9e1e1297c69dcee58..08d08e867441b9d2ef70208dfd2ed2c2451a74fc 100644 --- a/Api/Data/EntryInterface.php +++ b/Api/Data/EntryInterface.php @@ -8,6 +8,8 @@ interface EntryInterface const KEY = 'key'; const IS_MAINTAINED = 'is_maintained'; const STORES = 'stores'; + const MEDIA_DIRECTORY = 'media_directory'; + const MEDIA_FILES = 'media_files'; /** * @return string @@ -38,4 +40,24 @@ interface EntryInterface * @param array $stores */ public function setStores(array $stores): void; + + /** + * @return string + */ + public function getMediaDirectory(): string; + + /** + * @param string $path + */ + public function setMediaDirectory(string $path): void; + + /** + * @return array + */ + public function getMediaFiles(): array; + + /** + * @param array $files + */ + public function setMediaFiles(array $files): void; } diff --git a/Model/BlockEntry.php b/Model/BlockEntry.php index 0ee51370c3fee2498f166e84f50bf5a3b98ba4dc..7c91c6abf027f809588012212071f13c5fe0399e 100644 --- a/Model/BlockEntry.php +++ b/Model/BlockEntry.php @@ -55,4 +55,36 @@ class BlockEntry extends Block implements BlockEntryInterface { $this->setData(BlockEntryInterface::STORES, $stores); } + + /** + * @return string + */ + public function getMediaDirectory(): string + { + return (string)$this->getData(BlockEntryInterface::MEDIA_DIRECTORY); + } + + /** + * @param string $path + */ + public function setMediaDirectory(string $path): void + { + $this->setData(BlockEntryInterface::MEDIA_DIRECTORY, $path); + } + + /** + * @return array + */ + public function getMediaFiles(): array + { + return (array)$this->getData(BlockEntryInterface::MEDIA_FILES); + } + + /** + * @param array $files + */ + public function setMediaFiles(array $files): void + { + $this->setData(BlockEntryInterface::MEDIA_FILES, $files); + } } diff --git a/Model/Config/Parser/ContentParser.php b/Model/Config/Parser/ContentParser.php index 6a84951a74a038b7dfb4edda148c7b1fe62eb541..7a925823081ea47285dfcc9c4c811fc56ac66a81 100644 --- a/Model/Config/Parser/ContentParser.php +++ b/Model/Config/Parser/ContentParser.php @@ -5,8 +5,10 @@ namespace Firegento\ContentProvisioning\Model\Config\Parser; use DOMElement; use Firegento\ContentProvisioning\Api\ConfigParserInterface; +use Firegento\ContentProvisioning\Api\Data\EntryInterface; use Firegento\ContentProvisioning\Api\Data\PageEntryInterface; use Firegento\ContentProvisioning\Model\Config\Parser\Query\FetchAttributeValue; +use Firegento\ContentProvisioning\Model\Config\Parser\Query\FetchMediaFilesFromContent; use Firegento\ContentProvisioning\Model\Resolver\ContentResolverProvider; use Magento\Framework\Exception\LocalizedException; @@ -27,19 +29,27 @@ class ContentParser implements ConfigParserInterface */ private $arrayKey; + /** + * @var FetchMediaFilesFromContent + */ + private $fetchMediaFilesFromContent; + /** * @param ContentResolverProvider $contentResolverProvider * @param FetchAttributeValue $fetchAttributeValue + * @param FetchMediaFilesFromContent $fetchMediaFilesFromContent * @param string $arrayKey */ public function __construct( ContentResolverProvider $contentResolverProvider, FetchAttributeValue $fetchAttributeValue, + FetchMediaFilesFromContent $fetchMediaFilesFromContent, string $arrayKey = PageEntryInterface::CONTENT ) { $this->contentResolverProvider = $contentResolverProvider; $this->fetchAttributeValue = $fetchAttributeValue; $this->arrayKey = $arrayKey; + $this->fetchMediaFilesFromContent = $fetchMediaFilesFromContent; } /** @@ -52,9 +62,11 @@ class ContentParser implements ConfigParserInterface $contentNode = $element->getElementsByTagName('content')->item(0); $type = $this->fetchAttributeValue->execute($contentNode, 'type', 'plain'); $contentResolver = $this->contentResolverProvider->get($type); + $content = $contentResolver->execute($contentNode); return [ - $this->arrayKey => $contentResolver->execute($contentNode) + $this->arrayKey => $content, + EntryInterface::MEDIA_FILES => $this->fetchMediaFilesFromContent->execute($content) ]; } } diff --git a/Model/Config/Parser/MediaDirectoryParser.php b/Model/Config/Parser/MediaDirectoryParser.php new file mode 100644 index 0000000000000000000000000000000000000000..5e78cb759697233c2ef8ca18428ce7a08bba6313 --- /dev/null +++ b/Model/Config/Parser/MediaDirectoryParser.php @@ -0,0 +1,52 @@ +<?php +declare(strict_types=1); + +namespace Firegento\ContentProvisioning\Model\Config\Parser; + +use DOMElement; +use Firegento\ContentProvisioning\Api\ConfigParserInterface; +use Firegento\ContentProvisioning\Api\Data\EntryInterface; +use Firegento\ContentProvisioning\Model\Config\Parser\Query\FetchChildNodeValue; +use Firegento\ContentProvisioning\Model\Resolver\PathResolver; + +class MediaDirectoryParser implements ConfigParserInterface +{ + /** + * @var FetchChildNodeValue + */ + private $fetchChildNodeValue; + /** + * @var PathResolver + */ + private $pathResolver; + + /** + * @param FetchChildNodeValue $fetchChildNodeValue + * @param PathResolver $pathResolver + */ + public function __construct( + FetchChildNodeValue $fetchChildNodeValue, + PathResolver $pathResolver + ) { + $this->fetchChildNodeValue = $fetchChildNodeValue; + $this->pathResolver = $pathResolver; + } + + /** + * @param DOMElement $element + * @return array + */ + public function execute(DOMElement $element): array + { + $nodeValue = $this->fetchChildNodeValue->execute($element, 'media_directory'); + + $mediaDirectory = null; + if (!empty($nodeValue)) { + $mediaDirectory = $this->pathResolver->execute($nodeValue); + } + + return [ + EntryInterface::MEDIA_DIRECTORY => $mediaDirectory + ]; + } +} diff --git a/Model/Config/Parser/MetaDataParser.php b/Model/Config/Parser/MetaDataParser.php index 5c3fd4bcd8bb78fb1b95ded1aa3ebfbca1f7368a..1b8231a39633a7f4d4e0748da0db22bfde9f0d18 100644 --- a/Model/Config/Parser/MetaDataParser.php +++ b/Model/Config/Parser/MetaDataParser.php @@ -5,6 +5,7 @@ namespace Firegento\ContentProvisioning\Model\Config\Parser; use DOMElement; use Firegento\ContentProvisioning\Api\ConfigParserInterface; +use Firegento\ContentProvisioning\Api\Data\EntryInterface; use Firegento\ContentProvisioning\Api\Data\PageEntryInterface; use Firegento\ContentProvisioning\Model\Config\Parser\Query\FetchAttributeValue; use Firegento\ContentProvisioning\Model\Config\Parser\Query\FetchBooleanAttributeValue; @@ -49,12 +50,12 @@ class MetaDataParser implements ConfigParserInterface public function execute(DOMElement $element): array { return [ - PageEntryInterface::KEY => $this->fetchAttributeValue->execute($element, 'key'), + EntryInterface::KEY => $this->fetchAttributeValue->execute($element, 'key'), PageEntryInterface::IDENTIFIER => $this->fetchAttributeValue->execute($element, 'identifier'), PageEntryInterface::TITLE => $this->fetchChildNodeValue->execute($element, 'title'), PageEntryInterface::IS_ACTIVE => $this->fetchBooleanAttributeValue->execute($element, 'active', 'false'), - PageEntryInterface::IS_MAINTAINED => + EntryInterface::IS_MAINTAINED => $this->fetchBooleanAttributeValue->execute($element, 'maintained', 'false') ]; } diff --git a/Model/Config/Parser/Query/FetchMediaFilesFromContent.php b/Model/Config/Parser/Query/FetchMediaFilesFromContent.php new file mode 100644 index 0000000000000000000000000000000000000000..857b9e38c519a17797bd2c62e82cb9972a9a8f26 --- /dev/null +++ b/Model/Config/Parser/Query/FetchMediaFilesFromContent.php @@ -0,0 +1,22 @@ +<?php +declare(strict_types=1); + +namespace Firegento\ContentProvisioning\Model\Config\Parser\Query; + +use Magento\Framework\Exception\LocalizedException; + +class FetchMediaFilesFromContent +{ + /** + * @param string $content + * @return array + * @throws LocalizedException + */ + public function execute(string $content): array + { + if (preg_match_all('/\{\{media url=\"\;(?P<path>.*?)\"\;\}\}/', $content, $matches)) { + return $matches['path']; + } + return []; + } +} diff --git a/Model/Console/PageListCommand.php b/Model/Console/PageListCommand.php index 16c5a52b389f906eab737bc9a7e62a5f8fafe1dd..d4e95a9b0ae86ff9a01c7711d5fa77faee2e2940 100644 --- a/Model/Console/PageListCommand.php +++ b/Model/Console/PageListCommand.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace Firegento\ContentProvisioning\Model\Console; +use Firegento\ContentProvisioning\Api\Data\EntryInterface; use Firegento\ContentProvisioning\Api\Data\PageEntryInterface; use Firegento\ContentProvisioning\Model\Query\GetPageEntryList\Proxy as GetPageEntryList; use Firegento\ContentProvisioning\Model\Query\GetPagesByPageEntry\Proxy as GetPagesByPageEntry; @@ -47,6 +48,7 @@ class PageListCommand extends Command $table = new Table($output); $table->setHeaders(['Key', 'Identifier', 'Stores', 'Maintained', 'Active', 'Title', 'in DB (IDs)']); + /** @var EntryInterface $entry */ foreach ($this->getAllContentEntries->get() as $entry) { $table->addRow([ $entry->getKey(), @@ -55,7 +57,7 @@ class PageListCommand extends Command $entry->isMaintained() ? 'yes' : 'no', $entry->isActive() ? 'yes' : 'no', $entry->getTitle(), - $this->getExistsInDbValue($entry) + $this->getExistsInDbValue($entry), ]); } diff --git a/Model/PageEntry.php b/Model/PageEntry.php index 7f44ee30ad43463b5809f09d1bd93b80149c4829..7824ec6c1f38eb682e888d5a410ef1928c255849 100644 --- a/Model/PageEntry.php +++ b/Model/PageEntry.php @@ -55,4 +55,36 @@ class PageEntry extends Page implements PageEntryInterface { $this->setData(PageEntryInterface::STORES, $stores); } -} \ No newline at end of file + + /** + * @return string + */ + public function getMediaDirectory(): string + { + return (string)$this->getData(PageEntryInterface::MEDIA_DIRECTORY); + } + + /** + * @param string $path + */ + public function setMediaDirectory(string $path): void + { + $this->setData(PageEntryInterface::MEDIA_DIRECTORY, $path); + } + + /** + * @return array + */ + public function getMediaFiles(): array + { + return (array)$this->getData(PageEntryInterface::MEDIA_FILES); + } + + /** + * @param array $files + */ + public function setMediaFiles(array $files): void + { + $this->setData(PageEntryInterface::MEDIA_FILES, $files); + } +} diff --git a/Model/Resolver/FileContentResolver.php b/Model/Resolver/FileContentResolver.php index 57da446840e28946f74ebf4772ad00dd40986262..709b5558dcb22f7a770bdbc9a90bdcab84f86fea 100644 --- a/Model/Resolver/FileContentResolver.php +++ b/Model/Resolver/FileContentResolver.php @@ -12,25 +12,17 @@ use Magento\Framework\Module\Dir\Reader; class FileContentResolver implements ContentResolverInterface { /** - * @var Reader + * @var PathResolver */ - private $moduleReader; + private $pathResolver; /** - * @var DirectoryList - */ - private $directoryList; - - /** - * @param Reader $moduleReader - * @param DirectoryList $directoryList + * @param PathResolver $pathResolver */ public function __construct( - Reader $moduleReader, - DirectoryList $directoryList + PathResolver $pathResolver ) { - $this->moduleReader = $moduleReader; - $this->directoryList = $directoryList; + $this->pathResolver = $pathResolver; } /** @@ -40,34 +32,11 @@ class FileContentResolver implements ContentResolverInterface */ public function execute(DOMElement $node): string { - $path = $this->resolvePath((string)$node->textContent); + $path = $this->pathResolver->execute((string)$node->textContent); if (!is_file($path)) { throw new LocalizedException(__('Given content file %file does not exists.', ['file' => $path])); } return file_get_contents($path); } - - /** - * @param string $path - * @return string - */ - private function resolvePath(string $path): string - { - if (strpos($path, '::') !== false) { - $pathParts = explode('::', $path, 2); - $moduleName = $pathParts[0]; - $filePath = $pathParts[1]; - $moduleDirectory = $this->moduleReader->getModuleDir('', $moduleName); - return implode(DIRECTORY_SEPARATOR, [ - $moduleDirectory, - $filePath - ]); - } else { - return implode(DIRECTORY_SEPARATOR, [ - $this->directoryList->getRoot(), - $path - ]); - } - } } \ No newline at end of file diff --git a/Model/Resolver/PathResolver.php b/Model/Resolver/PathResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..8d67b803d524dd8fc164a47d83d515d4be9ad1a1 --- /dev/null +++ b/Model/Resolver/PathResolver.php @@ -0,0 +1,58 @@ +<?php +declare(strict_types=1); + +namespace Firegento\ContentProvisioning\Model\Resolver; + +use DOMElement; +use Firegento\ContentProvisioning\Api\ContentResolverInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Filesystem\DirectoryList; +use Magento\Framework\Module\Dir\Reader; + +class PathResolver +{ + /** + * @var Reader + */ + private $moduleReader; + + /** + * @var DirectoryList + */ + private $directoryList; + + /** + * @param Reader $moduleReader + * @param DirectoryList $directoryList + */ + public function __construct( + Reader $moduleReader, + DirectoryList $directoryList + ) { + $this->moduleReader = $moduleReader; + $this->directoryList = $directoryList; + } + + /** + * @param string $path + * @return string + */ + public function execute(string $path): string + { + if (strpos($path, '::') !== false) { + $pathParts = explode('::', $path, 2); + $moduleName = $pathParts[0]; + $filePath = $pathParts[1]; + $moduleDirectory = $this->moduleReader->getModuleDir('', $moduleName); + return implode(DIRECTORY_SEPARATOR, [ + $moduleDirectory, + $filePath + ]); + } else { + return implode(DIRECTORY_SEPARATOR, [ + $this->directoryList->getRoot(), + $path + ]); + } + } +} \ No newline at end of file diff --git a/Test/Integration/Model/Config/_files/content_provisioning.xml b/Test/Integration/Model/Config/_files/content_provisioning.xml index 2617f11d02e43cb1e18103656d28e871eebc05da..4affb24378506f3e87796636e967f4ba2b53b11a 100644 --- a/Test/Integration/Model/Config/_files/content_provisioning.xml +++ b/Test/Integration/Model/Config/_files/content_provisioning.xml @@ -27,6 +27,11 @@ <theme_id>3</theme_id> </custom_design> </page> + <page key="test.page.3" identifier="test-page-3" active="true" maintained="true"> + <title>Page With Images</title> + <content type="file">Firegento_ContentProvisioning::Test/Integration/Model/Config/_files/test-files/content-with-images-1.html</content> + <media_directory>Firegento_ContentProvisioning::Test/Integration/Model/Config/_files/test-files/media</media_directory> + </page> <block key="test.block.1" identifier="test-block-1" maintained="true" active="true"> <title>Test Block 1</title> <content><![CDATA[<h2>test foobar Aenean commodo ligula eget dolor aenean massa</h2>]]></content> @@ -39,4 +44,9 @@ <store code="admin" /> </stores> </block> + <block key="test.block.3" identifier="test-block-3" maintained="true" active="true"> + <title>Block With Images</title> + <content type="file">Firegento_ContentProvisioning::Test/Integration/Model/Config/_files/test-files/content-with-images-1.html</content> + <media_directory>Firegento_ContentProvisioning::Test/Integration/Model/Config/_files/test-files/media</media_directory> + </block> </config> \ No newline at end of file diff --git a/Test/Integration/Model/Config/_files/result.php b/Test/Integration/Model/Config/_files/result.php index cef5050908eca6069c4030143323f0f5f425e5fd..5f2fdf33fdae32d34998ef0f4da79b80ce3e7074 100644 --- a/Test/Integration/Model/Config/_files/result.php +++ b/Test/Integration/Model/Config/_files/result.php @@ -14,6 +14,8 @@ return [ PageEntryInterface::IS_MAINTAINED => true, PageEntryInterface::STORES => ['admin'], PageEntryInterface::CONTENT_HEADING => '', + PageEntryInterface::MEDIA_DIRECTORY => null, + PageEntryInterface::MEDIA_FILES => [], ], 'test.page.2' => [ PageEntryInterface::TITLE => 'Title 2', @@ -33,6 +35,24 @@ return [ PageEntryInterface::CUSTOM_THEME_TO => '2019-03-29', PageEntryInterface::CUSTOM_THEME => '3', PageEntryInterface::CUSTOM_ROOT_TEMPLATE => '3columns', + PageEntryInterface::MEDIA_DIRECTORY => null, + PageEntryInterface::MEDIA_FILES => [], + ], + 'test.page.3' => [ + PageEntryInterface::TITLE => 'Page With Images', + PageEntryInterface::CONTENT => file_get_contents(__DIR__ . '/test-files/content-with-images-1.html'), + PageEntryInterface::KEY => 'test.page.3', + PageEntryInterface::IDENTIFIER => 'test-page-3', + PageEntryInterface::IS_ACTIVE => true, + PageEntryInterface::IS_MAINTAINED => true, + PageEntryInterface::STORES => ['admin'], + PageEntryInterface::CONTENT_HEADING => '', + PageEntryInterface::MEDIA_DIRECTORY => __DIR__ . '/test-files/media', + PageEntryInterface::MEDIA_FILES => [ + 'image-1.png', + 'some-test-image.png', + 'foobar/test.png', + ], ] ], 'blocks' => [ @@ -44,6 +64,8 @@ return [ BlockEntryInterface::IS_ACTIVE => true, BlockEntryInterface::IS_MAINTAINED => true, BlockEntryInterface::STORES => ['admin'], + BlockEntryInterface::MEDIA_DIRECTORY => null, + BlockEntryInterface::MEDIA_FILES => [], ], 'test.block.2' => [ BlockEntryInterface::TITLE => 'Test Block 2', @@ -53,6 +75,23 @@ return [ BlockEntryInterface::IS_ACTIVE => true, BlockEntryInterface::IS_MAINTAINED => false, BlockEntryInterface::STORES => ['default', 'admin'], + BlockEntryInterface::MEDIA_DIRECTORY => null, + BlockEntryInterface::MEDIA_FILES => [], + ], + 'test.block.3' => [ + BlockEntryInterface::TITLE => 'Block With Images', + BlockEntryInterface::CONTENT => file_get_contents(__DIR__ . '/test-files/content-with-images-1.html'), + BlockEntryInterface::KEY => 'test.block.3', + BlockEntryInterface::IDENTIFIER => 'test-block-3', + BlockEntryInterface::IS_ACTIVE => true, + BlockEntryInterface::IS_MAINTAINED => true, + BlockEntryInterface::STORES => ['admin'], + BlockEntryInterface::MEDIA_DIRECTORY => __DIR__ . '/test-files/media', + BlockEntryInterface::MEDIA_FILES => [ + 'image-1.png', + 'some-test-image.png', + 'foobar/test.png', + ], ], ], ]; diff --git a/Test/Integration/Model/Config/_files/test-files/content-with-images-1.html b/Test/Integration/Model/Config/_files/test-files/content-with-images-1.html new file mode 100644 index 0000000000000000000000000000000000000000..dc3c06d323aca666c6b1da39c36bdf930ab9d321 --- /dev/null +++ b/Test/Integration/Model/Config/_files/test-files/content-with-images-1.html @@ -0,0 +1,5 @@ +<h5>Some foobar</h5> +<p>Foobar: <a href="{{media url="image-1.png"}}">Link zu einem Bilder</a></p> +<p>Dummy content...</p> +<p>Image: <img src="{{media url="some-test-image.png"}}" alt="fooo" width="300"></p> +<p>Image 2: <img src="{{media url="foobar/test.png"}}" alt="" width="450" height="150"></p> \ No newline at end of file diff --git a/Test/Integration/Model/Config/_files/test-files/media/foobar/test.png b/Test/Integration/Model/Config/_files/test-files/media/foobar/test.png new file mode 100644 index 0000000000000000000000000000000000000000..9a5de74baa865b70141a0e4c352624393ad82e90 Binary files /dev/null and b/Test/Integration/Model/Config/_files/test-files/media/foobar/test.png differ diff --git a/Test/Integration/Model/Config/_files/test-files/media/image-1.png b/Test/Integration/Model/Config/_files/test-files/media/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2393ef78684f0511452736ab326b5a1ec37daa Binary files /dev/null and b/Test/Integration/Model/Config/_files/test-files/media/image-1.png differ diff --git a/Test/Integration/Model/Config/_files/test-files/media/some-test-image.png b/Test/Integration/Model/Config/_files/test-files/media/some-test-image.png new file mode 100644 index 0000000000000000000000000000000000000000..aadbb12a05c244448ab297f7763e68ea509b0449 Binary files /dev/null and b/Test/Integration/Model/Config/_files/test-files/media/some-test-image.png differ diff --git a/etc/content_provisioning.xsd b/etc/content_provisioning.xsd index 923dfaf6347b8611111f97527f2420f7b8c989b2..c755cb04bc64dc4d899b3fba229027213250085c 100644 --- a/etc/content_provisioning.xsd +++ b/etc/content_provisioning.xsd @@ -30,6 +30,13 @@ </xs:restriction> </xs:simpleType> </xs:element> + <xs:element minOccurs="0" maxOccurs="1" name="media_directory"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="3"/> + </xs:restriction> + </xs:simpleType> + </xs:element> <xs:element minOccurs="0" maxOccurs="1" type="stores" name="stores" /> <xs:element minOccurs="0" maxOccurs="1" type="seo" name="seo" /> <xs:element minOccurs="0" maxOccurs="1" type="design" name="design" /> @@ -69,6 +76,13 @@ <xs:choice maxOccurs="unbounded"> <xs:element minOccurs="0" maxOccurs="1" type="stores" name="stores" /> <xs:element minOccurs="1" maxOccurs="1" type="content" name="content" /> + <xs:element minOccurs="0" maxOccurs="1" name="media_directory"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="3"/> + </xs:restriction> + </xs:simpleType> + </xs:element> <xs:element minOccurs="1" maxOccurs="1" type="xs:string" name="title" /> </xs:choice> <xs:attribute name="key" type="key" use="required" /> diff --git a/etc/di.xml b/etc/di.xml index cfc4541397239df5f3f9b49cc41be671e048f842..b53e8cbde188a8a2ec2b8a01c817611db009026d 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -20,6 +20,7 @@ <item name="seo" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\SeoParser</item> <item name="design" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\DesignParser</item> <item name="custom_design" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\CustomDesignParser</item> + <item name="media_directory" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\MediaDirectoryParser</item> </argument> </arguments> </virtualType> @@ -35,6 +36,7 @@ <item name="meta" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\MetaDataParser</item> <item name="stores" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\StoresParser</item> <item name="content" xsi:type="object">Firegento\ContentProvisioning\Virtual\Config\Parser\BlockContentParser</item> + <item name="media_directory" xsi:type="object">Firegento\ContentProvisioning\Model\Config\Parser\MediaDirectoryParser</item> </argument> </arguments> </virtualType>