Several create and edit steps.

pull/10/head
Jens Pelzetter 2021-07-01 19:56:29 +02:00
parent 6d850fb503
commit 4618716d09
16 changed files with 2618 additions and 34 deletions

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.assets.AudioAsset;
import org.librecms.contentsection.AssetRepository;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsAudioAssetCreateStep")
public class AudioAssetCreateStep extends AbstractMvcAssetCreateStep<AudioAsset> {
@Inject
private AssetRepository assetRepo;
@Inject
private GlobalizationHelper globalizationHelper;
private String description;
@Override
public String showCreateStep() {
return "org/librecms/ui/contentsection/assets/audioasset/create-audioasset.xhtml";
}
@Override
public String getLabel() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("audioasset.label");
}
@Override
public String getDescription() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("audioasset.description");
}
@Override
public String getBundle() {
return MvcAssetStepsConstants.BUNDLE;
}
@Override
protected Class<AudioAsset> getAssetClass() {
return AudioAsset.class;
}
@Override
protected String setAssetProperties(
final AudioAsset asset, final Map<String, String[]> formParams
) {
description = Optional
.ofNullable(formParams.get("description"))
.filter(value -> value.length > 0)
.map(value -> value[0])
.orElse("");
asset.getDescription().addValue(
new Locale(getInitialLocale()), description
);
assetRepo.save(asset);
return String.format(
"redirect:/%s/assets/%s/%s/@audioasset-edit",
getContentSectionLabel(),
getFolderPath(),
getName()
);
}
}

View File

@ -0,0 +1,488 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.assets.FileAsset;
import org.librecms.assets.AudioAsset;
import org.librecms.assets.LegalMetadata;
import org.librecms.contentsection.AssetRepository;
import org.librecms.ui.contentsections.AssetPermissionsChecker;
import org.librecms.ui.contentsections.ContentSectionNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "audioasset-edit")
@Controller
@MvcAssetEditStepDef(
bundle = MvcAssetStepsConstants.BUNDLE,
descriptionKey = "audioasset.editstep.description",
labelKey = "audioasset.editstep.label",
supportedAssetType = FileAsset.class
)
public class AudioAssetEditStep extends AbstractMvcAssetEditStep {
private static final Logger LOGGER = LogManager.getLogger(
AudioAssetEditStep.class
);
@Inject
private AssetStepsDefaultMessagesBundle messageBundle;
@Inject
private AssetUi assetUi;
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private IdentifierParser identifierParser;
@Inject
private AssetPermissionsChecker assetPermissionsChecker;
@Inject
private Models models;
@Inject
private AudioAssetEditStepModel editStepModel;
@Override
public Class<? extends MvcAssetEditStep> getStepClass() {
return AudioAssetEditStep.class;
}
@Override
protected void init() throws ContentSectionNotFoundException,
AssetNotFoundException {
super.init();
if (getAsset() instanceof AudioAsset) {
editStepModel.setDescriptionValues(
getAudioAsset()
.getDescription()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
)
);
final Set<Locale> descriptionLocales = getAudioAsset()
.getDescription()
.getAvailableLocales();
editStepModel.setUnusedDescriptionLocales(
globalizationHelper
.getAvailableLocales()
.stream()
.filter(locale -> !descriptionLocales.contains(locale))
.map(Locale::toString)
.collect(Collectors.toList())
);
editStepModel.setFileName(getAudioAsset().getFileName());
editStepModel.setMimeType(
Optional
.ofNullable(getAudioAsset().getMimeType())
.map(MimeType::toString)
.orElse("")
);
editStepModel.setSize(getAudioAsset().getSize());
final long size = getAudioAsset().getSize();
if (size < 2048) {
editStepModel.setSizeLabel(String.format("%d Bytes", size));
} else if (size < 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d kB", size / 1024)
);
} else if (size < 1024 * 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d MB", size / (1024 * 1024))
);
} else {
editStepModel.setSizeLabel(
String.format("%d GB", size / (1024 * 1024 * 1024))
);
}
editStepModel.setLegalMetadata(getAudioAsset().getLegalMetadata());
} else {
throw new AssetNotFoundException(
assetUi.showAssetNotFound(
getContentSection(), getAssetPath()
),
String.format(
"No asset for path %s found in section %s.",
getAssetPath(),
getContentSection().getLabel()
)
);
}
}
@GET
@Path("/")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String showStep(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
return "org/librecms/ui/contentsection/assets/audioasset/edit-audioasset.xhtml";
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/add")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String addDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@FormParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final AudioAsset asset = getAudioAsset();
asset.getDescription().addValue(locale, value);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/edit/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String editDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final AudioAsset asset = getAudioAsset();
asset.getDescription().addValue(locale, value);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/remove/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final AudioAsset asset = getAudioAsset();
asset.getDescription().removeValue(locale);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/legalmetadata")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String setLegalMetadata(
@FormParam("legalMetadataIdentifier")
final String legalMetadataIdentifier
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
final Identifier identifier = identifierParser
.parseIdentifier(legalMetadataIdentifier);
final Optional<LegalMetadata> legalMetadataResult;
switch (identifier.getType()) {
case ID:
legalMetadataResult = assetRepo.findById(
Long.parseLong(identifier.getIdentifier()),
LegalMetadata.class
);
break;
case UUID:
legalMetadataResult = assetRepo.findByUuidAndType(
identifier.getIdentifier(),
LegalMetadata.class
);
break;
default:
legalMetadataResult = assetRepo
.findByPath(identifier.getIdentifier())
.map(result -> (LegalMetadata) result);
break;
}
if (!legalMetadataResult.isPresent()) {
return showLegalMetadataNotFound(legalMetadataIdentifier);
}
final LegalMetadata legalMetadata = legalMetadataResult.get();
getAudioAsset().setLegalMetadata(legalMetadata);
assetRepo.save(getAudioAsset());
return buildRedirectPathForStep();
}
@POST
@Path("/legalmetadata/@remove")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeLegalMetadata() {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
getAudioAsset().setLegalMetadata(null);
assetRepo.save(getAudioAsset());
return buildRedirectPathForStep();
}
private String showLegalMetadataNotFound(
final String legalMetadataIdentifer
) {
models.put("legalMetadataIdentifier", legalMetadataIdentifer);
return "org/librecms/ui/contentsection/assets/external-video-asset/legal-metadata-not-found.xhtml";
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String uploadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
final MultipartFormDataInput input
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final AudioAsset asset = getAudioAsset();
final Map<String, List<InputPart>> uploadForm = input
.getFormDataMap();
final List<InputPart> inputParts = uploadForm.get("fileData");
String fileName = "";
String contentType = "";
for (final InputPart inputPart : inputParts) {
try {
final MultivaluedMap<String, String> headers = inputPart
.getHeaders();
fileName = getFileName(headers);
contentType = getContentType(headers);
dataService.saveData(
asset,
inputPart.getBody(InputStream.class, null),
fileName,
contentType
);
} catch (IOException | UnexpectedErrorException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
}
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
public AudioAsset getAudioAsset() {
return (AudioAsset) getAsset();
}
private String getFileName(final MultivaluedMap<String, String> headers) {
final String[] contentDisposition = headers
.getFirst("Content-Disposition")
.split(";");
for (final String fileName : contentDisposition) {
if (fileName.trim().startsWith("filename")) {
final String[] name = fileName.split("=");
return name[1].trim().replaceAll("\"", "");
}
}
return "";
}
private String getContentType(
final MultivaluedMap<String, String> headers
) {
return headers.getFirst("Content-Type");
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.AudioAsset;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.ui.contentsections.ContentSectionsUi;
import java.io.IOException;
import java.io.OutputStream;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "audioasset-edit-download")
public class AudioAssetEditStepDownload {
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private ContentSectionsUi sectionsUi;
@GET
@Path("/")
@AuthorizationRequired
public Response downloadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath
) {
final ContentSection contentSection = sectionsUi
.findContentSection(sectionIdentifier)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"ContentSection %s not found.",
sectionIdentifier
)
).build()
)
);
final Asset asset = assetRepo
.findByPath(contentSection, assetPath)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No asset for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
)
);
if (!(asset instanceof AudioAsset)) {
throw new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No asset for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
);
}
final AudioAsset audioAsset = (AudioAsset) asset;
return Response
.ok()
.entity(
new StreamingOutput() {
@Override
public void write(final OutputStream outputStream)
throws IOException, WebApplicationException {
dataService.copyDataToOutputStream(
audioAsset, outputStream
);
}
})
.header("Content-Type", audioAsset.getMimeType())
.header(
"Content-Disposition",
String.format(
"attachment; filename=\"%s\"",
audioAsset.getFileName()
)
)
.build();
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.librecms.assets.LegalMetadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsAudioAssetEditStepModel")
public class AudioAssetEditStepModel {
private Map<String, String> descriptionValues;
private List<String> unusedDescriptionLocales;
private String fileName;
private String mimeType;
private long size;
private String sizeLabel;
private LegalMetadata legalMetadata;
public Map<String, String> getDescriptionValues() {
return Collections.unmodifiableMap(descriptionValues);
}
protected void setDescriptionValues(
final Map<String, String> descriptionValues
) {
this.descriptionValues = new HashMap<>(descriptionValues);
}
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
protected void setUnusedDescriptionLocales(
final List<String> descriptionLocales
) {
this.unusedDescriptionLocales = new ArrayList<>(descriptionLocales);
}
public String getFileName() {
return fileName;
}
protected void setFileName(final String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
return mimeType;
}
protected void setMimeType(final String mimeType) {
this.mimeType = mimeType;
}
public long getSize() {
return size;
}
protected void setSize(final long size) {
this.size = size;
}
public String getSizeLabel() {
return sizeLabel;
}
protected void setSizeLabel(final String sizeLabel) {
this.sizeLabel = sizeLabel;
}
public LegalMetadata getLegalMetadata() {
return legalMetadata;
}
protected void setLegalMetadata(final LegalMetadata legalMetadata) {
this.legalMetadata = legalMetadata;
}
public String getLegalMetadataType() {
return LegalMetadata.class.getName();
}
}

View File

@ -20,7 +20,6 @@ package org.librecms.ui.contentsections.assets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.engine.jdbc.BlobProxy;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.libreccm.core.UnexpectedErrorException;
@ -34,9 +33,6 @@ import org.librecms.ui.contentsections.ContentSectionNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.sql.Blob;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -45,7 +41,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
@ -208,10 +203,6 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
}
}
public FileAsset getFileAsset() {
return (FileAsset) getAsset();
}
@POST
@Path("/description/add")
@AuthorizationRequired
@ -234,10 +225,10 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final FileAsset bookmark = getFileAsset();
bookmark.getDescription().addValue(locale, value);
final FileAsset fileAsset = getFileAsset();
fileAsset.getDescription().addValue(locale, value);
assetRepo.save(bookmark);
assetRepo.save(fileAsset);
return buildRedirectPathForStep();
} else {
@ -270,10 +261,10 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final FileAsset bookmark = getFileAsset();
bookmark.getDescription().addValue(locale, value);
final FileAsset fileAsset = getFileAsset();
fileAsset.getDescription().addValue(locale, value);
assetRepo.save(bookmark);
assetRepo.save(fileAsset);
return buildRedirectPathForStep();
} else {
@ -305,10 +296,10 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final FileAsset bookmark = getFileAsset();
bookmark.getDescription().removeValue(locale);
final FileAsset fileAsset = getFileAsset();
fileAsset.getDescription().removeValue(locale);
assetRepo.save(bookmark);
assetRepo.save(fileAsset);
return buildRedirectPathForStep();
} else {
@ -380,7 +371,10 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
public FileAsset getFileAsset() {
return (FileAsset) getAsset();
}
private String getFileName(final MultivaluedMap<String, String> headers) {
@ -405,18 +399,4 @@ public class FileAssetEditStep extends AbstractMvcAssetEditStep {
return headers.getFirst("Content-Type");
}
private long getFileSize(final MultivaluedMap<String, String> headers) {
if (headers.containsKey("Content-Length")) {
try {
return Long.parseLong(
headers.getFirst("Content-Length")
);
} catch (NumberFormatException ex) {
throw new UnexpectedErrorException(ex);
}
} else {
return 0;
}
}
}

View File

@ -31,7 +31,6 @@ import java.io.OutputStream;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -45,7 +44,6 @@ import javax.ws.rs.core.StreamingOutput;
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "fileasset-edit-download")
public class FileAssetEditStepDownload {
@Inject

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.assets.Image;
import org.librecms.contentsection.AssetRepository;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsImageCreatStep")
public class ImageCreateStep extends AbstractMvcAssetCreateStep<Image> {
@Inject
private AssetRepository assetRepository;
@Inject
private GlobalizationHelper globalizationHelper;
private String description;
@Override
public String showCreateStep() {
return "org/librecms/ui/contentsection/assets/image/create-image.xhtml";
}
@Override
public String getLabel() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("image.label");
}
@Override
public String getDescription() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("image.description");
}
@Override
public String getBundle() {
return MvcAssetStepsConstants.BUNDLE;
}
@Override
protected Class<Image> getAssetClass() {
return Image.class;
}
@Override
protected String setAssetProperties(
final Image image, final Map<String, String[]> formParams
) {
description = Optional
.ofNullable(formParams.get("description"))
.filter(value -> value.length > 0)
.map(value -> value[0])
.orElse("");
image.getDescription().addValue(
new Locale(getInitialLocale()), description
);
assetRepository.save(image);
return String.format(
"redirect:/%s/assets/%s/%s/@image-edit",
getContentSectionLabel(),
getFolderPath(),
getName()
);
}
}

View File

@ -0,0 +1,489 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.assets.FileAsset;
import org.librecms.assets.Image;
import org.librecms.assets.LegalMetadata;
import org.librecms.contentsection.AssetRepository;
import org.librecms.ui.contentsections.AssetPermissionsChecker;
import org.librecms.ui.contentsections.ContentSectionNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "image-edit")
@Controller
@MvcAssetEditStepDef(
bundle = MvcAssetStepsConstants.BUNDLE,
descriptionKey = "image.editstep.description",
labelKey = "image.editstep.label",
supportedAssetType = FileAsset.class
)
public class ImageEditStep extends AbstractMvcAssetEditStep {
private static final Logger LOGGER = LogManager.getLogger(
ImageEditStep.class
);
@Inject
private AssetStepsDefaultMessagesBundle messageBundle;
@Inject
private AssetUi assetUi;
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private IdentifierParser identifierParser;
@Inject
private AssetPermissionsChecker assetPermissionsChecker;
@Inject
private Models models;
@Inject
private ImageEditStepModel editStepModel;
@Override
public Class<? extends MvcAssetEditStep> getStepClass() {
return ImageEditStep.class;
}
@Override
public void init() throws ContentSectionNotFoundException,
AssetNotFoundException {
super.init();
if (getAsset() instanceof Image) {
editStepModel.setDescriptionValues(
getImage()
.getDescription()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
)
);
final Set<Locale> descriptionLocales = getImage()
.getDescription()
.getAvailableLocales();
editStepModel.setUnusedDescriptionLocales(
globalizationHelper
.getAvailableLocales()
.stream()
.filter(locale -> !descriptionLocales.contains(locale))
.map(Locale::toString)
.collect(Collectors.toList())
);
editStepModel.setFileName(getImage().getFileName());
editStepModel.setMimeType(
Optional
.ofNullable(getImage().getMimeType())
.map(MimeType::toString)
.orElse("")
);
editStepModel.setSize(getImage().getSize());
final long size = getImage().getSize();
if (size < 2048) {
editStepModel.setSizeLabel(String.format("%d Bytes", size));
} else if (size < 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d kB", size / 1024)
);
} else if (size < 1024 * 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d MB", size / (1024 * 1024))
);
} else {
editStepModel.setSizeLabel(
String.format("%d GB", size / (1024 * 1024 * 1024))
);
}
editStepModel.setHeight(getImage().getHeight());
editStepModel.setWidth(getImage().getWidth());
editStepModel.setLegalMetadata(getImage().getLegalMetadata());
} else {
throw new AssetNotFoundException(
assetUi.showAssetNotFound(
getContentSection(), getAssetPath()
),
String.format(
"No image for path %s found in section %s.",
getAssetPath(),
getContentSection().getLabel()
)
);
}
}
@GET
@Path("/")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String showStep(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
return "org/librecms/ui/contentsection/assets/image/edit-image.xhtml";
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/add")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String addDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@FormParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final Image image = getImage();
image.getDescription().addValue(locale, value);
assetRepo.save(image);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/edit/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String editDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final Image image = getImage();
image.getDescription().addValue(locale, value);
assetRepo.save(image);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/remove/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final Image image = getImage();
image.getDescription().removeValue(locale);
assetRepo.save(image);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/legalmetadata")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String setLegalMetadata(
@FormParam("legalMetadataIdentifier")
final String legalMetadataIdentifier
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
final Identifier identifier = identifierParser
.parseIdentifier(legalMetadataIdentifier);
final Optional<LegalMetadata> legalMetadataResult;
switch (identifier.getType()) {
case ID:
legalMetadataResult = assetRepo.findById(
Long.parseLong(identifier.getIdentifier()),
LegalMetadata.class
);
break;
case UUID:
legalMetadataResult = assetRepo.findByUuidAndType(
identifier.getIdentifier(),
LegalMetadata.class
);
break;
default:
legalMetadataResult = assetRepo
.findByPath(identifier.getIdentifier())
.map(result -> (LegalMetadata) result);
break;
}
if (!legalMetadataResult.isPresent()) {
return showLegalMetadataNotFound(legalMetadataIdentifier);
}
final LegalMetadata legalMetadata = legalMetadataResult.get();
getImage().setLegalMetadata(legalMetadata);
assetRepo.save(getImage());
return buildRedirectPathForStep();
}
@POST
@Path("/legalmetadata/@remove")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeLegalMetadata() {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
getImage().setLegalMetadata(null);
assetRepo.save(getImage());
return buildRedirectPathForStep();
}
private String showLegalMetadataNotFound(
final String legalMetadataIdentifer
) {
models.put("legalMetadataIdentifier", legalMetadataIdentifer);
return "org/librecms/ui/contentsection/assets/external-video-asset/legal-metadata-not-found.xhtml";
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String uploadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
final MultipartFormDataInput input
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Image image = getImage();
final Map<String, List<InputPart>> uploadForm = input
.getFormDataMap();
final List<InputPart> inputParts = uploadForm.get("fileData");
String fileName = "";
String contentType = "";
for (final InputPart inputPart : inputParts) {
try {
final MultivaluedMap<String, String> headers = inputPart
.getHeaders();
fileName = getFileName(headers);
contentType = getContentType(headers);
dataService.saveData(
image,
inputPart.getBody(InputStream.class, null),
fileName,
contentType
);
} catch (IOException | UnexpectedErrorException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
}
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
public Image getImage() {
return (Image) getAsset();
}
private String getFileName(final MultivaluedMap<String, String> headers) {
final String[] contentDisposition = headers
.getFirst("Content-Disposition")
.split(";");
for (final String fileName : contentDisposition) {
if (fileName.trim().startsWith("filename")) {
final String[] name = fileName.split("=");
return name[1].trim().replaceAll("\"", "");
}
}
return "";
}
private String getContentType(
final MultivaluedMap<String, String> headers
) {
return headers.getFirst("Content-Type");
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.assets.Image;
import org.librecms.assets.ImageService;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.ui.contentsections.ContentSectionsUi;
import java.io.IOException;
import java.io.OutputStream;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "image-edit-download")
public class ImageEditStepDownload {
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private ContentSectionsUi sectionsUi;
@Inject
private ImageService imageService;
@GET
@Path("/")
@AuthorizationRequired
public Response downloadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@QueryParam("width") @DefaultValue("-1")
final int width,
@QueryParam("height") @DefaultValue("-1")
final int height
) {
final ContentSection contentSection = sectionsUi
.findContentSection(sectionIdentifier)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"ContentSection %s not found.",
sectionIdentifier
)
).build()
)
);
final Asset asset = assetRepo
.findByPath(contentSection, assetPath)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No asset for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
)
);
if (!(asset instanceof Image)) {
throw new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No image for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
);
}
final Image image = (Image) asset;
final StreamingOutput streamingOutput;
if (width == -1 && height == -1) {
streamingOutput = new StreamingOutput() {
@Override
public void write(final OutputStream outputStream)
throws IOException, WebApplicationException {
dataService.copyDataToOutputStream(image, outputStream);
}
};
} else {
final byte[] scaled = imageService.scaleImage(image, width, height);
streamingOutput = new StreamingOutput() {
@Override
public void write(final OutputStream outputStream)
throws IOException, WebApplicationException {
outputStream.write(scaled);
}
};
}
return Response
.ok()
.entity(streamingOutput)
.header("Content-Type", image.getMimeType())
.header(
"Content-Disposition",
String.format(
"attachment; filename=\"%s\"",
image.getFileName()
)
)
.build();
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.librecms.assets.LegalMetadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsImageEditStepModel")
public class ImageEditStepModel {
private Map<String, String> descriptionValues;
private List<String> unusedDescriptionLocales;
private String fileName;
private String mimeType;
private long size;
private String sizeLabel;
private long width;
private long height;
private LegalMetadata legalMetadata;
public Map<String, String> getDescriptionValues() {
return Collections.unmodifiableMap(descriptionValues);
}
protected void setDescriptionValues(
final Map<String, String> descriptionValues
) {
this.descriptionValues = new HashMap<>(descriptionValues);
}
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
protected void setUnusedDescriptionLocales(
final List<String> descriptionLocales
) {
this.unusedDescriptionLocales = new ArrayList<>(descriptionLocales);
}
public String getFileName() {
return fileName;
}
protected void setFileName(final String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
return mimeType;
}
protected void setMimeType(final String mimeType) {
this.mimeType = mimeType;
}
public long getSize() {
return size;
}
protected void setSize(final long size) {
this.size = size;
}
public String getSizeLabel() {
return sizeLabel;
}
protected void setSizeLabel(final String sizeLabel) {
this.sizeLabel = sizeLabel;
}
public long getWidth() {
return width;
}
protected void setWidth(final long width) {
this.width = width;
}
public long getHeight() {
return height;
}
protected void setHeight(final long height) {
this.height = height;
}
public LegalMetadata getLegalMetadata() {
return legalMetadata;
}
protected void setLegalMetadata(final LegalMetadata legalMetadata) {
this.legalMetadata = legalMetadata;
}
public String getLegalMetadataType() {
return LegalMetadata.class.getName();
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.l10n.GlobalizationHelper;
import org.librecms.assets.FileAsset;
import org.librecms.assets.VideoAsset;
import org.librecms.contentsection.AssetRepository;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsVideoAssetCreateStep")
public class VideoAssetCreateStep extends AbstractMvcAssetCreateStep<VideoAsset>{
@Inject
private AssetRepository assetRepo;
@Inject
private GlobalizationHelper globalizationHelper;
private String fileDescription;
@Override
public String showCreateStep() {
return "org/librecms/ui/contentsection/assets/videoasset/create-videoasset.xhtml";
}
@Override
public String getLabel() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("videoasset.label");
}
@Override
public String getDescription() {
return globalizationHelper
.getLocalizedTextsUtil(getBundle())
.getText("videoasset.description");
}
@Override
public String getBundle() {
return MvcAssetStepsConstants.BUNDLE;
}
@Override
protected Class<VideoAsset> getAssetClass() {
return VideoAsset.class;
}
@Override
protected String setAssetProperties(
final VideoAsset asset, final Map<String, String[]> formParams
) {
fileDescription = Optional
.ofNullable(formParams.get("description"))
.filter(value -> value.length > 0)
.map(value -> value[0])
.orElse("");
asset.getDescription().addValue(
new Locale(getInitialLocale()), fileDescription
);
assetRepo.save(asset);
return String.format(
"redirect:/%s/assets/%s/%s/@videoasset-edit",
getContentSectionLabel(),
getFolderPath(),
getName()
);
}
}

View File

@ -0,0 +1,487 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.libreccm.api.Identifier;
import org.libreccm.api.IdentifierParser;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.assets.FileAsset;
import org.librecms.assets.VideoAsset;
import org.librecms.assets.LegalMetadata;
import org.librecms.contentsection.AssetRepository;
import org.librecms.ui.contentsections.AssetPermissionsChecker;
import org.librecms.ui.contentsections.ContentSectionNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "videoasset-edit")
@Controller
@MvcAssetEditStepDef(
bundle = MvcAssetStepsConstants.BUNDLE,
descriptionKey = "videoasset.editstep.description",
labelKey = "videoasset.editstep.lable",
supportedAssetType = FileAsset.class
)
public class VideoAssetEditStep extends AbstractMvcAssetEditStep {
private static final Logger LOGGER = LogManager.getLogger(VideoAssetEditStep.class
);
@Inject
private AssetStepsDefaultMessagesBundle messageBundle;
@Inject
private AssetUi assetUi;
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private IdentifierParser identifierParser;
@Inject
private AssetPermissionsChecker assetPermissionsChecker;
@Inject
private Models models;
@Inject
private VideoAssetEditStepModel editStepModel;
@Override
public Class<? extends MvcAssetEditStep> getStepClass() {
return VideoAssetEditStep.class;
}
@Override
protected void init() throws ContentSectionNotFoundException,
AssetNotFoundException {
super.init();
if (getAsset() instanceof VideoAsset) {
editStepModel.setDescriptionValues(
getVideoAsset()
.getDescription()
.getValues()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey().toString(),
entry -> entry.getValue()
)
)
);
final Set<Locale> descriptionLocales = getVideoAsset()
.getDescription()
.getAvailableLocales();
editStepModel.setUnusedDescriptionLocales(
globalizationHelper
.getAvailableLocales()
.stream()
.filter(locale -> !descriptionLocales.contains(locale))
.map(Locale::toString)
.collect(Collectors.toList())
);
editStepModel.setFileName(getVideoAsset().getFileName());
editStepModel.setMimeType(
Optional
.ofNullable(getVideoAsset().getMimeType())
.map(MimeType::toString)
.orElse("")
);
editStepModel.setSize(getVideoAsset().getSize());
final long size = getVideoAsset().getSize();
if (size < 2048) {
editStepModel.setSizeLabel(String.format("%d Bytes", size));
} else if (size < 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d kB", size / 1024)
);
} else if (size < 1024 * 1024 * 1024) {
editStepModel.setSizeLabel(
String.format("%d MB", size / (1024 * 1024))
);
} else {
editStepModel.setSizeLabel(
String.format("%d GB", size / (1024 * 1024 * 1024))
);
}
editStepModel.setLegalMetadata(getVideoAsset().getLegalMetadata());
} else {
throw new AssetNotFoundException(
assetUi.showAssetNotFound(
getContentSection(), getAssetPath()
),
String.format(
"No asset for path %s found in section %s.",
getAssetPath(),
getContentSection().getLabel()
)
);
}
}
@GET
@Path("/")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
@Override
public String showStep(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
return "org/librecms/ui/contentsection/assets/videoasset/edit-videoasset.xhtml";
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/add")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String addDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@FormParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final VideoAsset asset = getVideoAsset();
asset.getDescription().addValue(locale, value);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/edit/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String editDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam,
@FormParam("value") final String value
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final VideoAsset asset = getVideoAsset();
asset.getDescription().addValue(locale, value);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/description/remove/{locale}")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeDescription(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
@PathParam("locale") final String localeParam
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final Locale locale = new Locale(localeParam);
final VideoAsset asset = getVideoAsset();
asset.getDescription().removeValue(locale);
assetRepo.save(asset);
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
@POST
@Path("/legalmetadata")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String setLegalMetadata(
@FormParam("legalMetadataIdentifier")
final String legalMetadataIdentifier
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
final Identifier identifier = identifierParser
.parseIdentifier(legalMetadataIdentifier);
final Optional<LegalMetadata> legalMetadataResult;
switch (identifier.getType()) {
case ID:
legalMetadataResult = assetRepo.findById(
Long.parseLong(identifier.getIdentifier()),
LegalMetadata.class
);
break;
case UUID:
legalMetadataResult = assetRepo.findByUuidAndType(
identifier.getIdentifier(),
LegalMetadata.class
);
break;
default:
legalMetadataResult = assetRepo
.findByPath(identifier.getIdentifier())
.map(result -> (LegalMetadata) result);
break;
}
if (!legalMetadataResult.isPresent()) {
return showLegalMetadataNotFound(legalMetadataIdentifier);
}
final LegalMetadata legalMetadata = legalMetadataResult.get();
getVideoAsset().setLegalMetadata(legalMetadata);
assetRepo.save(getVideoAsset());
return buildRedirectPathForStep();
}
@POST
@Path("/legalmetadata/@remove")
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String removeLegalMetadata() {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
getVideoAsset().setLegalMetadata(null);
assetRepo.save(getVideoAsset());
return buildRedirectPathForStep();
}
private String showLegalMetadataNotFound(
final String legalMetadataIdentifer
) {
models.put("legalMetadataIdentifier", legalMetadataIdentifer);
return "org/librecms/ui/contentsection/assets/external-video-asset/legal-metadata-not-found.xhtml";
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
public String uploadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath,
final MultipartFormDataInput input
) {
try {
init();
} catch (ContentSectionNotFoundException ex) {
return ex.showErrorMessage();
} catch (AssetNotFoundException ex) {
return ex.showErrorMessage();
}
if (assetPermissionsChecker.canEditAsset(getAsset())) {
final VideoAsset asset = getVideoAsset();
final Map<String, List<InputPart>> uploadForm = input
.getFormDataMap();
final List<InputPart> inputParts = uploadForm.get("fileData");
String fileName = "";
String contentType = "";
for (final InputPart inputPart : inputParts) {
try {
final MultivaluedMap<String, String> headers = inputPart
.getHeaders();
fileName = getFileName(headers);
contentType = getContentType(headers);
dataService.saveData(
asset,
inputPart.getBody(InputStream.class, null),
fileName,
contentType
);
} catch (IOException | UnexpectedErrorException ex) {
LOGGER.error(
"Failed to upload file for FileAsset {}:", assetPath
);
LOGGER.error(ex);
models.put("uploadFailed", true);
return buildRedirectPathForStep();
}
}
return buildRedirectPathForStep();
} else {
return assetUi.showAccessDenied(
getContentSection(),
getAsset(),
messageBundle.get("asset.edit.denied"));
}
}
public VideoAsset getVideoAsset() {
return (VideoAsset) getAsset();
}
private String getFileName(final MultivaluedMap<String, String> headers) {
final String[] contentDisposition = headers
.getFirst("Content-Disposition")
.split(";");
for (final String fileName : contentDisposition) {
if (fileName.trim().startsWith("filename")) {
final String[] name = fileName.split("=");
return name[1].trim().replaceAll("\"", "");
}
}
return "";
}
private String getContentType(
final MultivaluedMap<String, String> headers
) {
return headers.getFirst("Content-Type");
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.libreccm.security.AuthorizationRequired;
import org.librecms.assets.VideoAsset;
import org.librecms.assets.BinaryAssetDataService;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.ui.contentsections.ContentSectionsUi;
import java.io.IOException;
import java.io.OutputStream;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path(MvcAssetEditSteps.PATH_PREFIX + "videoasset-edit-download")
public class VideoAssetEditStepDownload {
@Inject
private AssetRepository assetRepo;
@Inject
private BinaryAssetDataService dataService;
@Inject
private ContentSectionsUi sectionsUi;
@GET
@Path("/")
@AuthorizationRequired
public Response downloadFile(
@PathParam(MvcAssetEditSteps.SECTION_IDENTIFIER_PATH_PARAM)
final String sectionIdentifier,
@PathParam(MvcAssetEditSteps.ASSET_PATH_PATH_PARAM_NAME)
final String assetPath
) {
final ContentSection contentSection = sectionsUi
.findContentSection(sectionIdentifier)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"ContentSection %s not found.",
sectionIdentifier
)
).build()
)
);
final Asset asset = assetRepo
.findByPath(contentSection, assetPath)
.orElseThrow(
() -> new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No asset for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
)
);
if (!(asset instanceof VideoAsset)) {
throw new WebApplicationException(
Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"No asset for path %s found in section %s.",
assetPath,
contentSection.getLabel()
)
)
.build()
);
}
final VideoAsset videoAsset = (VideoAsset) asset;
return Response
.ok()
.entity(
new StreamingOutput() {
@Override
public void write(final OutputStream outputStream)
throws IOException, WebApplicationException {
dataService.copyDataToOutputStream(
videoAsset, outputStream
);
}
})
.header("Content-Type", videoAsset.getMimeType())
.header(
"Content-Disposition",
String.format(
"attachment; filename=\"%s\"",
videoAsset.getFileName()
)
)
.build();
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2021 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.ui.contentsections.assets;
import org.librecms.assets.LegalMetadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Named("CmsVideoAssetEditStepModel")
public class VideoAssetEditStepModel {
private Map<String, String> descriptionValues;
private List<String> unusedDescriptionLocales;
private String fileName;
private String mimeType;
private long size;
private String sizeLabel;
private LegalMetadata legalMetadata;
public Map<String, String> getDescriptionValues() {
return Collections.unmodifiableMap(descriptionValues);
}
protected void setDescriptionValues(
final Map<String, String> descriptionValues
) {
this.descriptionValues = new HashMap<>(descriptionValues);
}
public List<String> getUnusedDescriptionLocales() {
return Collections.unmodifiableList(unusedDescriptionLocales);
}
protected void setUnusedDescriptionLocales(
final List<String> descriptionLocales
) {
this.unusedDescriptionLocales = new ArrayList<>(descriptionLocales);
}
public String getFileName() {
return fileName;
}
protected void setFileName(final String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
return mimeType;
}
protected void setMimeType(final String mimeType) {
this.mimeType = mimeType;
}
public long getSize() {
return size;
}
protected void setSize(final long size) {
this.size = size;
}
public String getSizeLabel() {
return sizeLabel;
}
protected void setSizeLabel(final String sizeLabel) {
this.sizeLabel = sizeLabel;
}
public LegalMetadata getLegalMetadata() {
return legalMetadata;
}
protected void setLegalMetadata(final LegalMetadata legalMetadata) {
this.legalMetadata = legalMetadata;
}
public String getLegalMetadataType() {
return LegalMetadata.class.getName();
}
}

View File

@ -388,3 +388,15 @@ createform.externalvideoasset.url.label=URL
createform.externalvideoasset.description.help=A short description of the contents of the video.
createform.externalvideoasset.description.label=Description
fileasset.editstep.file.download.button.label=Download File
image.description=An asset for managing images.
image.label=Image
image.editstep.label=Edit image
image.editstep.description=Edit image
audioasset.description=Asset for managing audio files
audioasset.label=Audio
videoasset.label=Video
videoasset.description=An asset for videos.
audioasset.editstep.label=Edit audio asset
audioasset.editstep.description=Edit audio asset
videoasset.editstep.description=Edit video asset
videoasset.editstep.label=Edit video asset

View File

@ -388,3 +388,15 @@ createform.externalvideoasset.url.label=URL
createform.externalvideoasset.description.help=Eine kurze Beschreibung des Inhaltes des Videos.
createform.externalvideoasset.description.label=Beschreibung
fileasset.editstep.file.download.button.label=Datei herunterladen
image.description=Asset f\u00fcr Bilder.
image.label=Bild
image.editstep.label=Bild bearbeiten
image.editstep.description=Bild bearbeiten
audioasset.description=Asset f\u00fcr Audio-Dateien
audioasset.label=Audio
videoasset.label=Video
videoasset.description=Asset f\u00fcr Videos.
audioasset.editstep.label=Audio Asset bearbeiten
audioasset.editstep.description=Audio Asset bearbeiten
videoasset.editstep.description=Video Asset bearbeiten
videoasset.editstep.label=Video Asset bearbeiten