diff --git a/Classes/Wwwision/AssetConstraints/Aspects/ContentControllerAspect.php b/Classes/Aspects/ContentControllerAspect.php similarity index 64% rename from Classes/Wwwision/AssetConstraints/Aspects/ContentControllerAspect.php rename to Classes/Aspects/ContentControllerAspect.php index ca83f84..c82dc58 100644 --- a/Classes/Wwwision/AssetConstraints/Aspects/ContentControllerAspect.php +++ b/Classes/Aspects/ContentControllerAspect.php @@ -1,32 +1,30 @@ uploadAssetAction())") + * @Flow\Before("method(Neos\Neos\Controller\Backend\ContentController->uploadAssetAction())") * @param JoinPointInterface $joinPoint The current join point * @return void */ public function rewriteSiteAssetCollection(JoinPointInterface $joinPoint) { - if ($this->lookupNodeFilter === NULL || $this->lookupPropertyName === NULL) { + if ($this->lookupNodeFilter === null || $this->lookupPropertyName === null) { return; } /** @var ContentController $contentController */ $contentController = $joinPoint->getProxy(); /** @var ActionRequest $actionRequest */ - $actionRequest = ObjectAccess::getProperty($contentController, 'request', TRUE); + $actionRequest = ObjectAccess::getProperty($contentController, 'request', true); $nodeContextPath = $actionRequest->getInternalArgument('__node'); - if ($nodeContextPath === NULL) { + if ($nodeContextPath === null) { return; } $node = $this->propertyMapper->convert($nodeContextPath, NodeInterface::class); - $flowQuery = new FlowQuery(array($node)); + $flowQuery = new FlowQuery([$node]); /** @var NodeInterface $documentNode */ $documentNode = $flowQuery->closest($this->lookupNodeFilter)->get(0); @@ -83,7 +81,7 @@ public function rewriteSiteAssetCollection(JoinPointInterface $joinPoint) } /** @var AssetCollection $assetCollection */ $assetCollection = $this->assetCollectionRepository->findByIdentifier($documentNode->getProperty($this->lookupPropertyName)); - if ($assetCollection === NULL) { + if ($assetCollection === null) { return; } @@ -92,5 +90,4 @@ public function rewriteSiteAssetCollection(JoinPointInterface $joinPoint) $assetCollection->addAsset($asset); $this->assetCollectionRepository->update($assetCollection); } - -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/DataSource/AssetCollectionDataSource.php b/Classes/DataSource/AssetCollectionDataSource.php similarity index 79% rename from Classes/Wwwision/AssetConstraints/DataSource/AssetCollectionDataSource.php rename to Classes/DataSource/AssetCollectionDataSource.php index e806099..29c849a 100644 --- a/Classes/Wwwision/AssetConstraints/DataSource/AssetCollectionDataSource.php +++ b/Classes/DataSource/AssetCollectionDataSource.php @@ -1,12 +1,12 @@ assetCollectionRepository->findAll() as $assetCollection) { $assetCollections[] = ['value' => $this->persistenceManager->getIdentifierByObject($assetCollection), 'label' => $assetCollection->getTitle()]; } + return $assetCollections; } } diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php similarity index 67% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php index 50b4c5d..1da36c0 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/AssetAssetCollectionConditionGenerator.php @@ -4,16 +4,16 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; -use TYPO3\Flow\Annotations as Flow; -use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\PropertyConditionGenerator; -use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\PropertyConditionGenerator; +use Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; /** - * Condition generator covering Asset <-> AssetCollection relations (M:M relations are not supported by the Flow PropertyConditionGenerator yet) + * Condition generator covering Asset <-> AssetCollection relations (M:M relations are not supported by the Flow + * PropertyConditionGenerator yet) */ class AssetAssetCollectionConditionGenerator implements SqlGeneratorInterface { - /** * @Flow\Inject * @var ObjectManager @@ -44,11 +44,12 @@ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity $propertyConditionGenerator = new PropertyConditionGenerator(''); $collectionTitle = $propertyConditionGenerator->getValueForOperand($this->collectionTitle); $quotedCollectionTitle = $this->entityManager->getConnection()->quote($collectionTitle); + return $targetTableAlias . '.persistence_object_identifier IN ( SELECT ' . $targetTableAlias . '_a.persistence_object_identifier - FROM typo3_media_domain_model_asset AS ' . $targetTableAlias . '_a - LEFT JOIN typo3_media_domain_model_assetcollection_assets_join ' . $targetTableAlias . '_acj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_asset - LEFT JOIN typo3_media_domain_model_assetcollection ' . $targetTableAlias . '_ac ON ' . $targetTableAlias . '_ac.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_assetcollection + FROM neos_media_domain_model_asset AS ' . $targetTableAlias . '_a + LEFT JOIN neos_media_domain_model_assetcollection_assets_join ' . $targetTableAlias . '_acj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_asset + LEFT JOIN neos_media_domain_model_assetcollection ' . $targetTableAlias . '_ac ON ' . $targetTableAlias . '_ac.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_assetcollection WHERE ' . $targetTableAlias . '_ac.title = ' . $quotedCollectionTitle . ')'; } -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php similarity index 73% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php index 28cc045..31ecfaf 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/AssetCollectionConditionGenerator.php @@ -1,18 +1,16 @@ equals($collectionTitle); } - -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php similarity index 84% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php index 9e7c6a4..1e7c221 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/AssetConditionGenerator.php @@ -1,18 +1,16 @@ like($term . '%'); } @@ -45,6 +44,7 @@ public function titleStartsWith($term) public function hasMediaType($mediaType) { $propertyConditionGenerator = new PropertyConditionGenerator('resource.mediaType'); + return $propertyConditionGenerator->equals($mediaType); } @@ -73,5 +73,4 @@ public function isWithoutCollection() { return new AssetWithoutAssetCollectionConditionGenerator(); } - -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php similarity index 67% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php index 02f23d0..867aa22 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/AssetTagConditionGenerator.php @@ -4,16 +4,16 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; -use TYPO3\Flow\Annotations as Flow; -use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\PropertyConditionGenerator; -use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\PropertyConditionGenerator; +use Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; /** - * Condition generator covering Asset <-> Tag relations (M:M relations are not supported by the Flow PropertyConditionGenerator yet) + * Condition generator covering Asset <-> Tag relations (M:M relations are not supported by the Flow + * PropertyConditionGenerator yet) */ class AssetTagConditionGenerator implements SqlGeneratorInterface { - /** * @Flow\Inject * @var ObjectManager @@ -44,11 +44,12 @@ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity $propertyConditionGenerator = new PropertyConditionGenerator(''); $tagLabel = $propertyConditionGenerator->getValueForOperand($this->tagLabel); $quotedTagLabel = $this->entityManager->getConnection()->quote($tagLabel); + return $targetTableAlias . '.persistence_object_identifier IN ( SELECT ' . $targetTableAlias . '_a.persistence_object_identifier - FROM typo3_media_domain_model_asset AS ' . $targetTableAlias . '_a - LEFT JOIN typo3_media_domain_model_asset_tags_join ' . $targetTableAlias . '_atj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_atj.media_asset - LEFT JOIN typo3_media_domain_model_tag ' . $targetTableAlias . '_t ON ' . $targetTableAlias . '_t.persistence_object_identifier = ' . $targetTableAlias . '_atj.media_tag + FROM neos_media_domain_model_asset AS ' . $targetTableAlias . '_a + LEFT JOIN neos_media_domain_model_asset_tags_join ' . $targetTableAlias . '_atj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_atj.media_asset + LEFT JOIN neos_media_domain_model_tag ' . $targetTableAlias . '_t ON ' . $targetTableAlias . '_t.persistence_object_identifier = ' . $targetTableAlias . '_atj.media_tag WHERE ' . $targetTableAlias . '_t.label = ' . $quotedTagLabel . ')'; } -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php similarity index 69% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php index 1007a35..ed02909 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/AssetWithoutAssetCollectionConditionGenerator.php @@ -4,15 +4,15 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Query\Filter\SQLFilter as DoctrineSqlFilter; -use TYPO3\Flow\Annotations as Flow; -use TYPO3\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Security\Authorization\Privilege\Entity\Doctrine\SqlGeneratorInterface; /** - * Condition generator covering Asset >-< AssetCollection relations (M:M relations are not supported by the Flow PropertyConditionGenerator yet) + * Condition generator covering Asset >-< AssetCollection relations (M:M relations are not supported by the Flow + * PropertyConditionGenerator yet) */ class AssetWithoutAssetCollectionConditionGenerator implements SqlGeneratorInterface { - /** * @Flow\Inject * @var ObjectManager @@ -29,9 +29,10 @@ public function getSql(DoctrineSqlFilter $sqlFilter, ClassMetadata $targetEntity { $sql = $targetTableAlias . '.persistence_object_identifier IN ( SELECT ' . $targetTableAlias . '_a.persistence_object_identifier - FROM typo3_media_domain_model_asset AS ' . $targetTableAlias . '_a - LEFT JOIN typo3_media_domain_model_assetcollection_assets_join ' . $targetTableAlias . '_acj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_asset + FROM neos_media_domain_model_asset AS ' . $targetTableAlias . '_a + LEFT JOIN neos_media_domain_model_assetcollection_assets_join ' . $targetTableAlias . '_acj ON ' . $targetTableAlias . '_a.persistence_object_identifier = ' . $targetTableAlias . '_acj.media_asset WHERE ' . $targetTableAlias . '_acj.media_asset IS NULL)'; + return $sql; } -} \ No newline at end of file +} diff --git a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php b/Classes/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php similarity index 72% rename from Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php rename to Classes/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php index d779816..c96278a 100644 --- a/Classes/Wwwision/AssetConstraints/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php +++ b/Classes/Security/Authorization/Privilege/Doctrine/TagConditionGenerator.php @@ -1,18 +1,16 @@ equals($tagLabel); } - -} \ No newline at end of file +} diff --git a/Classes/Security/Authorization/Privilege/ReadAssetCollectionPrivilege.php b/Classes/Security/Authorization/Privilege/ReadAssetCollectionPrivilege.php new file mode 100644 index 0000000..a202782 --- /dev/null +++ b/Classes/Security/Authorization/Privilege/ReadAssetCollectionPrivilege.php @@ -0,0 +1,29 @@ +': + 'array': editor: 'Wwwision.AssetConstraints/Editors/AssetEditor' # copy editor settings from the original editors editors: 'Wwwision.AssetConstraints/Editors/ImageEditor': editorOptions: - fileChooserLabel: 'TYPO3.Neos:Main:choose' + fileChooserLabel: ' Neos.Neos:Main:choose' 'Wwwision.AssetConstraints/Editors/AssetEditor': editorOptions: - fileChooserLabel: 'TYPO3.Neos:Main:choose' + fileChooserLabel: 'Neos.Neos:Main:choose' Wwwision: AssetConstraints: - # configuration for the ContentControllerAspect that sets asset collection by looking up the closest node matching "nodeFilter" and checking its property with the name "propertyName" + # configuration for the ContentControllerAspect that sets asset collection by looking up the closest node matching + # "nodeFilter" and checking its property with the name "propertyName" nodeLookup: - nodeFilter: '[instanceof TYPO3.Neos:Document]' + nodeFilter: '[instanceof Neos.Neos:Document]' propertyName: 'assetCollection' \ No newline at end of file diff --git a/README.md b/README.md index 62ea650..59b55e8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Wwwision.AssetConstraints -Simple package to constraint access to TYPO3.Media assets based on tags, content type or asset collection + +Simple package to constraint access to Neos.Media assets based on tags, content type or asset collection **NOTE:** This package is in an **experimental** state at the moment ## Usage -1. Drop package into your (TYPO3 Neos) installation +1. Drop package into your (Neos) installation 2. Add policies to your main package `Policy.yaml` 3. Adjust `Settings` and `NodeTypes` configuration to your needs @@ -16,6 +17,7 @@ Simple package to constraint access to TYPO3.Media assets based on tags, content This package comes with Entity Privileges allowing to restrict reading of `Assets` based on several attributes: #### Restrict read access to `Assets` based on their *media type* + *Policy.yaml:* ```yaml privilegeTargets: @@ -25,6 +27,7 @@ privilegeTargets: ``` #### Restrict read access to `Assets` based on *Tag* + *Policy.yaml:* ```yaml privilegeTargets: @@ -34,6 +37,7 @@ privilegeTargets: ``` #### Restrict read access to `Assets` based on *Asset Collection* + *Policy.yaml:* ```yaml privilegeTargets: @@ -43,6 +47,7 @@ privilegeTargets: ``` Of course you can combine the three matchers like: + ```yaml privilegeTargets: 'Wwwision\AssetConstraints\Security\Authorization\Privilege\ReadAssetPrivilege': @@ -51,6 +56,7 @@ privilegeTargets: ``` #### Restrict read access to `Tags` based on *Tag label* + *Policy.yaml:* ```yaml privilegeTargets: @@ -60,6 +66,7 @@ privilegeTargets: ``` #### Restrict read access to `Asset Collections` based on *Collection title* + *Policy.yaml:* ```yaml privilegeTargets: @@ -72,12 +79,14 @@ privilegeTargets: When uploading new `Assets` using the Neos inspector, they will be added to the current site's default `Asset Collection` if one is configured in the *Sites Management module*. + Unfortunately this mechanism is not (yet) flexible enough to set the collection based on other characteristics (the currently selected node for example). This package therefore adds two specialized Inspector editors for Asset/Image uploads that send the current node along with the upload-data to the server. Besides it hooks into the asset creation process (via AOP) to add the uploaded -`Asset` to an `Asset Collection` based on the current node: +`Asset` to an `Asset Collection` based on the current node. + The default behavior is to grab the closest document node, evaluate it's "assetCollection" and adds the Asset to that collection if it succeeded. @@ -87,7 +96,7 @@ This package also comes with a `DataSource` to allow for selecting the `AssetCol *NodeTypes.yaml:* ```yaml -'TYPO3.Neos:Document': +'Neos.Neos:Document': ui: inspector: groups: @@ -102,7 +111,7 @@ This package also comes with a `DataSource` to allow for selecting the `AssetCol editor: 'Content/Inspector/Editors/SelectBoxEditor' editorOptions: dataSourceIdentifier: 'wwwision-assetconstraints-assetcollections' - allowEmpty: TRUE + allowEmpty: true placeholder: 'Asset Collection for uploads' ``` @@ -112,7 +121,7 @@ specific node type such as `Your.Package:Page`. #### Adjusting the behavior of the AOP aspect: As mentioned above, the default behavior of the AOP aspect is to check for a property called "assetCollection" in the -closest `TYPO3.Neos:Document` node of the node the asset was uploaded to. +closest `Neos.Neos:Document` node of the node the asset was uploaded to. This can be adjusted via Settings. Imagine you have a custom node type `Your.Package:MainPage` that contains the target assetCollection in a property "collection": @@ -128,8 +137,11 @@ Wwwision: ## Example Policy -Given you have three "groups" and corresponding roles `Some.Package:Group1Editor`, `Some.Package:Group2Editor` and `Some.Package:Group3Editor` as well as an administrative role ``Some.Package:Administrator`. -Now, if you have three "Asset Collections" named `group1`, `group2` and `group3` the following `Policy.yaml` would restrict editors to only see collections and assets corresponding to their role: +Given you have three "groups" and corresponding roles `Some.Package:Group1Editor`, `Some.Package:Group2Editor` and +`Some.Package:Group3Editor` as well as an administrative role ``Some.Package:Administrator`. + +Now, if you have three "Asset Collections" named `group1`, `group2` and `group3` the following `Policy.yaml` would +restrict editors to only see collections and assets corresponding to their role: ```yaml privilegeTargets: diff --git a/composer.json b/composer.json index ec9e3e8..c3ab2b4 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "wwwision/assetconstraints", - "type": "typo3-flow-package", - "description": "Simple package to constraint access to TYPO3.Media assets based on tags, content type or asset collection", + "type": "neos-package", + "description": "Simple package to constraint access to Neos.Media assets based on tags, content type or asset collection", "license": "GPL-3.0+", "authors": [ { @@ -10,14 +10,89 @@ } ], "require": { - "typo3/media": "~2.0" + "neos/media": "~3.0" }, "suggest": { - "typo3/neos": "To use this with the Neos CMS" + "neos/neos": "To use this with the Neos CMS" }, "autoload": { - "psr-0": { - "Wwwision\\AssetConstraints": "Classes" + "psr-4": { + "Wwwision\\AssetConstraints\\": "Classes" } + }, + "extra": { + "applied-flow-migrations": [ + "TYPO3.FLOW3-201201261636", + "TYPO3.Fluid-201205031303", + "TYPO3.FLOW3-201205292145", + "TYPO3.FLOW3-201206271128", + "TYPO3.FLOW3-201209201112", + "TYPO3.Flow-201209251426", + "TYPO3.Flow-201211151101", + "TYPO3.Flow-201212051340", + "TYPO3.TypoScript-130516234520", + "TYPO3.TypoScript-130516235550", + "TYPO3.TYPO3CR-130523180140", + "TYPO3.Neos.NodeTypes-201309111655", + "TYPO3.Flow-201310031523", + "TYPO3.Flow-201405111147", + "TYPO3.Neos-201407061038", + "TYPO3.Neos-201409071922", + "TYPO3.TYPO3CR-140911160326", + "TYPO3.Neos-201410010000", + "TYPO3.TYPO3CR-141101082142", + "TYPO3.Neos-20141113115300", + "TYPO3.Fluid-20141113120800", + "TYPO3.Flow-20141113121400", + "TYPO3.Fluid-20141121091700", + "TYPO3.Neos-20141218134700", + "TYPO3.Fluid-20150214130800", + "TYPO3.Neos-20150303231600", + "TYPO3.TYPO3CR-20150510103823", + "TYPO3.Flow-20151113161300", + "TYPO3.Form-20160601101500", + "TYPO3.Flow-20161115140400", + "TYPO3.Flow-20161115140430", + "Neos.Flow-20161124204700", + "Neos.Flow-20161124204701", + "Neos.Twitter.Bootstrap-20161124204912", + "Neos.Form-20161124205254", + "Neos.Flow-20161124224015", + "Neos.Party-20161124225257", + "Neos.Eel-20161124230101", + "Neos.Kickstart-20161124230102", + "Neos.Setup-20161124230842", + "Neos.Imagine-20161124231742", + "Neos.Media-20161124233100", + "Neos.NodeTypes-20161125002300", + "Neos.SiteKickstarter-20161125002311", + "Neos.Neos-20161125002322", + "Neos.ContentRepository-20161125012000", + "Neos.Fusion-20161125013710", + "Neos.Setup-20161125014759", + "Neos.SiteKickstarter-20161125095901", + "Neos.Fusion-20161125104701", + "Neos.NodeTypes-20161125104800", + "Neos.Neos-20161125104802", + "Neos.Kickstarter-20161125110814", + "Neos.Neos-20161125122412", + "Neos.Flow-20161125124112", + "TYPO3.FluidAdaptor-20161130112935", + "Neos.Fusion-20161201202543", + "Neos.Neos-20161201222211", + "Neos.Fusion-20161202215034", + "Neos.Fusion-20161219092345", + "Neos.ContentRepository-20161219093512", + "Neos.Media-20161219094126", + "Neos.Neos-20161219094403", + "Neos.Neos-20161219122512", + "Neos.Fusion-20161219130100", + "Neos.Neos-20161220163741", + "Neos.Neos-20170115114620", + "Neos.Fusion-20170120013047", + "Neos.Flow-20170125103800", + "Neos.Seo-20170127154600", + "Neos.Flow-20170127183102" + ] } } \ No newline at end of file