From 43943e1c4683d6dd8e01858fa876bfba3f766fe8 Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Wed, 15 Dec 2021 20:41:07 +0100 Subject: [PATCH] MVC edit views for MultiPartArticle part II --- ccm-cms/pom.xml | 5 + .../article/MvcArticleTextBodyStep.java | 1 - .../mpa/MpaSectionStepService.java | 95 ++++- .../mpa/MpaSectionsStepModel.java | 14 + .../contenttypes/mpa/MpaSectionsTableRow.java | 89 +++++ .../ui/contenttypes/mpa/MvcMpaCreateStep.java | 1 - .../contenttypes/mpa/MvcMpaSectionsStep.java | 337 +++++++++++++++++- pom.xml | 5 + 8 files changed, 534 insertions(+), 13 deletions(-) create mode 100644 ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsTableRow.java diff --git a/ccm-cms/pom.xml b/ccm-cms/pom.xml index ff021d971..49a8c9740 100644 --- a/ccm-cms/pom.xml +++ b/ccm-cms/pom.xml @@ -122,6 +122,11 @@ org.jsoup jsoup + + + org.apache.commons + commons-lang3 + junit diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/article/MvcArticleTextBodyStep.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/article/MvcArticleTextBodyStep.java index 4a6ad528e..aaef37a9b 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/article/MvcArticleTextBodyStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/article/MvcArticleTextBodyStep.java @@ -18,7 +18,6 @@ */ package org.librecms.ui.contenttypes.article; -import com.arsdigita.kernel.KernelConfig; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionStepService.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionStepService.java index 8c8697612..7a9444fb5 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionStepService.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionStepService.java @@ -18,13 +18,23 @@ */ package org.librecms.ui.contenttypes.mpa; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemRepository; +import org.librecms.contentsection.ContentSection; +import org.librecms.contenttypes.MultiPartArticle; +import org.librecms.contenttypes.MultiPartArticleSection; +import org.librecms.contenttypes.MultiPartArticleSectionRepository; +import org.librecms.ui.contentsections.ContentSectionsUi; import org.librecms.ui.contentsections.documents.MvcAuthoringSteps; import java.util.List; import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; import javax.transaction.Transactional; +import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; +import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -39,6 +49,15 @@ import javax.ws.rs.core.Response; @Path(MvcAuthoringSteps.PATH_PREFIX + "mpa-sections-service") public class MpaSectionStepService { + @Inject + private ContentItemRepository itemRepo; + + @Inject + private MultiPartArticleSectionRepository sectionRepo; + + @Inject + private ContentSectionsUi sectionsUi; + @POST @Path("/save-order") @Consumes(MediaType.APPLICATION_JSON) @@ -50,8 +69,80 @@ public class MpaSectionStepService { final String documentPath, final List sectionsOrder ) { - // ToDo - throw new UnsupportedOperationException(); + final ContentSection contentSection = sectionsUi + .findContentSection(sectionIdentifier) + .orElseThrow( + () -> new NotFoundException( + String.format( + "No content identifed by %s found.", + sectionIdentifier + ) + ) + ); + + final ContentItem document = itemRepo + .findByPath(contentSection, documentPath) + .orElseThrow( + () -> new NotFoundException( + String.format( + "No document for path %s in section %s.", + documentPath, + contentSection.getLabel() + ) + ) + ); + + if ((document instanceof MultiPartArticle)) { + throw new BadRequestException( + String.format( + "Document %s is not a %s.", + documentPath, + MultiPartArticle.class.getSimpleName() + ) + ); + } + + final MultiPartArticle mpa = (MultiPartArticle) document; + + final List sections = mpa.getSections(); + + if (sectionsOrder.size() != sections.size()) { + throw new BadRequestException( + String.format( + "Number of sections of the MultiPartArticle %s does " + + "not match the number of values in the oder " + + "list. Number of sections: %d, number of values in " + + "the sections order list: %d", + documentPath, + sections.size(), + sectionsOrder.size() + ) + ); + } + + for (int i = 0; i < sectionsOrder.size(); i++) { + final String sectionIdParam = sectionsOrder.get(i); + final long sectionId = Long.parseLong(sectionIdParam); + final MultiPartArticleSection section = sections + .stream() + .filter(sec -> sec.getSectionId() == sectionId) + .findAny() + .orElseThrow( + () -> new BadRequestException( + String.format( + "sectionsOrder has an entry for section with " + + "ID %d, but there is not section with that " + + "ID.", + sectionId + ) + ) + ); + + section.setRank(i); + sectionRepo.save(section); + } + + return Response.ok().build(); } } diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsStepModel.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsStepModel.java index ed0bb9ebf..f5f4adee3 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsStepModel.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsStepModel.java @@ -18,6 +18,10 @@ */ package org.librecms.ui.contenttypes.mpa; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import javax.enterprise.context.RequestScoped; import javax.inject.Named; @@ -31,6 +35,8 @@ public class MpaSectionsStepModel { private boolean canEdit; + private List rows; + public boolean getCanEdit() { return canEdit; } @@ -39,4 +45,12 @@ public class MpaSectionsStepModel { this.canEdit = canEdit; } + public List getRows() { + return Collections.unmodifiableList(rows); + } + + protected void setRows(final List rows) { + this.rows = new ArrayList<>(rows); + } + } diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsTableRow.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsTableRow.java new file mode 100644 index 000000000..6e85e9362 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MpaSectionsTableRow.java @@ -0,0 +1,89 @@ +/* + * 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.contenttypes.mpa; + +import java.util.Comparator; + +/** + * + * @author Jens Pelzetter + */ +public class MpaSectionsTableRow implements Comparable { + + private long sectionId; + + private String title; + + private int rank; + + private boolean pageBreak; + + private String textPreview; + + public long getSectionId() { + return sectionId; + } + + protected void setSectionId(final long sectionId) { + this.sectionId = sectionId; + } + + public String getTitle() { + return title; + } + + protected void setTitle(final String title) { + this.title = title; + } + + public int getRank() { + return rank; + } + + protected void setRank(final int rank) { + this.rank = rank; + } + + public boolean isPageBreak() { + return pageBreak; + } + + protected void setPageBreak(final boolean pageBreak) { + this.pageBreak = pageBreak; + } + + public String getTextPreview() { + return textPreview; + } + + protected void setTextPreview(final String textPreview) { + this.textPreview = textPreview; + } + + @Override + public int compareTo(final MpaSectionsTableRow other) { + return Comparator + .comparing(MpaSectionsTableRow::getRank) + .thenComparing( + Comparator.comparing(MpaSectionsTableRow::getTitle) + ) + .compare(this, other); + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaCreateStep.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaCreateStep.java index 6dbcdb495..920dd725e 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaCreateStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaCreateStep.java @@ -35,7 +35,6 @@ import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; -import javax.mvc.Models; import javax.transaction.Transactional; /** diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaSectionsStep.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaSectionsStep.java index aa5be86b6..46dc39f51 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaSectionsStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/mpa/MvcMpaSectionsStep.java @@ -18,7 +18,11 @@ */ package org.librecms.ui.contenttypes.mpa; +import org.apache.commons.lang3.StringUtils; +import org.libreccm.l10n.GlobalizationHelper; import org.librecms.contenttypes.MultiPartArticle; +import org.librecms.contenttypes.MultiPartArticleSection; +import org.librecms.contenttypes.MultiPartArticleSectionManager; import org.librecms.ui.contentsections.ContentSectionNotFoundException; import org.librecms.ui.contentsections.ItemPermissionChecker; import org.librecms.ui.contentsections.documents.AbstractMvcAuthoringStep; @@ -27,10 +31,18 @@ import org.librecms.ui.contentsections.documents.DocumentUi; import org.librecms.ui.contentsections.documents.MvcAuthoringStepDef; import org.librecms.ui.contentsections.documents.MvcAuthoringSteps; +import java.util.Comparator; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + 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.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -59,9 +71,18 @@ public class MvcMpaSectionsStep extends AbstractMvcAuthoringStep { @Inject private DocumentUi documentUi; + @Inject + private GlobalizationHelper globalizationHelper; + @Inject private ItemPermissionChecker itemPermissionChecker; + @Inject + private MultiPartArticleSectionManager sectionManager; + + @Inject + private Models models; + @Inject private MpaSectionsStepModel mpaSectionsStepModel; @@ -88,6 +109,15 @@ public class MvcMpaSectionsStep extends AbstractMvcAuthoringStep { } if (itemPermissionChecker.canEditItem(getMpa())) { + mpaSectionsStepModel.setRows( + getMpa() + .getSections() + .stream() + .map(this::buildMpaSectionsTableRow) + .sorted() + .collect(Collectors.toList()) + ); + return "org/librecms/ui/contenttypes/mpa/mpa-sections.xhtml"; } else { return documentUi.showAccessDenied( @@ -99,34 +129,293 @@ public class MvcMpaSectionsStep extends AbstractMvcAuthoringStep { } @POST - @Path("/add") + @Path("/@add") @Transactional(Transactional.TxType.REQUIRED) public String addSection( @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) final String sectionIdentifier, @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) final String documentPath, - @FormParam("initialLocal") + @FormParam("initialLocal") @DefaultValue("") final String initialLocaleParam, - @FormParam("title") + @FormParam("title") @DefaultValue("") final String title, - @FormParam("text") + @FormParam("text") @DefaultValue("") final String text + ) { + try { + init(); + } catch (ContentSectionNotFoundException ex) { + return ex.showErrorMessage(); + } catch (DocumentNotFoundException ex) { + return ex.showErrorMessage(); + } + + if (itemPermissionChecker.canEditItem(getMpa())) { + if (initialLocaleParam.isBlank()) { + models.put("initialLocaleMissing", true); + models.put("title", title); + models.put("text", text); + return showStep(sectionIdentifier, documentPath); + } + if (title.isBlank()) { + models.put("titleMissing", true); + models.put("initialLocale", initialLocaleParam); + models.put("text", text); + + return showStep(sectionIdentifier, documentPath); + } + + final MultiPartArticleSection section + = new MultiPartArticleSection(); + section.setPageBreak(false); + section.setRank( + getMpa() + .getSections() + .stream() + .map(MultiPartArticleSection::getRank) + .max(Comparator.naturalOrder()) + .orElse(0) + 1 + ); + + final Locale initialLocale = new Locale(initialLocaleParam); + + section.getText().addValue(initialLocale, text); + section.getTitle().addValue(initialLocale, title); + + sectionManager.addSectionToMultiPartArticle(section, getMpa()); + + return buildRedirectPathForStep(); + } else { + return documentUi.showAccessDenied( + getContentSection(), + getMpa(), + mpaMessageBundle.getMessage("mpa.edit.denied") + ); + } + } + + @POST + @Path("/@remove/{sectionId}") + @Transactional(Transactional.TxType.REQUIRED) + public String removeSection( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam + ) { + try { + init(); + } catch (ContentSectionNotFoundException ex) { + return ex.showErrorMessage(); + } catch (DocumentNotFoundException ex) { + return ex.showErrorMessage(); + } + + if (itemPermissionChecker.canEditItem(getMpa())) { + if (!sectionIdParam.matches("[0-9]*")) { + models.put("invalidSectionId", true); + return showStep(sectionIdentifier, documentPath); + } + + final long sectionId = Long.parseLong(sectionIdParam); + + final Optional result + = getMpa() + .getSections() + .stream() + .filter(section -> section.getSectionId() == sectionId) + .findAny(); + + if (!result.isPresent()) { + models.put("sectionNotFound", true); + return showStep(sectionIdentifier, documentPath); + } + + final MultiPartArticleSection sectionToRemove = result.get(); + + sectionManager.removeSectionFromMultiPartArticle( + sectionToRemove, getMpa() + ); + + return buildRedirectPathForStep(); + } else { + return documentUi.showAccessDenied( + getContentSection(), + getMpa(), + mpaMessageBundle.getMessage("mpa.edit.denied") + ); + } + } + + @POST + @Path("/{sectionId}") + @Transactional(Transactional.TxType.REQUIRED) + public String showSection( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam ) { // ToDo throw new UnsupportedOperationException(); } @POST - @Path("/remove/{sectionId}") + @Path("/{sectionId}/title/@add") @Transactional(Transactional.TxType.REQUIRED) - public String removeSection( - @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + public String addTitleValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) final String sectionIdentifier, @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) final String documentPath, - @PathParam("sectionId") - final String sectionIdParam + @PathParam("sectionId") + final String sectionIdParam, + @FormParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @GET + @Path("/{sectionId}/title/@edit/{locale}") + @Transactional(Transactional.TxType.REQUIRED) + public String editTitleValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @PathParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/title/@edit/{locale}") + @Transactional(Transactional.TxType.REQUIRED) + public String editTitleValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @PathParam("locale") + final String localeParam, + @FormParam("value") + final String value + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/title/@remove") + @Transactional(Transactional.TxType.REQUIRED) + public String removeTitleValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @FormParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/text/@add") + @Transactional(Transactional.TxType.REQUIRED) + public String addTextValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @FormParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @GET + @Path("/{sectionId}/text/@edit/{locale}") + @Transactional(Transactional.TxType.REQUIRED) + public String editTextValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @PathParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/text/@edit/{locale}") + @Transactional(Transactional.TxType.REQUIRED) + public String editTextValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @PathParam("locale") + final String localeParam, + @FormParam("value") + final String value + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/text/@remove") + @Transactional(Transactional.TxType.REQUIRED) + public String removeTextValue( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @FormParam("locale") + final String localeParam + ) { + // ToDo + throw new UnsupportedOperationException(); + } + + @POST + @Path("/{sectionId}/pagebreak") + @Transactional(Transactional.TxType.REQUIRED) + public String setPageBreak( + @PathParam(MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM) + final String sectionIdentifier, + @PathParam(MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME) + final String documentPath, + @PathParam("sectionId") + final String sectionIdParam, + @FormParam("pageBreak") final String pageBreakParam ) { // ToDo throw new UnsupportedOperationException(); @@ -149,4 +438,34 @@ public class MvcMpaSectionsStep extends AbstractMvcAuthoringStep { return (MultiPartArticle) getDocument(); } + private MpaSectionsTableRow buildMpaSectionsTableRow( + final MultiPartArticleSection section + ) { + final MpaSectionsTableRow row = new MpaSectionsTableRow(); + row.setPageBreak(section.isPageBreak()); + row.setRank(section.getRank()); + row.setSectionId(section.getSectionId()); + final String text = Objects.requireNonNullElse( + globalizationHelper.getValueFromLocalizedString(section.getText()), + "" + ); + if (text.length() <= 100) { + row.setTextPreview(text); + } else { + row.setTextPreview( + String.format( + "%s...", + StringUtils.truncate(text, 97) + ) + ); + } + row.setTitle( + globalizationHelper.getValueFromLocalizedString( + section.getText() + ) + ); + + return row; + } + } diff --git a/pom.xml b/pom.xml index 7691c31d0..b4ff45e41 100644 --- a/pom.xml +++ b/pom.xml @@ -708,6 +708,11 @@ commons-lang 2.6 + + org.apache.commons + commons-lang3 + 3.12.0 + commons-logging commons-logging