First working version
This commit is contained in:
parent
2f22be0c3f
commit
f35695268f
14 changed files with 643 additions and 0 deletions
8
.woodpecker/code-style.yml
Normal file
8
.woodpecker/code-style.yml
Normal 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
|
32
.woodpecker/functional-tests.yml
Normal file
32
.woodpecker/functional-tests.yml
Normal 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"
|
22
Classes/Aspects/AssetExtensionsAspect.php
Normal file
22
Classes/Aspects/AssetExtensionsAspect.php
Normal 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;
|
||||
}
|
29
Classes/AssetAttributeTrait.php
Normal file
29
Classes/AssetAttributeTrait.php
Normal 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;
|
||||
}
|
||||
}
|
100
Classes/Domain/Model/AssetAttribute.php
Normal file
100
Classes/Domain/Model/AssetAttribute.php
Normal 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();
|
||||
}
|
||||
}
|
21
Classes/ViewHelpers/CustomAttributesViewHelper.php
Normal file
21
Classes/ViewHelpers/CustomAttributesViewHelper.php
Normal 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();
|
||||
}
|
||||
}
|
13
Configuration/Settings.yaml
Normal file
13
Configuration/Settings.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
DigiComp:
|
||||
AssetAttributes:
|
||||
urlReplacements:
|
||||
-
|
||||
key: ","
|
||||
value: "-"
|
||||
-
|
||||
key: "/"
|
||||
value: "-"
|
||||
-
|
||||
key: "+"
|
||||
value: "-"
|
||||
customAssetProperties: []
|
22
Configuration/Views.yaml
Normal file
22
Configuration/Views.yaml
Normal 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"
|
37
Migrations/Mysql/Version20221018191523.php
Normal file
37
Migrations/Mysql/Version20221018191523.php
Normal 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');
|
||||
}
|
||||
}
|
25
Migrations/Mysql/Version20221211170135.php
Normal file
25
Migrations/Mysql/Version20221211170135.php
Normal 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
40
README.md
Normal 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]);
|
||||
```
|
206
Resources/Private/Templates/Asset/Edit.html
Normal file
206
Resources/Private/Templates/Asset/Edit.html
Normal 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>
|
50
Tests/Functional/AssetAttributeTest.php
Normal file
50
Tests/Functional/AssetAttributeTest.php
Normal 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
38
composer.json
Normal 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"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue