Skip to content

Commit

Permalink
Merge pull request #249 from Flowpack/feature/protect-asset-collections
Browse files Browse the repository at this point in the history
FEATURE: Protect referenced asset collections from being deleted
  • Loading branch information
Sebobo authored Nov 1, 2024
2 parents 5fa0849 + 05bff0d commit 86a7fbc
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 626 deletions.
31 changes: 31 additions & 0 deletions Classes/GraphQL/Resolver/Type/AssetCollectionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Flowpack\Media\Ui\Service\AssetCollectionService;
use Neos\Flow\Annotations as Flow;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use t3n\GraphQL\ResolverInterface;
use Neos\Media\Domain\Model\AssetCollection;
use Neos\Flow\Persistence\PersistenceManagerInterface;
Expand Down Expand Up @@ -44,6 +46,14 @@ class AssetCollectionResolver implements ResolverInterface
*/
protected $assetCollectionService;

/**
* @Flow\Inject
* @var SiteRepository
*/
protected $siteRepository;

protected array|null $siteDefaultAssetCollections = null;

public function id(AssetCollection $assetCollection): string
{
return $this->persistenceManager->getIdentifierByObject($assetCollection);
Expand All @@ -53,4 +63,25 @@ public function assetCount(AssetCollection $assetCollection): int
{
return $this->assetCollectionService->getAssetCollectionAssetCount($this->id($assetCollection));
}

/**
* Returns true if the asset collection is empty and is not assigned as default collection for a site
*/
public function canDelete(AssetCollection $assetCollection): bool
{
if ($this->siteDefaultAssetCollections === null) {
$this->siteDefaultAssetCollections = [];
/** @var Site $site */
foreach ($this->siteRepository->findAll() as $site) {
$siteAssetCollection = $site->getAssetCollection();
if (!$siteAssetCollection) {
continue;
}
$this->siteDefaultAssetCollections[$this->id($site->getAssetCollection())] = true;
}
}

return !array_key_exists($this->id($assetCollection), $this->siteDefaultAssetCollections)
&& $this->assetCount($assetCollection) === 0;
}
}
9 changes: 6 additions & 3 deletions Classes/GraphQL/Resolver/Type/MutationResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -652,10 +652,13 @@ public function deleteAssetCollection($_, array $variables): array
throw new Exception('Asset collection not found', 1591972269);
}

if ($this->assetCollectionService->getAssetCollectionAssetCount($id) > 0) {
throw new Exception('Asset collection is not empty', 1730102095);
}

/** @noinspection PhpUndefinedMethodInspection */
foreach ($this->siteRepository->findByAssetCollection($assetCollection) as $site) {
$site->setAssetCollection(null);
$this->siteRepository->update($site);
if ($this->siteRepository->findOneByAssetCollection($assetCollection)) {
throw new Exception('Asset collection is referenced as default collection of a site', 1730101671);
}

$this->assetCollectionRepository->remove($assetCollection);
Expand Down
1 change: 1 addition & 0 deletions Resources/Private/GraphQL/schema.root.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ type AssetCollection {
tags: [Tag!]!
assetCount: Int!
path: AssetCollectionPath
canDelete: Boolean!
}

"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const DeleteButton: React.FC = () => {
style="transparent"
hoverStyle="error"
disabled={
(!selectedAssetCollection || !config.canManageAssetCollections) &&
(!selectedAssetCollection || !config.canManageAssetCollections || !selectedAssetCollection.canDelete) &&
(!selectedTag || !config.canManageTags)
}
title={translate('assetCollectionTree.toolbar.delete', 'Delete')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const ASSET_COLLECTION_FRAGMENT = gql`
}
assetCount
path
canDelete
}
${TAG_FRAGMENT}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ interface AssetCollection extends GraphQlEntity {
tags?: Tag[];
assetCount: number;
path?: string;
canDelete: boolean;
}
16 changes: 14 additions & 2 deletions Resources/Private/JavaScript/dev-server/src/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const assetCollections: AssetCollection[] = range(3).map((index) => ({
: null,
// TODO: Recalculate assetCount of assetCollections after generation of assets
assetCount: 0,
canDelete: true,
}));

const getUsageDetailsForAsset = (assetId: string): UsageDetailsGroup[] => {
Expand Down Expand Up @@ -154,6 +155,17 @@ const assets = range(150).map((index) => {
const isCloud = index > 120;
const filename = getExampleFilename(index);

const collectionsForAsset = range(index % 2).map(
(i) => assetCollections[(i * 2 + index) % assetCollections.length]
);

collectionsForAsset.forEach((collection) => {
collection.assetCount++;
collection.canDelete = false;
});

const tagsForAsset = range(index % 3).map((i) => tags[(i * 3 + index) % tags.length]);

return {
__typename: 'Asset',
id: index.toString(),
Expand All @@ -163,8 +175,8 @@ const assets = range(150).map((index) => {
label: `Example asset ${index + 1}`,
caption: `The caption for example asset ${index + 1}`,
filename: 'example1.jpg',
tags: range(index % 3).map((i) => tags[(i * 3 + index) % tags.length]),
collections: range(index % 2).map((i) => assetCollections[(i * 2 + index) % assetCollections.length]),
tags: tagsForAsset,
collections: collectionsForAsset,
copyrightNotice: 'The Neos team',
lastModified: new Date(`2020-06-16 15:${Math.floor((150 - index) / 60)}:${(150 - index) % 60}`),
iptcProperties: index % 5 === 0 ? getIptcProperties(index) : [],
Expand Down
2 changes: 2 additions & 0 deletions Resources/Private/JavaScript/dev-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
deleteAssetCollection: ($_, { id }: { id: string }): boolean => {
const assetCollection = assetCollections.find((assetCollection) => assetCollection.id === id);
if (!assetCollection) return false;
if (assetCollection.assetCount > 0) return false;
assetCollections = assetCollections.filter((assetCollection) => assetCollection.id !== id);
return true;
},
Expand All @@ -236,6 +237,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
: null,
tags: [],
assetCount: 0,
canDelete: true,
};
assetCollections.push(newCollection);
return newCollection;
Expand Down
1 change: 0 additions & 1 deletion Resources/Private/JavaScript/media-module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"@types/react-dom": "^17.0.20",
"@types/react-dropzone": "^5.1.0",
"apollo-server-express": "^2.26.1",
"node-sass": "^8.0.0",
"parcel": "^2.9.1"
}
}
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ parameters:
- Classes
ignoreErrors:
- '#Call to an undefined method [a-zA-Z0-9\\_]+Repository::findBy[a-zA-Z]+\(\)#'
- '#Call to an undefined method [a-zA-Z0-9\\_]+Repository::findOneBy[a-zA-Z]+\(\)#'
Loading

0 comments on commit 86a7fbc

Please sign in to comment.