First working version
All checks were successful
ci/woodpecker/push/code-style Pipeline was successful
ci/woodpecker/push/functional-tests Pipeline was successful

This commit is contained in:
Ferdinand Kuhl 2023-02-09 13:02:22 +01:00
parent 2f22be0c3f
commit f35695268f
14 changed files with 643 additions and 0 deletions

View file

@ -0,0 +1,8 @@
pipeline:
code-style:
image: composer
commands:
- composer global config repositories.repo-name vcs https://git.digital-competence.de/Packages/php-codesniffer
- composer global config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
- composer global require digicomp/php-codesniffer:@dev
- composer global exec -- phpcs --runtime-set ignore_warnings_on_exit 1 --standard=DigiComp Classes/ Tests/ Migrations

View file

@ -0,0 +1,32 @@
workspace:
base: /woodpecker
path: package
matrix:
PHP_VERSION:
include:
- FLOW_VERSION: 7.3
PHP_VERSION: 7.4
- FLOW_VERSION: 7.3
PHP_VERSION: 8.1
- FLOW_VERSION: 8.2
PHP_VERSION: 8.2
pipeline:
functional-tests:
image: "thecodingmachine/php:${PHP_VERSION}-v4-cli"
environment:
# Enable the PDO_SQLITE extension
- "PHP_EXTENSION_PDO_SQLITE=1"
- "FLOW_VERSION=${FLOW_VERSION}"
- "NEOS_BUILD_DIR=/woodpecker/Build-${FLOW_VERSION}"
commands:
- "sudo mkdir $NEOS_BUILD_DIR"
- "sudo chown -R docker:docker $NEOS_BUILD_DIR"
- "cd $NEOS_BUILD_DIR"
- "composer create-project --no-install neos/flow-base-distribution:^$FLOW_VERSION ."
- "composer config repositories.repo-name path /woodpecker/package"
- "composer config --no-plugins allow-plugins.neos/composer-plugin true"
- "composer require digicomp/asset-attributes:@dev"
- "bin/phpunit --configuration Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/DigiComp.AssetAttributes/Tests/Functional"

View file

@ -0,0 +1,22 @@
<?php
namespace DigiComp\AssetAttributes\Aspects;
use DigiComp\AssetAttributes\Domain\Model\AssetAttribute;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
/**
* @Flow\Aspect
* @Flow\Introduce("class(Neos\Media\Domain\Model\Asset)", traitName="DigiComp\AssetAttributes\AssetAttributeTrait")
*/
class AssetExtensionsAspect
{
/**
* @Flow\Introduce("class(Neos\Media\Domain\Model\Asset)")
* @ORM\ManyToMany(inversedBy="assets", indexBy="name")
* @var Collection<AssetAttribute>
*/
protected $attributes;
}

View file

@ -0,0 +1,29 @@
<?php
namespace DigiComp\AssetAttributes;
use DigiComp\AssetAttributes\Domain\Model\AssetAttribute;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
trait AssetAttributeTrait
{
/**
* @return Collection<AssetAttribute>
*/
public function getAttributes(): Collection
{
if ($this->attributes === null) {
$this->attributes = new ArrayCollection();
}
return $this->attributes;
}
/**
* @param Collection<AssetAttribute> $attributes
*/
public function setAttributes(Collection $attributes): void
{
$this->attributes = $attributes;
}
}

View file

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace DigiComp\AssetAttributes\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
/**
* @Flow\ValueObject(embedded=false)
* @ORM\Table(
* indexes={
* @ORM\Index(columns={"name", "value"})
* }
* )
*/
class AssetAttribute
{
/**
* @var string
*/
protected string $name;
/**
* @var string
*/
protected string $value;
/**
* @var string
*/
protected string $urlValue;
/**
* @Flow\Transient
* @Flow\InjectConfiguration(package="DigiComp.AssetAttributes", path="urlReplacements")
* @var array
*/
protected array $replacementMap;
/**
* @param string $name
* @param string $value
* @param string $urlValue
*/
public function __construct(string $name, string $value, string $urlValue = '')
{
$this->name = $name;
$this->value = $value;
if (!$urlValue) {
$urlValue = $value;
}
$this->urlValue = $urlValue;
}
public function initializeObject(): void
{
if ($this->urlValue === $this->value) {
$this->urlValue = \str_replace(
\array_column($this->replacementMap, 'key'),
\array_column($this->replacementMap, 'value'),
$this->urlValue
);
$this->urlValue = \urlencode(\strtolower($this->urlValue));
}
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* @return string
*/
public function getUrlValue(): string
{
return $this->urlValue;
}
/**
* @return string
*/
public function __toString(): string
{
return $this->getName() . ': ' . $this->getValue();
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace DigiComp\AssetAttributes\ViewHelpers;
use Neos\Flow\Annotations as Flow;
use Neos\FluidAdaptor\Core\ViewHelper\AbstractViewHelper;
use Neos\Utility\PositionalArraySorter;
class CustomAttributesViewHelper extends AbstractViewHelper
{
/**
* @Flow\InjectConfiguration(path="customAssetProperties")
* @var array|null
*/
protected ?array $customAssetProperties;
public function render(): array
{
return (new PositionalArraySorter($this->customAssetProperties))->toArray();
}
}

View file

@ -0,0 +1,13 @@
DigiComp:
AssetAttributes:
urlReplacements:
-
key: ","
value: "-"
-
key: "/"
value: "-"
-
key: "+"
value: "-"
customAssetProperties: []

22
Configuration/Views.yaml Normal file
View file

@ -0,0 +1,22 @@
-
requestFilter: "isFormat('html') && isPackage('Neos.Media.Browser')"
options:
templatePathAndFilenamePattern: "@templateRoot/@subpackage/Asset/@action.@format"
templateRootPaths:
"DigiComp.AssetAttributes": "resource://DigiComp.AssetAttributes/Private/Templates"
"Neos.Media.Browser": "resource://Neos.Media.Browser/Private/Templates"
partialRootPaths:
"Neos.Neos": "resource://Neos.Neos/Private/Partials"
"Neos.Media.Browser": "resource://Neos.Media.Browser/Private/Partials"
-
requestFilter: "parentRequest.isPackage('Neos.Neos') && isFormat('html') && isPackage('Neos.Media.Browser')"
options:
templateRootPaths:
"DigiComp.AssetAttributes": "resource://DigiComp.AssetAttributes/Private/Templates"
"Neos.Media.Browser": "resource://Neos.Media.Browser/Private/Templates"
layoutRootPaths:
"Neos.Media.Browser": "resource://Neos.Media.Browser/Private/Layouts/Module"
partialRootPaths:
"Neos.Neos": "resource://Neos.Neos/Private/Partials"
"Neos.Media.Browser": "resource://Neos.Media.Browser/Private/Partials"

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Neos\Flow\Persistence\Doctrine\Migrations;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221018191523 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->abortIf(
!$this->connection->getDatabasePlatform() instanceof MySqlPlatform,
"Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MySqlPlatform'."
);
$this->addSql('CREATE TABLE digicomp_assetattributes_domain_model_assetattribute (persistence_object_identifier VARCHAR(40) NOT NULL, name VARCHAR(255) NOT NULL, value VARCHAR(255) NOT NULL, urlvalue VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_2EFCAB7C5E237E061D775834 (name, value), PRIMARY KEY(persistence_object_identifier))');
$this->addSql('CREATE TABLE neos_media_domain_model_asset_attributes_join (media_asset VARCHAR(40) NOT NULL, assetattributes_assetattribute VARCHAR(40) NOT NULL, INDEX IDX_68EDD3AD1DB69EED (media_asset), INDEX IDX_68EDD3ADE9AACB21 (assetattributes_assetattribute), PRIMARY KEY(media_asset, assetattributes_assetattribute))');
$this->addSql('ALTER TABLE neos_media_domain_model_asset_attributes_join ADD CONSTRAINT FK_68EDD3AD1DB69EED FOREIGN KEY (media_asset) REFERENCES neos_media_domain_model_asset (persistence_object_identifier)');
$this->addSql('ALTER TABLE neos_media_domain_model_asset_attributes_join ADD CONSTRAINT FK_68EDD3ADE9AACB21 FOREIGN KEY (assetattributes_assetattribute) REFERENCES digicomp_assetattributes_domain_model_assetattribute (persistence_object_identifier)');
}
public function down(Schema $schema): void
{
$this->abortIf(
!$this->connection->getDatabasePlatform() instanceof MySqlPlatform,
"Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MySqlPlatform'."
);
$this->addSql('ALTER TABLE neos_media_domain_model_asset_attributes_join DROP FOREIGN KEY FK_68EDD3ADE9AACB21');
$this->addSql('DROP TABLE digicomp_assetattributes_domain_model_assetattribute');
$this->addSql('DROP TABLE neos_media_domain_model_asset_attributes_join');
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Neos\Flow\Persistence\Doctrine\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221211170135 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE digicomp_assetattributes_domain_model_assetattribute DROP INDEX UNIQ_2EFCAB7C5E237E061D775834, ADD INDEX IDX_2EFCAB7C5E237E061D775834 (name, value)');
}
public function down(Schema $schema): void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE digicomp_assetattributes_domain_model_assetattribute DROP INDEX IDX_2EFCAB7C5E237E061D775834, ADD UNIQUE INDEX UNIQ_2EFCAB7C5E237E061D775834 (name, value)');
}
}

40
README.md Normal file
View file

@ -0,0 +1,40 @@
DigiComp.AssetAttributes
------------------------
![Build status](https://ci.digital-competence.de/api/badges/Packages/DigiComp.AssetAttributes/status.svg)
This package allows you to extend Neos Media assets with custom attributes.
This extension overwrites the original edit template of `neos/media-browser` - that way you get all of your custom properties and matching form fields in the classic asset editor.
You can add new attributes, by adding them to your `Settings.yaml`:
```yaml
DigiComp:
AssetAttributes:
customAssetProperties:
author:
type: 'textarea' # or empty for textfields
position: 'end'
```
Each asset instance will get an "attributes" property introduced, you can work with in PHP or DQL.
Examples:
- Working with Asset instances:
```php
$assetObject->getAttributes()->set($key, new AssetAttribute($key, $value));
```
- querying with DQL:
```dql
SELECT att FROM Neos\Media\Domain\Model\Asset a JOIN a.attributes att
```
- working with query objects:
```php
$query = new \Neos\Flow\Persistence\Doctrine\Query(\Neos\Media\Domain\Model\Asset::class);
$query->setOrderings(['attributes.value' => \Neos\Flow\Persistence\QueryInterface::ORDER_ASCENDING]);
```

View file

@ -0,0 +1,206 @@
{namespace m=Neos\Media\ViewHelpers}
{namespace neos=Neos\Neos\ViewHelpers}
{namespace assetAttributes=DigiComp\AssetAttributes\ViewHelpers}
<f:layout name="EditImage"/>
<f:section name="Title">Edit view</f:section>
<f:section name="Content">
<f:if condition="{connectionError}">
<f:then>
<h2>{neos:backend.translate(id: 'connectionError', package: 'Neos.Media.Browser')}</h2>
<p>{connectionError.message} ({connectionError.code})</p>
</f:then>
<f:else>
<f:if condition="{variantsTabFeatureEnabled} && {canShowVariants} && {assetProxy.localAssetIdentifier}">
<ul class="neos-nav neos-nav-tabs" role="tablist">
<li role="presentation" class="neos-active">
<a href="#" role="tab">Overview</a>
</li>
<li role="presentation">
<f:link.action action="variants" arguments="{assetSourceIdentifier: assetProxy.assetSource.identifier, assetProxyIdentifier: assetProxy.identifier, overviewAction: 'edit'}" addQueryString="true">Variants</f:link.action>
</li>
</ul>
</f:if>
<div class="neos-tab-content">
<div role="tabpanel" class="neos-tab-pane neos-active">
<f:form method="post" action="update" object="{assetProxy.asset}" objectName="asset">
<div class="neos-row-fluid">
<div class="neos-span6 neos-image-inputs">
<fieldset>
<f:if condition="{assetProxy.imported}">
<f:then>
<legend>{neos:backend.translate(id: 'basics', package: 'Neos.Media.Browser')}</legend>
<label for="title">{neos:backend.translate(id: 'field.title', package: 'Neos.Media.Browser')}</label>
<input id="title" readonly="readonly" value="{assetProxy.iptcProperties.Title}"/>
<label for="caption">{neos:backend.translate(id: 'field.caption', package: 'Neos.Media.Browser')}</label>
<textarea id="caption" rows="3" readonly="readonly">{assetProxy.iptcProperties.CaptionAbstract}</textarea>
<label for="copyrightnotice">{neos:backend.translate(id: 'field.copyrightnotice', package: 'Neos.Media.Browser')}</label>
<textarea id="copyrightnotice" rows="2" readonly="readonly">{assetProxy.iptcProperties.CopyrightNotice}</textarea>
</f:then>
<f:else>
<legend>{neos:backend.translate(id: 'basics', package: 'Neos.Media.Browser')}</legend>
<label for="title">{neos:backend.translate(id: 'field.title', package: 'Neos.Media.Browser')}</label>
<f:form.textfield property="title" id="title" placeholder="{neos:backend.translate(id: 'field.title', package: 'Neos.Media.Browser')}"/>
<label for="caption">{neos:backend.translate(id: 'field.caption', package: 'Neos.Media.Browser')}</label>
<f:form.textarea property="caption" id="caption" rows="3" placeholder="{neos:backend.translate(id: 'field.caption', package: 'Neos.Media.Browser')}"/>
<f:for each="{assetAttributes:customAttributes()}" key="attribute" as="attributeOptions">
<label for="attributes.{attribute}.value">{neos:backend.translate(id: '{attribute}.caption', package: 'DigiComp.AssetAttributes')}</label>
<f:switch expression="{attributeOptions.type}">
<f:case value="textarea">
<f:form.textarea property="attributes.{attribute}.value" id="attributes.{attribute}.value" rows="3" placeholder="{neos:backend.translate(id: '{attribute}.placeholder', package: 'DigiComp.AssetAttributes')}"/>
</f:case>
<f:defaultCase>
<f:form.textfield property="attributes.{attribute}.value" id="attributes.{attribute}.value" placeholder="{neos:backend.translate(id: '{attribute}.placeholder', package: 'DigiComp.AssetAttributes')}"/>
</f:defaultCase>
</f:switch>
<f:form.hidden property="attributes.{attribute}.name" value="{attribute}" />
</f:for>
<label for="copyrightnotice">{neos:backend.translate(id: 'field.copyrightnotice', package: 'Neos.Media.Browser')}</label>
<f:form.textarea property="copyrightNotice" id="copyrightnotice" rows="2" placeholder="{neos:backend.translate(id: 'field.copyrightnotice', package: 'Neos.Media.Browser')}"/>
</f:else>
</f:if>
<f:if condition="{tags}">
<label>{neos:backend.translate(id: 'tags', package: 'Neos.Media.Browser')}</label>
<f:for each="{tags}" as="tag">
<label class="neos-checkbox neos-inline">
<m:form.checkbox property="tags" multiple="TRUE" value="{tag}" /><span></span> {tag.label}
</label>
</f:for>
</f:if>
<f:if condition="{assetCollections}">
<label>{neos:backend.translate(id: 'collections', package: 'Neos.Media.Browser')}</label>
<f:for each="{assetCollections}" as="assetCollection">
<label class="neos-checkbox neos-inline">
<m:form.checkbox property="assetCollections" multiple="TRUE" value="{assetCollection}" /><span></span> {assetCollection.title}
</label>
</f:for>
</f:if>
</fieldset>
<fieldset>
<legend>{neos:backend.translate(id: 'metadata', package: 'Neos.Media.Browser')}</legend>
<table class="neos-info-table">
<tbody>
<f:if condition="{assetProxy.assetSource}">
<tr>
<th>{neos:backend.translate(id: 'mediaSource', package: 'Neos.Media.Browser')}</th>
<td>{assetProxy.assetSource.label}</td>
</tr>
</f:if>
<tr>
<th>{neos:backend.translate(id: 'metadata.filename', package: 'Neos.Media.Browser')}</th>
<td><a href="#" target="_blank">{assetProxy.filename}</a></td>
</tr>
<tr>
<th>{neos:backend.translate(id: 'metadata.lastModified', package: 'Neos.Media.Browser')}</th>
<td><span title="{assetProxy.lastModified -> f:format.date(format: 'd-m-Y H:i')}" data-neos-toggle="tooltip">{assetProxy.lastModified -> m:format.relativeDate()}</span></td>
</tr>
<tr>
<th>{neos:backend.translate(id: 'metadata.fileSize', package: 'Neos.Media.Browser')}</th>
<td>{assetProxy.fileSize -> f:format.bytes()}</td>
</tr>
<f:if condition="{assetProxy.iptcProperties.CopyrightNotice}">
<tr>
<th>{neos:backend.translate(id: 'metadata.iptcProperties.CopyrightNotice', package: 'Neos.Media.Browser')}</th>
<td>{assetProxy.iptcProperties.CopyrightNotice}</td>
</tr>
</f:if>
<f:if condition="{assetProxy.widthInPixels}">
<tr>
<th>{neos:backend.translate(id: 'metadata.dimensions', package: 'Neos.Media.Browser')}</th>
<td>{assetProxy.widthInPixels} x {assetProxy.heightInPixels}</td>
</tr>
</f:if>
<tr>
<th>{neos:backend.translate(id: 'metadata.type', package: 'Neos.Media.Browser')}</th>
<td><span class="neos-label">{assetProxy.mediaType}</span></td>
</tr>
<tr>
<th>{neos:backend.translate(id: 'metadata.identifier', package: 'Neos.Media.Browser')}</th>
<td><span class="neos-label">{assetProxy.localAssetIdentifier}</span></td>
</tr>
</tbody>
</table>
<f:if condition="{assetProxy.asset.inUse}">
<f:link.action action="relatedNodes" arguments="{asset:assetProxy.asset}" addQueryString="true" class="neos-button">
{neos:backend.translate(id: 'relatedNodes', quantity: '{assetProxy.asset.usageCount}', arguments: {0: assetProxy.asset.usageCount}, package: 'Neos.Media.Browser')}
</f:link.action>
</f:if>
</fieldset>
</div>
<div class="neos-span6 neos-image-example">
<f:render partial="{contentPreview}Preview" arguments="{_all}" />
</div>
</div>
<div class="neos-footer">
<f:link.action action="index" addQueryString="true" class="neos-button neos-action-cancel">{neos:backend.translate(id: 'cancel', package: 'Neos.Neos')}</f:link.action>
<f:if condition="!{assetSource.readOnly}">
<f:security.ifAccess privilegeTarget="Neos.Media.Browser:ReplaceAssetResource">
<f:link.action action="replaceAssetResource" arguments="{asset: assetProxy.asset}" addQueryString="true" class="neos-button" title="{neos:backend.translate(id: 'replaceAssetResource', package: 'Neos.Media.Browser')}" data="{neos-toggle: 'tooltip', container: 'body'}">
{neos:backend.translate(id: 'replaceAssetResource', package: 'Neos.Media.Browser')}
</f:link.action>
</f:security.ifAccess>
<f:if condition="{assetProxy.asset.inUse}">
<f:then>
<a title="{neos:backend.translate(id: 'deleteRelatedNodes', package: 'Neos.Media.Browser')}" data-neos-toggle="tooltip" data-container="body" class="neos-button neos-button-danger neos-disabled">{neos:backend.translate(id: 'delete', package: 'Neos.Neos')}</a>
</f:then>
<f:else>
<a data-toggle="modal" href="#asset-{assetProxy.asset -> f:format.identifier()}" class="neos-button neos-button-danger">{neos:backend.translate(id: 'delete', package: 'Neos.Neos')}</a>
</f:else>
</f:if>
<f:form.submit id="save" class="neos-button neos-button-primary" value="{neos:backend.translate(id: 'saveEditing', package: 'Neos.Media.Browser')}" />
</f:if>
</div>
<div class="neos-hide" id="asset-{assetProxy.localAssetIdentifier}">
<div class="neos-modal-centered">
<div class="neos-modal-content">
<div class="neos-modal-header">
<button type="button" class="neos-close neos-button" data-dismiss="modal"></button>
<div class="neos-header">
{neos:backend.translate(id: 'message.reallyDeleteAsset', arguments: {0: assetProxy.label}, package: 'Neos.Media.Browser')}
</div>
<div>
<div class="neos-subheader">
<p>
{neos:backend.translate(id: 'message.willBeDeleted', package: 'Neos.Media.Browser')}<br />
{neos:backend.translate(id: 'message.operationCannotBeUndone', package: 'Neos.Media.Browser')}
</p>
</div>
</div>
</div>
<div class="neos-modal-footer">
<a href="#" class="neos-button" data-dismiss="modal">{neos:backend.translate(id: 'cancel', package: 'Neos.Neos')}</a>
<button type="submit" form="postHelper" formaction="{f:uri.action(action: 'delete', arguments: {asset: assetProxy.asset}, addQueryString: true)}" class="neos-button neos-button-mini neos-button-danger">
{neos:backend.translate(id: 'message.confirmDelete', package: 'Neos.Media.Browser')}
</button>
</div>
</div>
</div>
<div class="neos-modal-backdrop neos-in"></div>
</div>
<f:render partial="ConstraintsHiddenFields" arguments="{constraints: constraints}" />
</f:form>
<f:form action="index" id="postHelper" method="post">
<f:render partial="ConstraintsHiddenFields" arguments="{constraints: constraints}" />
</f:form>
</div>
</div>
</f:else>
</f:if>
</f:section>
<f:section name="ContentImage">
<label>{neos:backend.translate(id: 'preview', package: 'Neos.Media.Browser')}</label>
<div class="neos-preview-image">
<a href="{assetProxy.originalUri}" target="_blank">
<img src="{assetProxy.previewUri}" class="img-polaroid" alt="{assetProxy.label}"/>
</a>
</div>
</f:section>
<f:section name="Scripts">
<script type="text/javascript" src="{f:uri.resource(package: 'Neos.Media.Browser', path: 'JavaScript/edit.js')}"></script>
</f:section>

View file

@ -0,0 +1,50 @@
<?php
namespace DigiComp\AssetAttributes\Tests\Functional;
use DigiComp\AssetAttributes\Domain\Model\AssetAttribute;
use Neos\Flow\Persistence\Doctrine\PersistenceManager;
use Neos\Flow\Persistence\Doctrine\Query;
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\Media\Domain\Model\Asset;
class AssetAttributeTest extends FunctionalTestCase
{
protected static $testablePersistenceEnabled = true;
protected ResourceManager $resourceManager;
protected function setUp(): void
{
parent::setUp();
if (!$this->persistenceManager instanceof PersistenceManager) {
$this->markTestSkipped('Doctrine persistence is not enabled');
}
$this->resourceManager = $this->objectManager->get(ResourceManager::class);
}
/**
* @test
*/
public function itFindsAssetsHavingAnOwnAttribute(): void
{
$resource = $this->resourceManager->importResourceFromContent('hello world', 'hello-world.txt');
$asset = new Asset($resource);
$asset->getAttributes()->set('author', new AssetAttribute('author', 'Joe'));
$this->persistenceManager->add($asset);
$resource2 = $this->resourceManager->importResourceFromContent('hello universe', 'hello-universe.txt');
$asset2 = new Asset($resource2);
$this->persistenceManager->add($asset2);
$this->persistenceManager->persistAll();
$query = new Query(Asset::class);
$result = $query->matching($query->logicalAnd([
$query->equals('attributes.name', 'author'),
$query->equals('attributes.value', 'Joe'),
]))->execute();
static::assertCount(1, $result);
}
}

38
composer.json Normal file
View file

@ -0,0 +1,38 @@
{
"description": "Attributes for media assets",
"type": "neos-package",
"name": "digicomp/asset-attributes",
"require": {
"neos/media": "^7.3.5 | ^8.0",
"php": "^7.4 | ^8.0"
},
"suggest": {
"neos/media-browser": "^7.3.10 | ^8.0"
},
"autoload": {
"psr-4": {
"DigiComp\\AssetAttributes\\": "Classes/"
}
},
"extra": {
"neos": {
"package-key": "DigiComp.AssetAttributes"
}
},
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "https://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://git.digital-competence.de/Packages/DigiComp.AssetAttributes",
"keywords": [
"Neos",
"Flow",
"media",
"asset"
]
}