From c9e4c4d57252814cb46c47cd2aa747889f6c51ac Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Mon, 22 Jun 2020 11:54:31 +0200 Subject: [PATCH] OO-4740: support inline images in forum --- .../collaboration/CollaborationTools.java | 66 --- .../form/flexible/FormUIFactory.java | 12 +- .../richText/RichTextConfiguration.java | 20 +- .../richText/RichTextContainerMapper.java | 70 +++ .../PersistingCourseGroupManager.java | 2 +- .../olat/course/nodes/DialogCourseNode.java | 33 +- .../org/olat/course/nodes/FOCourseNode.java | 14 +- .../org/olat/group/BusinessGroupService.java | 3 +- .../manager/BusinessGroupImportExport.java | 12 +- .../manager/BusinessGroupServiceImpl.java | 4 +- .../modules/fo/archiver/ForumArchive.java | 275 ++++++++++ .../fo/archiver/ForumArchiveManager.java | 204 -------- .../formatters/ForumDownloadResource.java | 65 +-- .../archiver/formatters/ForumFormatter.java | 117 ----- .../formatters/ForumOpenXMLFormatter.java | 36 +- .../formatters/ForumRTFFormatter.java | 468 ------------------ .../formatters/ForumStreamedRTFFormatter.java | 418 ---------------- .../olat/modules/fo/manager/ForumManager.java | 26 +- .../olat/modules/fo/manager/QuoterFilter.java | 103 ++++ .../modules/fo/ui/MessageEditController.java | 295 +++++++---- .../modules/fo/ui/MessageListController.java | 166 ++++--- .../org/olat/modules/fo/ui/MessageView.java | 14 +- .../modules/fo/ui/ThreadListController.java | 4 +- 23 files changed, 855 insertions(+), 1572 deletions(-) create mode 100644 src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextContainerMapper.java create mode 100644 src/main/java/org/olat/modules/fo/archiver/ForumArchive.java delete mode 100644 src/main/java/org/olat/modules/fo/archiver/ForumArchiveManager.java delete mode 100644 src/main/java/org/olat/modules/fo/archiver/formatters/ForumFormatter.java delete mode 100644 src/main/java/org/olat/modules/fo/archiver/formatters/ForumRTFFormatter.java delete mode 100644 src/main/java/org/olat/modules/fo/archiver/formatters/ForumStreamedRTFFormatter.java create mode 100644 src/main/java/org/olat/modules/fo/manager/QuoterFilter.java diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java index 2641f530d79..d60f116dc9f 100644 --- a/src/main/java/org/olat/collaboration/CollaborationTools.java +++ b/src/main/java/org/olat/collaboration/CollaborationTools.java @@ -25,8 +25,6 @@ package org.olat.collaboration; -import java.io.File; -import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -59,20 +57,15 @@ import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.AssertException; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; -import org.olat.core.util.FileUtils; import org.olat.core.util.Util; -import org.olat.core.util.ZipUtil; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.SyncerCallback; import org.olat.core.util.coordinate.SyncerExecutor; -import org.olat.core.util.i18n.I18nModule; import org.olat.core.util.mail.ContactMessage; -import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.NamedContainerImpl; import org.olat.core.util.vfs.Quota; import org.olat.core.util.vfs.QuotaManager; import org.olat.core.util.vfs.VFSContainer; -import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; import org.olat.course.CorruptedCourseException; @@ -94,9 +87,6 @@ import org.olat.modules.co.ContactFormController; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.ForumUIFactory; -import org.olat.modules.fo.archiver.ForumArchiveManager; -import org.olat.modules.fo.archiver.formatters.ForumFormatter; -import org.olat.modules.fo.archiver.formatters.ForumRTFFormatter; import org.olat.modules.fo.manager.ForumManager; import org.olat.modules.openmeetings.OpenMeetingsModule; import org.olat.modules.openmeetings.manager.OpenMeetingsException; @@ -117,7 +107,6 @@ import org.olat.modules.wiki.DryRunAssessmentProvider; import org.olat.modules.wiki.WikiManager; import org.olat.modules.wiki.WikiSecurityCallback; import org.olat.modules.wiki.WikiSecurityCallbackImpl; -import org.olat.modules.wiki.WikiToZipUtils; import org.olat.properties.NarrowedPropertyManager; import org.olat.properties.Property; import org.olat.properties.PropertyManager; @@ -1022,61 +1011,6 @@ public class CollaborationTools implements Serializable { } } - /** - * It is assumed that this is only called by an administrator - * (e.g. at deleteGroup) - * @param archivFilePath - */ - public void archive(String archivFilePath) { - if (isToolEnabled(CollaborationTools.TOOL_FORUM)) { - archiveForum(archivFilePath); - } - if (isToolEnabled(CollaborationTools.TOOL_WIKI)) { - archiveWiki(archivFilePath); - } - if (isToolEnabled(CollaborationTools.TOOL_FOLDER)) { - archiveFolder(archivFilePath); - } - } - - private void archiveForum(String archivFilePath) { - Property forumKeyProperty = NarrowedPropertyManager.getInstance(ores).findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FORUM); - if (forumKeyProperty != null) { - VFSContainer archiveContainer = new LocalFolderImpl(new File(archivFilePath)); - String archiveForumName = "del_forum_" + forumKeyProperty.getLongValue(); - VFSContainer archiveForumContainer = archiveContainer.createChildContainer(archiveForumName); - ForumFormatter ff = new ForumRTFFormatter(archiveForumContainer, false, I18nModule.getDefaultLocale()); - CoreSpringFactory.getImpl(ForumArchiveManager.class).applyFormatter(ff, forumKeyProperty.getLongValue(), null); - } - } - - private void archiveWiki(String archivFilePath) { - VFSContainer wikiContainer = WikiManager.getInstance().getWikiRootContainer(ores); - VFSLeaf wikiZip = WikiToZipUtils.getWikiAsZip(wikiContainer); - String exportFileName = "del_wiki_" + ores.getResourceableId() + ".zip"; - File archiveDir = new File(archivFilePath); - if (!archiveDir.exists()) { - archiveDir.mkdir(); - } - String fullFilePath = archivFilePath + File.separator + exportFileName; - - try { - FileUtils.bcopy(wikiZip.getInputStream(), new File(fullFilePath), "archive wiki"); - } catch (IOException ioe) { - log.warn("Can not archive wiki repoEntry={}", ores.getResourceableId()); - } - } - - private void archiveFolder(String archiveFilePath) { - LocalFolderImpl folderContainer = VFSManager.olatRootContainer(getFolderRelPath(), null); - File fFolderRoot = folderContainer.getBasefile(); - if (fFolderRoot.exists()) { - String zipFileName = "del_folder_" + ores.getResourceableId() + ".zip"; - String fullZipFilePath = archiveFilePath + File.separator + zipFileName; - ZipUtil.zipAll(fFolderRoot, new File(fullZipFilePath), true); - } - } - /** * whole object gets cached, if tool gets added or deleted the object becomes dirty and will be removed from cache. * @return diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java b/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java index 774d2cfa86d..c654ce7e746 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java @@ -775,20 +775,26 @@ public class FormUIFactory { * text element * @param usess The user session that dispatches the images * @param wControl the current window controller - * @param wControl - * the current window controller + * @return The rich text element instance */ public RichTextElement addRichTextElementForStringData(String name, String i18nLabel, String initialHTMLValue, int rows, int cols, boolean fullProfile, VFSContainer baseContainer, CustomLinkTreeModel customLinkTreeModel, FormItemContainer formLayout, UserSession usess, WindowControl wControl) { + return addRichTextElementForStringData(name, i18nLabel, initialHTMLValue, rows, cols, + fullProfile, baseContainer, null, customLinkTreeModel, formLayout, usess, wControl); + } + + public RichTextElement addRichTextElementForStringData(String name, String i18nLabel, String initialHTMLValue, int rows, + int cols, boolean fullProfile, VFSContainer baseContainer, String relFilePath, CustomLinkTreeModel customLinkTreeModel, + FormItemContainer formLayout, UserSession usess, WindowControl wControl) { // Create richt text element with bare bone configuration WindowBackOffice backoffice = wControl.getWindowBackOffice(); RichTextElement rte = new RichTextElementImpl(name, initialHTMLValue, rows, cols, formLayout.getRootForm(), formLayout.getTranslator().getLocale()); setLabelIfNotNull(i18nLabel, rte); // Now configure editor Theme theme = backoffice.getWindow().getGuiTheme(); - rte.getEditorConfiguration().setConfigProfileFormEditor(fullProfile, usess, theme, baseContainer, customLinkTreeModel); + rte.getEditorConfiguration().setConfigProfileFormEditor(fullProfile, usess, theme, baseContainer, relFilePath, customLinkTreeModel); // Add to form and finish formLayout.add(rte); return rte; diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java index 1e0704d5b66..aaf1e0ca22f 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java @@ -47,6 +47,7 @@ import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; @@ -315,7 +316,7 @@ public class RichTextConfiguration implements Disposable { * @param customLinkTreeModel */ public void setConfigProfileFormEditor(boolean fullProfile, UserSession usess, Theme guiTheme, - VFSContainer baseContainer, CustomLinkTreeModel customLinkTreeModel) { + VFSContainer baseContainer, String relFilePath, CustomLinkTreeModel customLinkTreeModel) { setConfigBasics(guiTheme); TinyMCECustomPluginFactory customPluginFactory = CoreSpringFactory.getImpl(TinyMCECustomPluginFactory.class); @@ -339,7 +340,7 @@ public class RichTextConfiguration implements Disposable { tinyConfig = tinyConfig.enableImageAndMedia(); setFileBrowserCallback(baseContainer, customLinkTreeModel, IMAGE_SUFFIXES_VALUES, MEDIA_SUFFIXES_VALUES, FLASH_PLAYER_SUFFIXES_VALUES); // since in form editor mode and not in file mode we use null as relFilePath - setDocumentMediaBase(baseContainer, null, usess); + setDocumentMediaBase(baseContainer, relFilePath, usess); } } @@ -809,12 +810,15 @@ public class RichTextConfiguration implements Disposable { private void setDocumentMediaBase(final VFSContainer documentBaseContainer, String relFilePath, UserSession usess) { linkBrowserRelativeFilePath = relFilePath; // get a usersession-local mapper for the file storage (and tinymce's references to images and such) - Mapper contentMapper = new VFSContainerMapper(documentBaseContainer); + Mapper contentMapper; + if(StringHelper.containsNonWhitespace(relFilePath)) { + contentMapper = new RichTextContainerMapper(documentBaseContainer, relFilePath); + } else { + contentMapper = new VFSContainerMapper(documentBaseContainer); + } + // Register mapper for this user. This mapper is cleaned up in the // dispose method (RichTextElementImpl will clean it up) - - - // Register mapper as cacheable String mapperID = VFSManager.getRealPath(documentBaseContainer); if (mapperID == null) { @@ -829,7 +833,7 @@ public class RichTextConfiguration implements Disposable { if (relFilePath != null) { // remove filename, path must end with slash - int lastSlash = relFilePath.lastIndexOf("/"); + int lastSlash = relFilePath.lastIndexOf('/'); if (lastSlash == -1) { relFilePath = ""; } else if (lastSlash + 1 < relFilePath.length()) { @@ -841,7 +845,7 @@ public class RichTextConfiguration implements Disposable { LocalFolderImpl folder = (LocalFolderImpl) documentBaseContainer; containerPath = folder.getBasefile().getAbsolutePath(); } - log.warn("Could not parse relative file path::" + relFilePath + " in container::" + containerPath); + log.warn("Could not parse relative file path::{} in container::{}", relFilePath, containerPath); } } else { relFilePath = ""; diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextContainerMapper.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextContainerMapper.java new file mode 100644 index 00000000000..c4e5d788123 --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextContainerMapper.java @@ -0,0 +1,70 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.gui.components.form.flexible.impl.elements.richText; + +import javax.servlet.http.HttpServletRequest; + +import org.olat.core.dispatcher.mapper.Mapper; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSMediaResource; + +/** + * + * Initial date: 18 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RichTextContainerMapper implements Mapper { + + private String path; + private final VFSContainer container; + + public RichTextContainerMapper(VFSContainer container, String relFilePath) { + this.container = container; + path = relFilePath; + if(!path.startsWith("/")) { + path = "/" + path; + } + if(!path.endsWith("/")) { + path += "/"; + } + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + VFSItem vfsItem = container.resolve(relPath); + if(vfsItem == null && relPath.startsWith(path)) { + String fallback = relPath.substring(path.length(), relPath.length()); + vfsItem = container.resolve(fallback); + } + + MediaResource mr; + if (vfsItem instanceof VFSLeaf) { + mr = new VFSMediaResource((VFSLeaf) vfsItem); + } else { + mr = new NotFoundMediaResource(); + } + return mr; + } +} diff --git a/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java b/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java index d0754bb7b27..5925e1f0a64 100644 --- a/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java +++ b/src/main/java/org/olat/course/groupsandrights/PersistingCourseGroupManager.java @@ -375,7 +375,7 @@ public class PersistingCourseGroupManager implements CourseGroupManager { BusinessGroupEnvironment bgEnv = new BusinessGroupEnvironment(); bgEnv.getGroups().addAll(courseEnv.getGroups()); bgEnv.getAreas().addAll(courseEnv.getAreas()); - businessGroupService.exportGroups(groups, areas, fExportFile, false); + businessGroupService.exportGroups(groups, areas, fExportFile); } /** diff --git a/src/main/java/org/olat/course/nodes/DialogCourseNode.java b/src/main/java/org/olat/course/nodes/DialogCourseNode.java index dfe2f94e38b..e427085f1ad 100644 --- a/src/main/java/org/olat/course/nodes/DialogCourseNode.java +++ b/src/main/java/org/olat/course/nodes/DialogCourseNode.java @@ -26,11 +26,13 @@ package org.olat.course.nodes; import java.io.File; +import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.zip.ZipOutputStream; +import org.apache.logging.log4j.Logger; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.SubscriptionContext; @@ -39,6 +41,7 @@ import org.olat.core.gui.components.stack.BreadcrumbPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.tabbable.TabbableController; +import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.Util; import org.olat.core.util.ZipUtil; @@ -68,10 +71,8 @@ import org.olat.course.run.userview.CourseNodeSecurityCallback; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.ModuleConfiguration; -import org.olat.modules.fo.archiver.ForumArchiveManager; -import org.olat.modules.fo.archiver.formatters.ForumFormatter; -import org.olat.modules.fo.archiver.formatters.ForumRTFFormatter; -import org.olat.modules.fo.archiver.formatters.ForumStreamedRTFFormatter; +import org.olat.modules.fo.Forum; +import org.olat.modules.fo.archiver.ForumArchive; import org.olat.repository.RepositoryEntry; /** @@ -81,6 +82,7 @@ import org.olat.repository.RepositoryEntry; */ public class DialogCourseNode extends AbstractAccessableCourseNode { + private static final Logger log = Tracing.createLoggerFor(DialogCourseNode.class); public static final String TYPE = "dialog"; @SuppressWarnings("deprecation") @@ -266,10 +268,13 @@ public class DialogCourseNode extends AbstractAccessableCourseNode { // don't check quota diaNodeElemExportContainer.setLocalSecurityCallback(new FullAccessCallback()); diaNodeElemExportContainer.copyFrom(dialogFile); - - ForumArchiveManager fam = CoreSpringFactory.getImpl(ForumArchiveManager.class); - ForumFormatter ff = new ForumRTFFormatter(diaNodeElemExportContainer, false, locale); - fam.applyFormatter(ff, element.getForum().getKey(), null); + + try { + ForumArchive archiver = new ForumArchive(element.getForum(), null, locale, null); + archiver.export("Forum.docx", diaNodeElemExportContainer); + } catch (IOException e) { + log.error("", e); + } } @Override @@ -304,10 +309,14 @@ public class DialogCourseNode extends AbstractAccessableCourseNode { for(VFSItem item: forumContainer.getItems(new VFSLeafFilter())) { ZipUtil.addToZip(item, exportDirName, exportStream); } - - ForumArchiveManager fam = CoreSpringFactory.getImpl(ForumArchiveManager.class); - ForumFormatter ff = new ForumStreamedRTFFormatter(exportStream, exportDirName, false, locale); - fam.applyFormatter(ff, element.getForum().getKey(), null); + + try { + Forum forum = element.getForum(); + ForumArchive archiver = new ForumArchive(forum, null, locale, null); + archiver.export("Dialogs.docx", exportDirName, exportStream); + } catch (IOException e) { + log.error("", e); + } } @Override diff --git a/src/main/java/org/olat/course/nodes/FOCourseNode.java b/src/main/java/org/olat/course/nodes/FOCourseNode.java index fe64bd3ce46..8719c7d5007 100644 --- a/src/main/java/org/olat/course/nodes/FOCourseNode.java +++ b/src/main/java/org/olat/course/nodes/FOCourseNode.java @@ -25,6 +25,7 @@ package org.olat.course.nodes; +import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -79,8 +80,7 @@ import org.olat.modules.ModuleConfiguration; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.ForumModule; -import org.olat.modules.fo.archiver.ForumArchiveManager; -import org.olat.modules.fo.archiver.formatters.ForumStreamedRTFFormatter; +import org.olat.modules.fo.archiver.ForumArchive; import org.olat.modules.fo.manager.ForumManager; import org.olat.properties.Property; import org.olat.repository.RepositoryEntry; @@ -457,8 +457,14 @@ public class FOCourseNode extends AbstractAccessableCourseNode { String forumName = "forum_" + Formatter.makeStringFilesystemSave(getShortTitle()) + "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())); forumName = ZipUtil.concat(archivePath, forumName); - ForumStreamedRTFFormatter rtff = new ForumStreamedRTFFormatter(exportStream, forumName, false, locale); - CoreSpringFactory.getImpl(ForumArchiveManager.class).applyFormatter(rtff, forumKey, null); + + try { + Forum forum = loadOrCreateForum(course.getCourseEnvironment()); + ForumArchive archiver = new ForumArchive(forum, null, locale, null); + archiver.export(forumName + ".docx", exportStream); + } catch (IOException e) { + log.error("", e); + } return true; } diff --git a/src/main/java/org/olat/group/BusinessGroupService.java b/src/main/java/org/olat/group/BusinessGroupService.java index cd54c80d567..3e21cc4742a 100644 --- a/src/main/java/org/olat/group/BusinessGroupService.java +++ b/src/main/java/org/olat/group/BusinessGroupService.java @@ -641,8 +641,7 @@ public interface BusinessGroupService { * @param groups * @param fExportFile */ - public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile, - boolean runtimeDatas); + public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile); /** * Import previously exported group definitions. diff --git a/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java b/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java index aab622da7f6..ba6f6db7580 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java @@ -70,7 +70,7 @@ public class BusinessGroupImportExport { this.groupModule = groupModule; } - public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile, boolean runtimeDatas) { + public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile) { if (groups == null || groups.isEmpty()) { return; // nothing to do... says Florian. } @@ -92,13 +92,13 @@ public class BusinessGroupImportExport { root.getGroups().setGroups(new ArrayList<Group>()); for (BusinessGroup group : groups) { String groupName = null; - Group newGroup = exportGroup(fExportFile, group, groupName, runtimeDatas); + Group newGroup = exportGroup(fExportFile, group, groupName); root.getGroups().getGroups().add(newGroup); } saveGroupConfiguration(fExportFile, root); } - private Group exportGroup(File fExportFile, BusinessGroup group, String groupName, boolean runtimeDatas) { + private Group exportGroup(File fExportFile, BusinessGroup group, String groupName) { Group newGroup = new Group(); newGroup.setKey(group.getKey()); newGroup.setName(StringHelper.containsNonWhitespace(groupName) ? groupName : group.getName()); @@ -147,10 +147,8 @@ public class BusinessGroupImportExport { newGroup.setInfo(info.trim()); } - log.debug("fExportFile.getParent()=" + fExportFile.getParent()); - if(runtimeDatas) { - ct.archive(fExportFile.getParent()); - } + log.debug("fExportFile.getParent()={}", fExportFile.getParent()); + // export membership List<BGArea> bgAreas = areaManager.findBGAreasOfBusinessGroup(group); newGroup.setAreaRelations(new ArrayList<String>()); diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java index 6c565d2c0a4..652047904fa 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java @@ -1814,9 +1814,9 @@ public class BusinessGroupServiceImpl implements BusinessGroupService { } @Override - public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile, boolean runtimeDatas) { + public void exportGroups(List<BusinessGroup> groups, List<BGArea> areas, File fExportFile) { BusinessGroupImportExport exporter = new BusinessGroupImportExport(dbInstance, areaManager, this, groupModule); - exporter.exportGroups(groups, areas, fExportFile, runtimeDatas); + exporter.exportGroups(groups, areas, fExportFile); } @Override diff --git a/src/main/java/org/olat/modules/fo/archiver/ForumArchive.java b/src/main/java/org/olat/modules/fo/archiver/ForumArchive.java new file mode 100644 index 00000000000..d279b1a339f --- /dev/null +++ b/src/main/java/org/olat/modules/fo/archiver/ForumArchive.java @@ -0,0 +1,275 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.archiver; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.logging.log4j.Logger; +import org.olat.core.CoreSpringFactory; +import org.olat.core.logging.Tracing; +import org.olat.core.util.ZipUtil; +import org.olat.core.util.io.ShieldOutputStream; +import org.olat.core.util.openxml.DocReference; +import org.olat.core.util.openxml.OpenXMLDocumentWriter; +import org.olat.core.util.tree.TreeVisitor; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; +import org.olat.modules.fo.Forum; +import org.olat.modules.fo.ForumCallback; +import org.olat.modules.fo.Message; +import org.olat.modules.fo.archiver.formatters.ForumOpenXMLFormatter; +import org.olat.modules.fo.manager.ForumManager; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 22 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ForumArchive { + + private static final Logger log = Tracing.createLoggerFor(ForumArchive.class); + + @Autowired + private ForumManager forumManager; + + private final Locale locale; + private final Forum forum; + private final Long topMessageId; + private final ForumCallback forumCallback; + + public ForumArchive(Forum forum, Long topMessageId, Locale locale, ForumCallback forumCallback) { + CoreSpringFactory.autowireObject(this); + this.forum = forum; + this.locale = locale; + this.topMessageId = topMessageId; + this.forumCallback = forumCallback; + } + + public void export(String name, VFSContainer exportDir) + throws IOException { + + Map<File,DocReference> attachments = null; + VFSLeaf forumDoc = exportDir.createChildLeaf(name); + try(OutputStream out = forumDoc.getOutputStream(false)) { + attachments = exportForum(out); + } catch(IOException e) { + log.error("", e); + } + + if(attachments != null && attachments.size() > 0) { + VFSContainer attachmentsContainer = VFSManager.getOrCreateContainer(exportDir, "attachments"); + for(Map.Entry<File,DocReference> attachmentEntry : attachments.entrySet()) { + File attachment = attachmentEntry.getKey(); + DocReference ref = attachmentEntry.getValue(); + VFSLeaf leaf = attachmentsContainer.createChildLeaf(ref.getFilename()); + VFSManager.copyContent(attachment, leaf); + } + } + } + + public void export(String name, ZipOutputStream zout) + throws IOException { + export(name, "", zout); + } + + public void export(String name, String path, ZipOutputStream zout) + throws IOException { + ZipEntry test = new ZipEntry(ZipUtil.concat(path, name)); + zout.putNextEntry(test); + Map<File,DocReference> attachments = null; + try(ShieldOutputStream sOut = new ShieldOutputStream(zout)) { + attachments = exportForum(sOut); + } catch(IOException e) { + log.error("", e); + } + zout.closeEntry(); + + if(attachments != null && attachments.size() > 0) { + for(Map.Entry<File,DocReference> attachmentEntry : attachments.entrySet()) { + File attachment = attachmentEntry.getKey(); + DocReference ref = attachmentEntry.getValue(); + zout.putNextEntry(new ZipEntry(ZipUtil.concat(path, "attachments/" + ref.getFilename()))); + copyShielded(attachment, zout); + zout.closeEntry(); + } + } + } + + private Map<File,DocReference> exportForum(OutputStream out) { + try(ZipOutputStream zout = new ZipOutputStream(out)) { + zout.setLevel(9); + + VFSContainer mediaContainer = forumManager.getForumContainer(forum.getKey()); + + ForumOpenXMLFormatter openXmlFormatter = new ForumOpenXMLFormatter(mediaContainer, locale); + if(topMessageId != null) { + applyFormatterForOneThread(openXmlFormatter, topMessageId); + } else { + applyFormatter(openXmlFormatter); + } + + OpenXMLDocumentWriter writer = new OpenXMLDocumentWriter(); + writer.createDocument(zout, openXmlFormatter.getOpenXMLDocument()); + return openXmlFormatter.getAttachments(); + } catch (Exception e) { + log.error("", e); + return null; + } + } + + private void copyShielded(File attachment, ZipOutputStream zout) { + try(OutputStream out = new ShieldOutputStream(zout)) { + Files.copy(attachment.toPath(), out); + } catch(Exception e) { + log.error("", e); + } + } + + /** + * If the forumCallback is null no restriction applies to the forum archiver. + * (that is it can archive all threads no matter the status) + * @param forumFormatter The formatter + * @return + */ + private void applyFormatter(ForumOpenXMLFormatter forumFormatter) { + log.info("Archiving complete forum: {}", forum); + //convert forum structure to trees + List<MessageNode> threadTreesList = convertToThreadTrees(); + //format forum trees by using the formatter given by the callee + formatForum(threadTreesList, forumFormatter); + } + + /** + * It is assumed that if the caller of this method is allowed to see the forum thread + * starting from topMessageId, then he also has the right to archive it, so no need for a ForumCallback. + * @param forumFormatter The formatter + * @param messageId The root message id + */ + private void applyFormatterForOneThread(ForumOpenXMLFormatter forumFormatter, Long messageId){ + MessageNode topMessageNode = convertToThreadTree(messageId); + formatThread(topMessageNode, forumFormatter); + } + + /** + * If the forumCallback is null no filtering is executed, + * else if a thread is hidden and the user doesn't have moderator rights the + * hidden thread is not included into the archive. + * + * @return all top message nodes together with their children in a list + */ + private List<MessageNode> convertToThreadTrees() { + List<MessageNode> topNodeList = new ArrayList<>(); + + List<Message> messages = forumManager.getMessagesByForum(forum); + for (Iterator<Message> iterTop = messages.iterator(); iterTop.hasNext();) { + Message msg = iterTop.next(); + if (msg.getParent() == null) { + iterTop.remove(); + MessageNode topNode = new MessageNode(msg); + if(!topNode.isHidden() + || (topNode.isHidden() && (forumCallback == null || forumCallback.mayEditMessageAsModerator()))) { + addChildren(messages, topNode); + topNodeList.add(topNode); + } + } + } + Collections.sort(topNodeList, new MessageNodeComparator()); + return topNodeList; + } + + public static class MessageNodeComparator implements Comparator<MessageNode> { + @Override + public int compare(final MessageNode m1, final MessageNode m2) { + if(m1.isSticky() && m2.isSticky()) { + return m2.getModifiedDate().compareTo(m1.getModifiedDate()); //last first + } else if(m1.isSticky()) { + return -1; + } else if(m2.isSticky()){ + return 1; + } else { + return m2.getModifiedDate().compareTo(m1.getModifiedDate()); //last first + } + } + } + + /** + * + * @param messageId + * @param metaInfo + * @return the top message node with all its children + */ + private MessageNode convertToThreadTree(Long messagedId) { + MessageNode topNode = null; + List<Message> messages = forumManager.getThread(messagedId); + for (Iterator<Message> iterTop = messages.iterator(); iterTop.hasNext();) { + Message msg = iterTop.next(); + if (msg.getParent() == null) { + iterTop.remove(); + topNode = new MessageNode(msg); + addChildren(messages, topNode); + } + } + return topNode; + } + + private void addChildren(List<Message> messages, MessageNode mn){ + for(Iterator<Message> iterMsg = messages.iterator(); iterMsg.hasNext(); ) { + Message msg = iterMsg.next(); + if (msg.getParent() != null && msg.getParent().getKey().equals(mn.getKey())){ + MessageNode childNode = new MessageNode(msg); + mn.addChild(childNode); + childNode.setParent(mn); + addChildren(messages, childNode); + } + } + } + + private void formatForum(List<MessageNode> topNodeList, ForumOpenXMLFormatter forumFormatter) { + forumFormatter.openForum(); + for (Iterator<MessageNode> iterTop = topNodeList.iterator(); iterTop.hasNext();){ + MessageNode mn = iterTop.next(); + //a new top thread starts, inform formatter + forumFormatter.openThread(); + TreeVisitor tv = new TreeVisitor(forumFormatter, mn, false); + tv.visitAll(); + } + } + + private void formatThread(MessageNode mn, ForumOpenXMLFormatter forumFormatter) { + forumFormatter.openThread(); + TreeVisitor tv = new TreeVisitor(forumFormatter, mn, false); + tv.visitAll(); + } +} diff --git a/src/main/java/org/olat/modules/fo/archiver/ForumArchiveManager.java b/src/main/java/org/olat/modules/fo/archiver/ForumArchiveManager.java deleted file mode 100644 index 8db9ce38360..00000000000 --- a/src/main/java/org/olat/modules/fo/archiver/ForumArchiveManager.java +++ /dev/null @@ -1,204 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.modules.fo.archiver; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.olat.core.logging.Tracing; -import org.olat.core.util.tree.TreeVisitor; -import org.olat.modules.fo.Forum; -import org.olat.modules.fo.ForumCallback; -import org.olat.modules.fo.Message; -import org.olat.modules.fo.archiver.formatters.ForumFormatter; -import org.olat.modules.fo.manager.ForumManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -/** - * Initial Date: Nov 11, 2005 <br> - * @author Alexander Schneider - */ -@Service -public class ForumArchiveManager { - - private static final Logger log = Tracing.createLoggerFor(ForumArchiveManager.class); - - @Autowired - private ForumManager forumManager; - - /** - * If the forumCallback is null no restriction applies to the forum archiver. - * (that is it can archive all threads no matter the status) - * @param forumFormatter - * @param forumId - * @param forumCallback - * @return - */ - public String applyFormatter(ForumFormatter forumFormatter, Long forumId, ForumCallback forumCallback){ - log.info("Archiving complete forum: " + forumId); - //convert forum structure to trees - List<MessageNode> threadTreesList = convertToThreadTrees(forumId, forumCallback); - //format forum trees by using the formatter given by the callee - return formatForum(threadTreesList, forumFormatter, forumId); - } - /** - * It is assumed that if the caller of this method is allowed to see the forum thread - * starting from topMessageId, then he also has the right to archive it, so no need for a ForumCallback. - * @param forumFormatter - * @param forumId - * @param topMessageId - * @return the message thread as String formatted - */ - public String applyFormatterForOneThread(ForumFormatter forumFormatter, Long forumId, Long topMessageId){ - MessageNode topMessageNode = convertToThreadTree(topMessageId); - return formatThread(topMessageNode, forumFormatter, forumId); - } - - /** - * If the forumCallback is null no filtering is executed, - * else if a thread is hidden and the user doesn't have moderator rights the - * hidden thread is not included into the archive. - * @param forumId - * @param metaInfo - * @return all top message nodes together with their children in a list - */ - private List<MessageNode> convertToThreadTrees(Long forumId, ForumCallback forumCallback){ - List<MessageNode> topNodeList = new ArrayList<>(); - - Forum f = forumManager.loadForum(forumId); - List<Message> messages = forumManager.getMessagesByForum(f); - - for (Iterator<Message> iterTop = messages.iterator(); iterTop.hasNext();) { - Message msg = iterTop.next(); - if (msg.getParent() == null) { - iterTop.remove(); - MessageNode topNode = new MessageNode(msg); - if(topNode.isHidden() && (forumCallback==null || (forumCallback!=null && forumCallback.mayEditMessageAsModerator()))) { - addChildren(messages, topNode); - topNodeList.add(topNode); - } else if(!topNode.isHidden()) { - addChildren(messages, topNode); - topNodeList.add(topNode); - } - } - } - Collections.sort(topNodeList, new MessageNodeComparator()); - return topNodeList; - } - - public static class MessageNodeComparator implements Comparator<MessageNode> { - @Override - public int compare(final MessageNode m1, final MessageNode m2) { - if(m1.isSticky() && m2.isSticky()) { - return m2.getModifiedDate().compareTo(m1.getModifiedDate()); //last first - } else if(m1.isSticky()) { - return -1; - } else if(m2.isSticky()){ - return 1; - } else { - return m2.getModifiedDate().compareTo(m1.getModifiedDate()); //last first - } - } - } - - /** - * - * @param messageId - * @param metaInfo - * @return the top message node with all its children - */ - private MessageNode convertToThreadTree(Long topMessageId){ - MessageNode topNode = null; - List<Message> messages = forumManager.getThread(topMessageId); - for (Iterator<Message> iterTop = messages.iterator(); iterTop.hasNext();) { - Message msg = iterTop.next(); - if (msg.getParent() == null) { - iterTop.remove(); - topNode = new MessageNode(msg); - addChildren(messages, topNode); - } - } - return topNode; - } - - private void addChildren(List<Message> messages, MessageNode mn){ - for(Iterator<Message> iterMsg = messages.iterator(); iterMsg.hasNext(); ) { - Message msg = iterMsg.next(); - if ((msg.getParent() != null) && (msg.getParent().getKey() == mn.getKey())){ - MessageNode childNode = new MessageNode(msg); - mn.addChild(childNode); - //FIXME:as:c next line is not necessary - childNode.setParent(mn); - addChildren(messages, childNode); - } - } - } - - /** - * - * @param topNodeList - * @param forumFormatter - * @param metaInfo - * @return - */ - private String formatForum(List<MessageNode> topNodeList, ForumFormatter forumFormatter, Long forumId) { - forumFormatter.setForumKey(forumId); - StringBuilder formattedForum = new StringBuilder(); - forumFormatter.openForum(); - for (Iterator<MessageNode> iterTop = topNodeList.iterator(); iterTop.hasNext();){ - MessageNode mn = iterTop.next(); - //a new top thread starts, inform formatter - forumFormatter.openThread(); - TreeVisitor tv = new TreeVisitor(forumFormatter, mn, false); - tv.visitAll(); - //commit - formattedForum.append(forumFormatter.closeThread()); - } - return formattedForum.append(forumFormatter.closeForum().toString()).toString(); - } - - /** - * - * @param mn - * @param forumFormatter - * @param metaInfo - * @return - */ - private String formatThread(MessageNode mn, ForumFormatter forumFormatter, Long forumId){ - forumFormatter.setForumKey(forumId); - StringBuilder formattedThread = new StringBuilder(); - forumFormatter.openThread(); - TreeVisitor tv = new TreeVisitor(forumFormatter, mn, false); - tv.visitAll(); - return formattedThread.append(formattedThread.append(forumFormatter.closeThread())).toString(); - } - -} diff --git a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumDownloadResource.java b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumDownloadResource.java index c9882bd9034..7a874af96fd 100644 --- a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumDownloadResource.java +++ b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumDownloadResource.java @@ -19,30 +19,20 @@ */ package org.olat.modules.fo.archiver.formatters; -import java.io.File; import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; import java.util.Locale; -import java.util.Map; -import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.servlet.http.HttpServletResponse; -import org.olat.core.CoreSpringFactory; +import org.apache.logging.log4j.Logger; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.ServletUtil; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; -import org.olat.core.util.io.ShieldOutputStream; -import org.olat.core.util.openxml.DocReference; -import org.olat.core.util.openxml.OpenXMLDocumentWriter; -import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; -import org.olat.modules.fo.archiver.ForumArchiveManager; +import org.olat.modules.fo.archiver.ForumArchive; /** * @@ -59,16 +49,14 @@ public class ForumDownloadResource implements MediaResource { private Long topMessageId; private String label; - private VFSContainer mediaContainer; private Locale locale; - public ForumDownloadResource(String label, Forum forum, ForumCallback foCallback, Long topMessageId, VFSContainer mediaContainer, Locale locale) { + public ForumDownloadResource(String label, Forum forum, ForumCallback foCallback, Long topMessageId, Locale locale) { this.locale = locale; this.forum = forum; this.label = label; this.foCallback = foCallback; this.topMessageId = topMessageId; - this.mediaContainer = mediaContainer; } @Override @@ -105,7 +93,6 @@ public class ForumDownloadResource implements MediaResource { public void release() { // } - @Override public void prepare(HttpServletResponse hres) { @@ -121,53 +108,11 @@ public class ForumDownloadResource implements MediaResource { String file = secureLabel + ".zip"; hres.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + StringHelper.urlEncodeUTF8(file)); hres.setHeader("Content-Description", StringHelper.urlEncodeUTF8(label)); - - ZipEntry test = new ZipEntry(secureLabel + ".docx"); - zout.putNextEntry(test); - Map<File,DocReference> attachments = exportForum(zout); - zout.closeEntry(); - - if(attachments != null && attachments.size() > 0) { - for(Map.Entry<File,DocReference> attachmentEntry : attachments.entrySet()) { - File attachment = attachmentEntry.getKey(); - DocReference ref = attachmentEntry.getValue(); - zout.putNextEntry(new ZipEntry("attachments/" + ref.getFilename())); - copyShielded(attachment, zout); - zout.closeEntry(); - } - } - } catch (Exception e) { - log.error("", e); - } - } - - private void copyShielded(File attachment, ZipOutputStream zout) { - try(OutputStream out = new ShieldOutputStream(zout)) { - Files.copy(attachment.toPath(), out); - } catch(Exception e) { - log.error("", e); - } - } - - private Map<File,DocReference> exportForum(OutputStream out) { - try(ZipOutputStream zout = new ZipOutputStream(out)) { - zout.setLevel(9); - - ForumOpenXMLFormatter openXmlFormatter = new ForumOpenXMLFormatter(mediaContainer, locale); - if(topMessageId != null) { - CoreSpringFactory.getImpl(ForumArchiveManager.class) - .applyFormatterForOneThread(openXmlFormatter, forum.getKey(), topMessageId); - } else { - CoreSpringFactory.getImpl(ForumArchiveManager.class) - .applyFormatter(openXmlFormatter, forum.getKey(), foCallback); - } - OpenXMLDocumentWriter writer = new OpenXMLDocumentWriter(); - writer.createDocument(zout, openXmlFormatter.getOpenXMLDocument()); - return openXmlFormatter.getAttachments(); + ForumArchive archive = new ForumArchive(forum, topMessageId, locale, foCallback); + archive.export(secureLabel + ".docx", zout); } catch (Exception e) { log.error("", e); - return null; } } } diff --git a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumFormatter.java b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumFormatter.java deleted file mode 100644 index 60d6635108e..00000000000 --- a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumFormatter.java +++ /dev/null @@ -1,117 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.modules.fo.archiver.formatters; - -import java.util.Locale; - -import org.olat.core.gui.translator.Translator; -import org.olat.core.util.Util; -import org.olat.core.util.nodes.INode; -import org.olat.core.util.tree.Visitor; -import org.olat.modules.fo.Forum; - -/** - * Initial Date: Nov 11, 2005 <br> - * @author Patrick Brunner, Alexander Schneider - */ - -public abstract class ForumFormatter implements Visitor { - protected StringBuilder sb; - protected boolean isTopThread = false; - protected boolean filePerThread = false; - private Long forumKey; - - protected final Translator translator; - - /** - * init string buffer - * - */ - protected ForumFormatter(Locale locale){ - sb = new StringBuilder(4096); - translator = Util.createPackageTranslator(Forum.class, locale); - } - /** - * contains (translation keys, value) pairs such as: - * forum.metainfo.key,1234556 - * forum.metainfo.topthreadcnt, number of top threads - * forum.metainfo.msgcnt, number of messages - * - * @param metaInfo - */ - public void setForumKey(Long forumKey){ - this.forumKey = forumKey; - } - /** - * inform formatter that a new top thread has started, - * e.g. ForumArchiveManager sets this if next node in topnode list is worked on - * - */ - public void openThread(){ - isTopThread = true; - } - /** - * inform formatter, that the top thread is completly consumed, thus create the formatted result for this thread - * @return - */ - public StringBuilder closeThread(){ - StringBuilder retVal = sb; - sb = new StringBuilder(); - return retVal; - } - /** - * - * @param key - * @return value of key - */ - public Long getForumKey() { - return forumKey; - } - - /** - * - * @return true if every thread is saved in his own file; false if all threads are saved in one file - */ - public boolean isFilePerThread(){ - return filePerThread; - } - - /** - * inform formatter that the forum is opened, and it must expect top threads being opened. - */ - public abstract void openForum(); - - /** - *inform formatter that all top thread of the forum are consumed. - */ - public abstract StringBuilder closeForum(); - - /** - * - * @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode) - */ - public abstract void visit(INode node); -} diff --git a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumOpenXMLFormatter.java b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumOpenXMLFormatter.java index f28264bb4bb..7d08751e50c 100644 --- a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumOpenXMLFormatter.java +++ b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumOpenXMLFormatter.java @@ -28,19 +28,24 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.UserConstants; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.core.util.filter.FilterFactory; import org.olat.core.util.nodes.INode; import org.olat.core.util.openxml.DocReference; import org.olat.core.util.openxml.OpenXMLDocument; import org.olat.core.util.openxml.OpenXMLDocument.Spacing; import org.olat.core.util.openxml.OpenXMLDocument.Style; +import org.olat.core.util.tree.Visitor; import org.olat.core.util.vfs.LocalFileImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; -import org.olat.core.util.vfs.filters.VFSItemMetaFilter; +import org.olat.core.util.vfs.filters.VFSLeafButSystemFilter; +import org.olat.modules.fo.Forum; import org.olat.modules.fo.archiver.MessageNode; /** @@ -49,11 +54,12 @@ import org.olat.modules.fo.archiver.MessageNode; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class ForumOpenXMLFormatter extends ForumFormatter { - - private final VFSItemMetaFilter filter = new VFSItemMetaFilter(); +public class ForumOpenXMLFormatter implements Visitor { private boolean firstThread = true; + private boolean isTopThread = false; + + private final Translator translator; private final Formatter formatter; private final VFSContainer forumContainer; @@ -63,7 +69,7 @@ public class ForumOpenXMLFormatter extends ForumFormatter { private final Map<File,DocReference> fileToAttachmentsMap = new HashMap<>(); public ForumOpenXMLFormatter(VFSContainer forumContainer, Locale locale) { - super(locale); + translator = Util.createPackageTranslator(Forum.class, locale); document.setMediaContainer(forumContainer); this.forumContainer = forumContainer; formatter = Formatter.getInstance(locale); @@ -76,20 +82,18 @@ public class ForumOpenXMLFormatter extends ForumFormatter { public Map<File,DocReference> getAttachments() { return fileToAttachmentsMap; } - - @Override + public void openForum() { // } - @Override public void openThread() { if(firstThread) { firstThread = false; } else { document.appendPageBreak(); } - super.openThread(); + isTopThread = true; } @Override @@ -151,6 +155,9 @@ public class ForumOpenXMLFormatter extends ForumFormatter { if(body != null) { body = body.replace("<p> ", "<p>"); } + + String mapperPath = m.getKey().toString(); + body = FilterFactory.getBaseURLToMediaRelativeURLFilter(mapperPath).filter(body); document.appendHtmlText(body, new Spacing(180, 0)); // message attachments @@ -161,15 +168,14 @@ public class ForumOpenXMLFormatter extends ForumFormatter { } private void processAttachments(VFSContainer attachmentsContainer) { - List<VFSItem> attachments = new ArrayList<>(attachmentsContainer.getItems(filter)); + List<VFSItem> attachments = new ArrayList<>(attachmentsContainer.getItems(new VFSLeafButSystemFilter())); for(VFSItem attachment:attachments) { if(attachment instanceof LocalFileImpl) { //add the text document.appendText(translator.translate("attachments"), true, Style.bold); } } - - + for(VFSItem attachment:attachments) { if(attachment instanceof LocalFileImpl) { File file = ((LocalFileImpl)attachment).getBasefile(); @@ -196,12 +202,6 @@ public class ForumOpenXMLFormatter extends ForumFormatter { } } - @Override - public StringBuilder closeThread() { - return super.closeThread(); - } - - @Override public StringBuilder closeForum() { return new StringBuilder(); } diff --git a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumRTFFormatter.java b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumRTFFormatter.java deleted file mode 100644 index 3ec2be445ac..00000000000 --- a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumRTFFormatter.java +++ /dev/null @@ -1,468 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.modules.fo.archiver.formatters; - -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.olat.core.CoreSpringFactory; -import org.olat.core.id.Identity; -import org.olat.core.id.UserConstants; -import org.olat.core.logging.AssertException; -import org.apache.logging.log4j.Logger; -import org.olat.core.logging.Tracing; -import org.olat.core.util.Formatter; -import org.olat.core.util.StringHelper; -import org.olat.core.util.WebappHelper; -import org.olat.core.util.ZipUtil; -import org.olat.core.util.filter.FilterFactory; -import org.olat.core.util.nodes.INode; -import org.olat.core.util.vfs.LocalFileImpl; -import org.olat.core.util.vfs.VFSContainer; -import org.olat.core.util.vfs.VFSItem; -import org.olat.core.util.vfs.VFSLeaf; -import org.olat.core.util.vfs.VFSManager; -import org.olat.core.util.vfs.filters.VFSSystemItemFilter; -import org.olat.modules.fo.archiver.MessageNode; -import org.olat.modules.fo.manager.ForumManager; - -/** - * Initial Date: Nov 09, 2005 <br> - * - * @author Patrick Brunner, Alexander Schneider - */ - -public class ForumRTFFormatter extends ForumFormatter { - - private static final Logger log = Tracing.createLoggerFor(ForumRTFFormatter.class); - - private VFSContainer container; - private VFSItem vfsFil = null; - private VFSContainer tempContainer; - - private final Pattern PATTERN_HTML_BOLD = Pattern.compile("<strong>(.*?)</strong>", Pattern.CASE_INSENSITIVE); - private final Pattern PATTERN_HTML_ITALIC = Pattern.compile("<em>(.*?)</em>", Pattern.CASE_INSENSITIVE); - private final Pattern PATTERN_HTML_BREAK = Pattern.compile("<br />", Pattern.CASE_INSENSITIVE); - private final Pattern PATTERN_HTML_PARAGRAPH = Pattern.compile("<p>(.*?)</p>", Pattern.CASE_INSENSITIVE); - private final Pattern PATTERN_HTML_AHREF = Pattern.compile("<a href=\"([^\"]+)\"[^>]*>(.*?)</a>", Pattern.CASE_INSENSITIVE); - private final Pattern PATTERN_HTML_LIST = Pattern.compile("<li>(.*?)</li>", Pattern.CASE_INSENSITIVE); - private final Pattern HTML_SPACE_PATTERN = Pattern.compile(" "); - - private final Pattern PATTERN_CSS_O_FOQUOTE = Pattern.compile("<div class=\"o_quote_wrapper\">\\s*<div class=\"b_quote_author mceNonEditable\">(.*?)</div>\\s*<blockquote class=\"b_quote\">\\s*(.*?)\\s*</blockquote>\\s*</div>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - - private final Pattern PATTERN_THREEPOINTS = Pattern.compile("…", Pattern.CASE_INSENSITIVE); - private final String THREEPOINTS = "..."; - - private String HIDDEN_STR = "VERBORGEN"; - - private final ForumManager forumManager; - - /** - * - * @param container - * @param filePerThread - */ - - public ForumRTFFormatter(VFSContainer container, boolean filePerThread, Locale locale) { - // init String Buffer in ForumFormatter - super(locale); - // where to write - this.container = container; - this.filePerThread = filePerThread; - forumManager = CoreSpringFactory.getImpl(ForumManager.class); - } - - /** - * @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode) - */ - public void visit(INode node) { - MessageNode mn = (MessageNode) node; - - if (isTopThread) { - if(filePerThread){ - //make a file per thread - //to have a meaningful filename we create the file here - String filName = "Thread_" + mn.getKey().toString(); - tempContainer = makeTempVFSContainer(); - vfsFil=tempContainer.resolve(filName + ".rtf"); - if(vfsFil==null){ - tempContainer.createChildLeaf(filName + ".rtf"); - vfsFil=tempContainer.resolve(filName + ".rtf"); - } - } - //important! - isTopThread = false; - } - // Message Title - sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\f1\\fs30\\b "); - sb.append(getImageRTF(mn)); - sb.append(getTitlePrefix(mn)); - sb.append(mn.getTitle()); - sb.append("\\par}"); - // Message Body - sb.append("{\\pard \\f0"); - sb.append(convertHTMLMarkupToRTF(mn.getBody())); - sb.append("\\par}"); - // Message key - sb.append("{\\pard \\f0\\fs15 Message key: "); - sb.append(mn.getKey()); - sb.append("} \\line "); - sb.append("{\\pard \\f0\\fs15 created: "); - // Creator and creation date - if(StringHelper.containsNonWhitespace(mn.getPseudonym())) { - sb.append(mn.getPseudonym()) - .append(" ") - .append(translator.translate("pseudonym.suffix")); - } else if(mn.isGuest()) { - sb.append(translator.translate("guest")); - } else { - sb.append(mn.getCreator().getUser().getProperty(UserConstants.FIRSTNAME, null)); - sb.append(", "); - sb.append(mn.getCreator().getUser().getProperty(UserConstants.LASTNAME, null)); - } - sb.append(" "); - sb.append(mn.getCreationDate().toString()); - // Modifier and modified date - Identity modifier = mn.getModifier(); - if (modifier != null) { - sb.append(" \\line modified: "); - sb.append(modifier.getUser().getProperty(UserConstants.FIRSTNAME, null)); - sb.append(", "); - sb.append(modifier.getUser().getProperty(UserConstants.LASTNAME, null)); - sb.append(" "); - sb.append(mn.getModifiedDate().toString()); - } - sb.append(" \\par}"); - // attachment(s) - VFSContainer msgContainer = forumManager.getMessageContainer(getForumKey(), mn.getKey()); - List<VFSItem> attachments = msgContainer.getItems(new VFSSystemItemFilter()); - if (attachments != null && !attachments.isEmpty()){ - VFSItem item = container.resolve("attachments"); - if (item == null){ - item = container.createChildContainer("attachments"); - } - VFSContainer attachmentContainer = (VFSContainer)item; - attachmentContainer.copyFrom(msgContainer); - - sb.append("{\\pard \\f0\\fs15 Attachment(s): "); - boolean commaFlag = false; - for (VFSItem attachment: attachments) { - if (commaFlag) sb.append(", "); - sb.append(attachment.getName()); - commaFlag = true; - } - sb.append("} \\line"); - } - sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\par}"); - } - - @Override - public void openThread() { - super.openThread(); - if(filePerThread){ - sb.append("{\\rtf1\\ansi\\deff0"); - sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} "); - sb.append("\\deflang1033\\plain"); - } - sb.append("{\\pard \\brdrb \\brdrs \\brdrdb \\brsp20 \\par}{\\pard\\par}"); - } - - @Override - public StringBuilder closeThread() { - boolean append = !filePerThread; - String footerThread = "{\\pard \\brdrb \\brdrs \\brdrw20 \\brsp20 \\par}{\\pard\\par}"; - sb.append(footerThread); - if(filePerThread){ - sb.append("}"); - } - writeToFile(append, sb); - if(filePerThread) { - zipContainer(tempContainer); - tempContainer.delete(); - } - return super.closeThread(); - } - - @Override - public void openForum(){ - if(!filePerThread){ - //make one ForumFile - Long forumKey = getForumKey(); - String filName = forumKey.toString(); - filName = "Threads_" + filName + ".rtf"; - - tempContainer = makeTempVFSContainer(); - this.vfsFil=tempContainer.resolve(filName); - if(vfsFil==null){ - tempContainer.createChildLeaf(filName); - vfsFil = tempContainer.resolve(filName); - } - sb.append("{\\rtf1\\ansi\\deff0"); - sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} "); - sb.append("\\deflang1033\\plain"); - } - } - - @Override - public StringBuilder closeForum(){ - if(!filePerThread){ - boolean append = !filePerThread; - String footerForum = "}"; - sb.append(footerForum); - writeToFile(append, sb); - zipContainer(tempContainer); - tempContainer.delete(); - } - return sb; - } - - - /** - * - * @param append - * @param buff - */ - private void writeToFile(boolean append, StringBuilder buff){ - BufferedOutputStream bos = new BufferedOutputStream(((VFSLeaf) vfsFil).getOutputStream(append)); - OutputStreamWriter w; - try { - w = new OutputStreamWriter(bos, "utf-8"); - BufferedWriter bw = new BufferedWriter(w); - String s = buff.toString(); - StringBuilder out = new StringBuilder(); - int len = s.length(); - for (int i = 0; i < len; i++) { - char c = s.charAt(i); - int val = c; - if (val > 127) { - out.append("\\u").append(String.valueOf(val)).append("?"); - } else { - out.append(c); - } - } - - String encoded = out.toString(); - bw.write(encoded); - bw.close(); - bos.close(); - } catch (UnsupportedEncodingException ueEx) { - throw new AssertException("could not encode stream from forum export file: " + ueEx); - } catch (IOException e) { - throw new AssertException("could not write to forum export file: " + e); - } - } - - /** - * - * @param originalText - * @return - */ - private String convertHTMLMarkupToRTF(String originalText){ - String htmlText = originalText; - - Matcher mb = PATTERN_HTML_BOLD.matcher(htmlText); - StringBuffer bolds = new StringBuffer(); - while (mb.find()) { - mb.appendReplacement(bolds, "{\\\\b $1} "); - } - mb.appendTail(bolds); - htmlText = bolds.toString(); - - Matcher mi = PATTERN_HTML_ITALIC.matcher(htmlText); - StringBuffer italics = new StringBuffer(); - while (mi.find()) { - mi.appendReplacement(italics, "{\\\\i $1} "); - } - mi.appendTail(italics); - htmlText = italics.toString(); - - Matcher mbr = PATTERN_HTML_BREAK.matcher(htmlText); - StringBuffer breaks = new StringBuffer(); - while(mbr.find()){ - mbr.appendReplacement(breaks, "\\\\line "); - } - mbr.appendTail(breaks); - htmlText = breaks.toString(); - - Matcher mofo = PATTERN_CSS_O_FOQUOTE.matcher(htmlText); - StringBuffer foquotes = new StringBuffer(); - while(mofo.find()){ - mofo.appendReplacement(foquotes, "\\\\line {\\\\i $1} {\\\\pard $2\\\\par}"); - } - mofo.appendTail(foquotes); - htmlText = foquotes.toString(); - - Matcher mp = PATTERN_HTML_PARAGRAPH.matcher(htmlText); - StringBuffer paragraphs = new StringBuffer(); - while(mp.find()){ - mp.appendReplacement(paragraphs, "\\\\line $1 \\\\line"); - } - mp.appendTail(paragraphs); - htmlText = paragraphs.toString(); - - Matcher mahref = PATTERN_HTML_AHREF.matcher(htmlText); - StringBuffer ahrefs = new StringBuffer(); - while(mahref.find()){ - mahref.appendReplacement(ahrefs, "{\\\\field{\\\\*\\\\fldinst{HYPERLINK\"$1\"}}{\\\\fldrslt{\\\\ul $2}}}"); - } - mahref.appendTail(ahrefs); - htmlText = ahrefs.toString(); - - Matcher mli = PATTERN_HTML_LIST.matcher(htmlText); - StringBuffer lists = new StringBuffer(); - while(mli.find()){ - mli.appendReplacement(lists, "$1\\\\line "); - } - mli.appendTail(lists); - htmlText = lists.toString(); - - Matcher mtp = PATTERN_THREEPOINTS.matcher(htmlText); - StringBuffer tps = new StringBuffer(); - while (mtp.find()) { - mtp.appendReplacement(tps, THREEPOINTS); - } - mtp.appendTail(tps); - htmlText = tps.toString(); - - // strip all other html-fragments, because not convertable that easy - htmlText = FilterFactory.getHtmlTagsFilter().filter(htmlText); - // Remove all - Matcher tmp = HTML_SPACE_PATTERN.matcher(htmlText); - htmlText = tmp.replaceAll(" "); - htmlText = StringHelper.unescapeHtml(htmlText); - - return htmlText; - } - - /** - * - * @param messageNode - * @return title prefix for hidden forum threads. - */ - private String getTitlePrefix(MessageNode messageNode) { - StringBuffer stringBuffer = new StringBuffer(); - if(messageNode.isHidden()) { - stringBuffer.append(HIDDEN_STR); - } - if(stringBuffer.length()>1) { - stringBuffer.append(": "); - } - return stringBuffer.toString(); - } - - /** - * Gets the RTF image section for the input messageNode. - * @param messageNode - * @return the RTF image section for the input messageNode. - */ - private String getImageRTF(MessageNode messageNode) { - - StringBuffer stringBuffer = new StringBuffer(); - List<String> fileNameList = addImagesToVFSContainer(messageNode, tempContainer); - Iterator<String> listIterator = fileNameList.iterator(); - while(listIterator.hasNext()) { - String fileName = listIterator.next(); - - stringBuffer.append("{\\field\\fldedit{\\*\\fldinst { INCLUDEPICTURE "); - stringBuffer.append("\"").append(fileName).append("\""); - stringBuffer.append(" \\\\d }}{\\fldrslt {}}}"); - } - return stringBuffer.toString(); - } - - /** - * Retrieves the appropriate images for the input messageNode, if any, - * and adds it to the input container. - * - * @param messageNode - * @param imageContainer - * @return - */ - private List<String> addImagesToVFSContainer(MessageNode messageNode, VFSContainer imageContainer) { - List<String> fileNameList = new ArrayList<>(); - String iconPath = null; - if(messageNode.isClosed() && messageNode.isSticky()) { - iconPath = getImagePath("fo_sticky_closed"); - } else if(messageNode.isClosed()) { - iconPath = getImagePath("fo_closed"); - } else if(messageNode.isSticky()) { - iconPath = getImagePath("fo_sticky"); - } - if (iconPath != null) { - File file = new File(iconPath); - if (file.exists()) { - LocalFileImpl imgFile = new LocalFileImpl(file); - imageContainer.copyFrom(imgFile); - fileNameList.add(file.getName()); - } else { - log.error("Could not find image for forum RTF formatter::" + iconPath); - } - } - return fileNameList; - } - - /** - * Gets the image path. - * @param val - * @return the path of the static icon image. - */ - private String getImagePath(Object val) { - return WebappHelper.getContextRealPath("/static/images/forum/" + val.toString() + ".png"); - } - - /** - * Generates a new temporary VFSContainer. - * @return the temp container. - */ - private VFSContainer makeTempVFSContainer() { - Long forumKey = getForumKey(); - String dateStamp = String.valueOf(System.currentTimeMillis()); - String fileName = "forum" + forumKey.toString() + "_" + dateStamp; - return VFSManager.olatRootContainer("/tmp/" + fileName, null); - } - - /** - * Zips the input vFSContainer into the container. - * @param vFSContainer - */ - private void zipContainer(VFSContainer vFSContainer) { - String dateStamp = Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())); - VFSLeaf zipVFSLeaf = container.createChildLeaf("forum_archive-"+dateStamp+".zip"); - ZipUtil.zip(vFSContainer.getItems(), zipVFSLeaf, true); - } - -} diff --git a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumStreamedRTFFormatter.java b/src/main/java/org/olat/modules/fo/archiver/formatters/ForumStreamedRTFFormatter.java deleted file mode 100644 index d71d676d498..00000000000 --- a/src/main/java/org/olat/modules/fo/archiver/formatters/ForumStreamedRTFFormatter.java +++ /dev/null @@ -1,418 +0,0 @@ -/** -* OLAT - Online Learning and Training<br> -* http://www.olat.org -* <p> -* Licensed under the Apache License, Version 2.0 (the "License"); <br> -* you may not use this file except in compliance with the License.<br> -* You may obtain a copy of the License at -* <p> -* http://www.apache.org/licenses/LICENSE-2.0 -* <p> -* Unless required by applicable law or agreed to in writing,<br> -* software distributed under the License is distributed on an "AS IS" BASIS, <br> -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> -* See the License for the specific language governing permissions and <br> -* limitations under the License. -* <p> -* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <hr> -* <a href="http://www.openolat.org"> -* OpenOLAT - Online Learning and Training</a><br> -* This file has been modified by the OpenOLAT community. Changes are licensed -* under the Apache 2.0 license as the original file. -*/ - -package org.olat.modules.fo.archiver.formatters; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.olat.core.CoreSpringFactory; -import org.olat.core.id.Identity; -import org.olat.core.id.UserConstants; -import org.olat.core.logging.AssertException; -import org.apache.logging.log4j.Logger; -import org.olat.core.logging.Tracing; -import org.olat.core.util.StringHelper; -import org.olat.core.util.WebappHelper; -import org.olat.core.util.ZipUtil; -import org.olat.core.util.filter.FilterFactory; -import org.olat.core.util.nodes.INode; -import org.olat.core.util.vfs.VFSContainer; -import org.olat.core.util.vfs.VFSItem; -import org.olat.core.util.vfs.filters.VFSSystemItemFilter; -import org.olat.modules.fo.archiver.MessageNode; -import org.olat.modules.fo.manager.ForumManager; - -/** - * Initial Date: Dec 19, 2013 <br> - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * @author Patrick Brunner, Alexander Schneider - */ -public class ForumStreamedRTFFormatter extends ForumFormatter { - - private static final Logger log = Tracing.createLoggerFor(ForumStreamedRTFFormatter.class); - - private ZipOutputStream exportStream; - - final Pattern PATTERN_HTML_BOLD = Pattern.compile("<strong>(.*?)</strong>", Pattern.CASE_INSENSITIVE); - final Pattern PATTERN_HTML_ITALIC = Pattern.compile("<em>(.*?)</em>", Pattern.CASE_INSENSITIVE); - final Pattern PATTERN_HTML_BREAK = Pattern.compile("<br />", Pattern.CASE_INSENSITIVE); - final Pattern PATTERN_HTML_PARAGRAPH = Pattern.compile("<p>(.*?)</p>", Pattern.CASE_INSENSITIVE); - final Pattern PATTERN_HTML_AHREF = Pattern.compile("<a href=\"([^\"]+)\"[^>]*>(.*?)</a>", Pattern.CASE_INSENSITIVE); - final Pattern PATTERN_HTML_LIST = Pattern.compile("<li>(.*?)</li>", Pattern.CASE_INSENSITIVE); - final Pattern HTML_SPACE_PATTERN = Pattern.compile(" "); - - final Pattern PATTERN_CSS_O_FOQUOTE = Pattern.compile("<div class=\"o_quote_wrapper\">\\s*<div class=\"b_quote_author mceNonEditable\">(.*?)</div>\\s*<blockquote class=\"b_quote\">\\s*(.*?)\\s*</blockquote>\\s*</div>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - - final Pattern PATTERN_THREEPOINTS = Pattern.compile("…", Pattern.CASE_INSENSITIVE); - final String THREEPOINTS = "..."; - - private String HIDDEN_STR = "VERBORGEN"; - private final String path; - - private final ForumManager forumManager; - - /** - * - * @param container - * @param filePerThread - */ - - public ForumStreamedRTFFormatter(ZipOutputStream exportStream, String path, boolean filePerThread, Locale locale) { - // init String Buffer in ForumFormatter - super(locale); - - forumManager = CoreSpringFactory.getImpl(ForumManager.class); - - // where to write - this.exportStream = exportStream; - this.filePerThread = filePerThread; - this.path = path; - addStandardImages(); - } - - private String fileName; - - @Override - public void visit(INode node) { - MessageNode mn = (MessageNode)node; - if (isTopThread) { - //important! - fileName = "Thread_" + mn.getKey().toString() + ".rtf"; - isTopThread = false; - } - // Message Title - sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\f1\\fs30\\b "); - sb.append(getImageRTF(mn)); - sb.append(getTitlePrefix(mn)); - sb.append(mn.getTitle()); - sb.append("\\par}"); - // Message Body - sb.append("{\\pard \\f0"); - sb.append(convertHTMLMarkupToRTF(mn.getBody())); - sb.append("\\par}"); - // Message key - sb.append("{\\pard \\f0\\fs15 Message key: "); - sb.append(mn.getKey()); - sb.append("} \\line "); - sb.append("{\\pard \\f0\\fs15 created: "); - // Creator and creation date - if(StringHelper.containsNonWhitespace(mn.getPseudonym())) { - sb.append(mn.getPseudonym()) - .append(" ") - .append(translator.translate("pseudonym.suffix")); - } else if(mn.isGuest()) { - sb.append(translator.translate("guest")); - } else { - sb.append(mn.getCreator().getUser().getProperty(UserConstants.FIRSTNAME, null)); - sb.append(", "); - sb.append(mn.getCreator().getUser().getProperty(UserConstants.LASTNAME, null)); - } - sb.append(" "); - sb.append(mn.getCreationDate().toString()); - // Modifier and modified date - Identity modifier = mn.getModifier(); - if (modifier != null) { - sb.append(" \\line modified: "); - sb.append(modifier.getUser().getProperty(UserConstants.FIRSTNAME, null)); - sb.append(", "); - sb.append(modifier.getUser().getProperty(UserConstants.LASTNAME, null)); - sb.append(" "); - sb.append(mn.getModifiedDate().toString()); - } - sb.append(" \\par}"); - // attachment(s) - VFSContainer msgContainer = forumManager.getMessageContainer(getForumKey(), mn.getKey()); - List<VFSItem> attachments = msgContainer.getItems(new VFSSystemItemFilter()); - if (attachments != null && !attachments.isEmpty()){ - sb.append("{\\pard \\f0\\fs15 Attachment(s): "); - boolean commaFlag = false; - for (VFSItem attachment: attachments) { - if (commaFlag) sb.append(", "); - sb.append(attachment.getName()); - commaFlag = true; - - ZipUtil.addToZip(attachment, path + "/attachments", exportStream); - } - sb.append("} \\line"); - } - sb.append("{\\pard \\brdrb\\brdrs\\brdrw10 \\par}"); - } - - private void addStandardImages() { - String[] images = new String[]{ "fo_sticky_closed", "fo_closed", "fo_sticky"}; - try { - for(String image:images) { - String iconPath = getImagePath(image); - if (iconPath != null) { - File file = new File(iconPath); - if (file.exists()) { - exportStream.putNextEntry(new ZipEntry(path + "/" + file.getName())); - FileUtils.copyFile(file, exportStream); - exportStream.closeEntry(); - } - } - } - } catch (IOException e) { - log.error("", e); - } - } - - @Override - public void openThread() { - super.openThread(); - if(filePerThread){ - sb.append("{\\rtf1\\ansi\\deff0"); - sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} "); - sb.append("\\deflang1033\\plain"); - } - sb.append("{\\pard \\brdrb \\brdrs \\brdrdb \\brsp20 \\par}{\\pard\\par}"); - } - - @Override - public StringBuilder closeThread() { - String footerThread = "{\\pard \\brdrb \\brdrs \\brdrw20 \\brsp20 \\par}{\\pard\\par}"; - sb.append(footerThread); - if(filePerThread){ - sb.append("}"); - writeToFile(sb); - sb = new StringBuilder(); - } - return sb; - } - - @Override - public void openForum(){ - if(!filePerThread){ - //make one ForumFile - Long forumKey = getForumKey(); - fileName = "Threads_" + forumKey.toString() + ".rtf"; - sb.append("{\\rtf1\\ansi\\deff0"); - sb.append("{\\fonttbl {\\f0\\fswiss Arial;}} "); - sb.append("\\deflang1033\\plain"); - } - } - - @Override - public StringBuilder closeForum(){ - if(!filePerThread){ - String footerForum = "}"; - sb.append(footerForum); - writeToFile(sb); - } - return sb; - } - - /** - * - * @param append - * @param buff - */ - private void writeToFile(StringBuilder buff){ - try { - StringBuilder out = new StringBuilder(); - int len = buff.length(); - for (int i = 0; i < len; i++) { - char c = buff.charAt(i); - int val = c; - if (val > 127) { - out.append("\\u").append(String.valueOf(val)).append("?"); - } else { - out.append(c); - } - } - String encoded = out.toString(); - exportStream.putNextEntry(new ZipEntry(path + "/" + fileName)); - IOUtils.write(encoded, exportStream, "UTF-8"); - exportStream.closeEntry(); - } catch (UnsupportedEncodingException ueEx) { - throw new AssertException("could not encode stream from forum export file: " + ueEx); - } catch (IOException e) { - throw new AssertException("could not write to forum export file: " + e); - } - } - - /** - * - * @param originalText - * @return - */ - private String convertHTMLMarkupToRTF(String originalText){ - String htmlText = originalText; - - Matcher mb = PATTERN_HTML_BOLD.matcher(htmlText); - StringBuffer bolds = new StringBuffer(); - while (mb.find()) { - mb.appendReplacement(bolds, "{\\\\b $1} "); - } - mb.appendTail(bolds); - htmlText = bolds.toString(); - - Matcher mi = PATTERN_HTML_ITALIC.matcher(htmlText); - StringBuffer italics = new StringBuffer(); - while (mi.find()) { - mi.appendReplacement(italics, "{\\\\i $1} "); - } - mi.appendTail(italics); - htmlText = italics.toString(); - - Matcher mbr = PATTERN_HTML_BREAK.matcher(htmlText); - StringBuffer breaks = new StringBuffer(); - while(mbr.find()){ - mbr.appendReplacement(breaks, "\\\\line "); - } - mbr.appendTail(breaks); - htmlText = breaks.toString(); - - Matcher mofo = PATTERN_CSS_O_FOQUOTE.matcher(htmlText); - StringBuffer foquotes = new StringBuffer(); - while(mofo.find()){ - mofo.appendReplacement(foquotes, "\\\\line {\\\\i $1} {\\\\pard $2\\\\par}"); - } - mofo.appendTail(foquotes); - htmlText = foquotes.toString(); - - Matcher mp = PATTERN_HTML_PARAGRAPH.matcher(htmlText); - StringBuffer paragraphs = new StringBuffer(); - while(mp.find()){ - mp.appendReplacement(paragraphs, "\\\\line $1 \\\\line"); - } - mp.appendTail(paragraphs); - htmlText = paragraphs.toString(); - - Matcher mahref = PATTERN_HTML_AHREF.matcher(htmlText); - StringBuffer ahrefs = new StringBuffer(); - while(mahref.find()){ - mahref.appendReplacement(ahrefs, "{\\\\field{\\\\*\\\\fldinst{HYPERLINK\"$1\"}}{\\\\fldrslt{\\\\ul $2}}}"); - } - mahref.appendTail(ahrefs); - htmlText = ahrefs.toString(); - - Matcher mli = PATTERN_HTML_LIST.matcher(htmlText); - StringBuffer lists = new StringBuffer(); - while(mli.find()){ - mli.appendReplacement(lists, "$1\\\\line "); - } - mli.appendTail(lists); - htmlText = lists.toString(); - - Matcher mtp = PATTERN_THREEPOINTS.matcher(htmlText); - StringBuffer tps = new StringBuffer(); - while (mtp.find()) { - mtp.appendReplacement(tps, THREEPOINTS); - } - mtp.appendTail(tps); - htmlText = tps.toString(); - - // strip all other html-fragments, because not convertable that easy - htmlText = FilterFactory.getHtmlTagsFilter().filter(htmlText); - // Remove all - Matcher tmp = HTML_SPACE_PATTERN.matcher(htmlText); - htmlText = tmp.replaceAll(" "); - htmlText = StringHelper.unescapeHtml(htmlText); - - return htmlText; - } - - /** - * - * @param messageNode - * @return title prefix for hidden forum threads. - */ - private String getTitlePrefix(MessageNode messageNode) { - StringBuilder titleSb = new StringBuilder(); - if(messageNode.isHidden()) { - titleSb.append(HIDDEN_STR); - } - if(titleSb.length()>1) { - titleSb.append(": "); - } - return titleSb.toString(); - } - - /** - * Gets the RTF image section for the input messageNode. - * @param messageNode - * @return the RTF image section for the input messageNode. - */ - private String getImageRTF(MessageNode messageNode) { - StringBuilder imgSb = new StringBuilder(); - for(String imageName : addImagesToVFSContainer(messageNode)) { - imgSb.append("{\\field\\fldedit{\\*\\fldinst { INCLUDEPICTURE ") - .append("\"").append(imageName).append("\"") - .append(" \\\\d }}{\\fldrslt {}}}"); - } - return imgSb.toString(); - } - - /** - * Retrieves the appropriate images for the input messageNode, if any, - * and adds it to the input container. - * - * @param messageNode - * @param container - * @return - */ - private List<String> addImagesToVFSContainer(MessageNode messageNode) { - List<String> fileNameList = new ArrayList<>(); - String iconPath = null; - if(messageNode.isClosed() && messageNode.isSticky()) { - iconPath = getImagePath("fo_sticky_closed"); - } else if(messageNode.isClosed()) { - iconPath = getImagePath("fo_closed"); - } else if(messageNode.isSticky()) { - iconPath = getImagePath("fo_sticky"); - } - if (iconPath != null) { - File file = new File(iconPath); - if (file.exists()) { - fileNameList.add(file.getName()); - } else { - log.error("Could not find image for forum RTF formatter::" + iconPath); - } - } - return fileNameList; - } - - /** - * Gets the image path. - * @param val - * @return the path of the static icon image. - */ - private String getImagePath(Object val) { - return WebappHelper.getContextRealPath("/static/images/forum/" + val.toString() + ".png"); - } -} diff --git a/src/main/java/org/olat/modules/fo/manager/ForumManager.java b/src/main/java/org/olat/modules/fo/manager/ForumManager.java index bc7ef6aaaf8..1b2e4d042f0 100644 --- a/src/main/java/org/olat/modules/fo/manager/ForumManager.java +++ b/src/main/java/org/olat/modules/fo/manager/ForumManager.java @@ -111,7 +111,7 @@ public class ForumManager { } /** - * @param msgid msg id of the topthread + * @param msgid The message id of the top thread * @return List messages */ public List<Message> getThread(Long msgid) { @@ -140,24 +140,24 @@ public class ForumManager { /** * - * @param forum_id - * @return + * @param forumId The forum ID + * @return The number of messages in the specified forum */ - public int countThreadsByForumID(Long forum_id) { - return countMessagesByForumID(forum_id, true); + public int countThreadsByForumID(Long forumId) { + return countMessagesByForumID(forumId, true); } /** * - * @param forum_id + * @param forumId * @param start * @param limit * @param orderBy * @param asc * @return */ - public List<Message> getThreadsByForumID(Long forum_id, int firstResult, int maxResults, Message.OrderBy orderBy, boolean asc) { - return getMessagesByForumID(forum_id, firstResult, maxResults, true, orderBy, asc); + public List<Message> getThreadsByForumID(Long forumId, int firstResult, int maxResults, Message.OrderBy orderBy, boolean asc) { + return getMessagesByForumID(forumId, firstResult, maxResults, true, orderBy, asc); } /** @@ -166,7 +166,7 @@ public class ForumManager { * @return */ public List<Message> getMessagesByForum(Forum forum){ - if (forum == null) return new ArrayList<>(0); // fxdiff: while indexing it can somehow occur, that forum is null! + if (forum == null) return new ArrayList<>(0); //while indexing it can somehow occur, that forum is null! return getMessagesByForumID(forum.getKey(), 0, -1, false, null, true); } @@ -905,9 +905,7 @@ public class ForumManager { markingService.getMarkManager().deleteMarks(ores, m.getKey().toString()); } - if(log.isDebugEnabled()){ - log.debug("Deleting message: " + m.getKey()); - } + log.debug("Deleting message: {}", m.getKey()); } /** @@ -920,7 +918,7 @@ public class ForumManager { .createQuery(q, Number.class) .setParameter("parentKey", m.getKey()) .getResultList(); - return count == null || count.isEmpty() || count.get(0) == null ? false : count.get(0).longValue() > 0; + return count != null && !count.isEmpty() && count.get(0) != null && count.get(0).longValue() > 0; } public int countMessageChildren(Long messageKey ) { @@ -965,7 +963,7 @@ public class ForumManager { } else if(messageContainer instanceof VFSContainer) { return (VFSContainer)messageContainer; } - log.error("The following message container is not a directory: " + messageContainer); + log.error("The following message container is not a directory: {}", messageContainer); return null; } diff --git a/src/main/java/org/olat/modules/fo/manager/QuoterFilter.java b/src/main/java/org/olat/modules/fo/manager/QuoterFilter.java new file mode 100644 index 00000000000..e8e47cb786f --- /dev/null +++ b/src/main/java/org/olat/modules/fo/manager/QuoterFilter.java @@ -0,0 +1,103 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.manager; + +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; + +import org.apache.logging.log4j.Logger; +import org.olat.core.logging.Tracing; +import org.olat.core.util.filter.Filter; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import nu.validator.htmlparser.common.XmlViolationPolicy; +import nu.validator.htmlparser.sax.HtmlParser; +import nu.validator.htmlparser.sax.HtmlSerializer; + +/** + * + * Initial date: 18 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QuoterFilter implements Filter { + + private static final Logger log = Tracing.createLoggerFor(QuoterFilter.class); + + @Override + public String filter(String original) { + if(original == null) return null; + if(original.isEmpty()) return ""; + + try { + HtmlParser parser = new HtmlParser(XmlViolationPolicy.ALTER_INFOSET); + Writer writer = new StringWriter(); + QuoteSerializer contentHandler = new QuoteSerializer(writer); + parser.setContentHandler(contentHandler); + parser.parse(new InputSource(new StringReader(original))); + return writer.toString(); + } catch (Exception e) { + log.error("", e); + return null; + } + } + + private class QuoteSerializer extends HtmlSerializer { + + public QuoteSerializer(Writer writer) { + super(writer); + } + + @Override + public void startDocument() throws SAXException { + // no doctype + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + if("img".equals(localName)) { + String img = "[" + atts.getValue("src") + "]"; + char[] imgChArr = img.toCharArray(); + characters(imgChArr, 0, imgChArr.length); + return; + } + if(ignore(localName)) { + return; + } + super.startElement(uri, localName, qName, atts); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if(ignore(localName)) { + return; + } + super.endElement(uri, localName, qName); + } + + private boolean ignore(String localName) { + return "html".equals(localName) || "head".equals(localName) + || "body".equals(localName) || "img".equals(localName); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/fo/ui/MessageEditController.java b/src/main/java/org/olat/modules/fo/ui/MessageEditController.java index c8ce1e7c51b..63643a782d0 100644 --- a/src/main/java/org/olat/modules/fo/ui/MessageEditController.java +++ b/src/main/java/org/olat/modules/fo/ui/MessageEditController.java @@ -27,12 +27,14 @@ import java.util.Comparator; import java.util.List; import javax.persistence.PersistenceException; +import javax.servlet.http.HttpServletRequest; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.IdentityShort; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -50,8 +52,10 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; +import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; -import org.olat.core.logging.AssertException; import org.olat.core.logging.DBRuntimeException; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.CodeHelper; @@ -59,12 +63,16 @@ import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.filter.FilterFactory; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; -import org.olat.core.util.vfs.filters.VFSItemMetaFilter; +import org.olat.core.util.vfs.VFSMediaResource; +import org.olat.core.util.vfs.filters.VFSItemFilter; +import org.olat.core.util.vfs.filters.VFSLeafButSystemFilter; +import org.olat.core.util.vfs.filters.VFSSystemItemFilter; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.ForumChangedEvent; @@ -100,25 +108,26 @@ public class MessageEditController extends FormBasicController { private static final String[] enableKeys = new String[]{ "on" }; private RichTextElement bodyEl; - private TextElement titleEl, pseudonymEl, passwordEl; + private TextElement titleEl; + private TextElement pseudonymEl; + private TextElement passwordEl; private MultipleSelectionElement usePseudonymEl; private FileElement fileUpload; - private DisplayPortraitController portraitCtr; private DialogBoxController confirmDeleteAttachmentCtrl; private VFSContainer tempUploadFolder; private boolean userIsMsgCreator; private boolean msgHasChildren; - private VFSItemMetaFilter exclFilter; private final Forum forum; private final EditMode editMode; private final boolean guestOnly; private String proposedPseudonym; private final ForumCallback foCallback; - private Message message, parentMessage; + private Message message; + private Message parentMessage; @Autowired private ForumManager fm; @@ -154,15 +163,10 @@ public class MessageEditController extends FormBasicController { this.guestOnly = ureq.getUserSession().getRoles().isGuestOnly(); tempUploadFolder = new LocalFolderImpl(new File(WebappHelper.getTmpDir(), CodeHelper.getUniqueID())); - exclFilter = new VFSItemMetaFilter(); - + initForm(ureq); } - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#initForm(org.olat.core.gui.components.form.flexible.FormItemContainer, - * org.olat.core.gui.control.Controller, org.olat.core.gui.UserRequest) - */ @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { formLayout.setElementCssClass("o_sel_forum_message_form"); @@ -171,7 +175,15 @@ public class MessageEditController extends FormBasicController { titleEl.setElementCssClass("o_sel_forum_message_title"); titleEl.setMandatory(true); titleEl.setNotEmptyCheck("error.field.not.empty"); - bodyEl = uifactory.addRichTextElementForStringData("msgBody", "msg.body", message.getBody(), 15, -1, true, null, null, + + VFSContainer msgContainer; + if(message.getKey() != null) { + msgContainer = fm.getMessageContainer(forum.getKey(), message.getKey()); + } else { + msgContainer = tempUploadFolder; + } + msgContainer = VFSManager.getOrCreateContainer(msgContainer, "media"); + bodyEl = uifactory.addRichTextElementForStringData("msgBody", "msg.body", message.getBody(), 15, -1, true, msgContainer, "media", null, formLayout, ureq.getUserSession(), getWindowControl()); bodyEl.setElementCssClass("o_sel_forum_message_body"); bodyEl.setMandatory(true); @@ -181,6 +193,8 @@ public class MessageEditController extends FormBasicController { bodyEl.getEditorConfiguration().enableCharCount(); bodyEl.getEditorConfiguration().setRelativeUrls(false); bodyEl.getEditorConfiguration().setRemoveScriptHost(false); + bodyEl.getEditorConfiguration().disableMedia(); + bodyEl.getEditorConfiguration().disableTinyMedia(); setEditPermissions(message); // list existing attachments. init attachment layout now, to place it in @@ -188,7 +202,7 @@ public class MessageEditController extends FormBasicController { createOrUpdateAttachmentListLayout(formLayout); // provide upload field - if (foCallback.mayEditMessageAsModerator() || ((userIsMsgCreator) && (msgHasChildren == false))) { + if (foCallback.mayEditMessageAsModerator() || (userIsMsgCreator && !msgHasChildren)) { fileUpload = uifactory.addFileElement(getWindowControl(), "msg.upload", formLayout); fileUpload.addActionListener(FormEvent.ONCHANGE); fileUpload.setMaxUploadSizeKB((int) FolderConfig.getLimitULKB(), "attachments.too.big", new String[] { ((Long) (FolderConfig @@ -248,27 +262,41 @@ public class MessageEditController extends FormBasicController { // save and cancel buttons FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); formLayout.add(buttonLayout); - uifactory.addFormSubmitButton("msg.save", buttonLayout); uifactory.addFormCancelButton("msg.cancel", buttonLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("msg.save", buttonLayout); // show message replying to, if in reply modus if (editMode == EditMode.reply) { - String previewPage = Util.getPackageVelocityRoot(this.getClass()) + "/msg-preview.html"; - FormLayoutContainer replyMsgLayout = FormLayoutContainer.createCustomFormLayout("replyMsg", getTranslator(), previewPage); - uifactory.addSpacerElement("spacer1", formLayout, false); - formLayout.add(replyMsgLayout); - - replyMsgLayout.setLabel("label.replytomsg", new String[] { StringHelper.escapeHtml(parentMessage.getTitle()) }); - replyMsgLayout.contextPut("messageBody", parentMessage.getBody()); - replyMsgLayout.contextPut("message", parentMessage); - replyMsgLayout.contextPut("guestOnly", new Boolean(guestOnly)); - - Identity creator = parentMessage.getCreator(); - if(creator != null) { - replyMsgLayout.contextPut("identity", creator); - portraitCtr = new DisplayPortraitController(ureq, getWindowControl(), creator, true, true); - replyMsgLayout.put("portrait", portraitCtr.getInitialComponent()); - } + initReply(formLayout, ureq); + } + } + + private void initReply(FormItemContainer formLayout, UserRequest ureq) { + String previewPage = Util.getPackageVelocityRoot(this.getClass()) + "/msg-preview.html"; + FormLayoutContainer replyMsgLayout = FormLayoutContainer.createCustomFormLayout("replyMsg", getTranslator(), previewPage); + uifactory.addSpacerElement("spacer1", formLayout, false); + formLayout.add(replyMsgLayout); + + replyMsgLayout.setLabel("label.replytomsg", new String[] { StringHelper.escapeHtml(parentMessage.getTitle()) }); + String body = parentMessage.getBody(); + + VFSContainer parentMessageContainer = fm.getMessageContainer(forum.getKey(), parentMessage.getKey()); + VFSItem parentMediaItem = parentMessageContainer.resolve("media"); + if(parentMediaItem instanceof VFSContainer) { + String mapper = registerCacheableMapper(ureq, "fo_reply_" + parentMessage.getKey(), + new BodyMediaMapper((VFSContainer)parentMediaItem)); + String messageMapperUri = mapper + "/" + parentMessage.getKey() + "/"; + body = FilterFactory.getBaseURLToMediaRelativeURLFilter(messageMapperUri).filter(body); + } + replyMsgLayout.contextPut("messageBody", body); + replyMsgLayout.contextPut("message", parentMessage); + replyMsgLayout.contextPut("guestOnly", Boolean.valueOf(guestOnly)); + + Identity creator = parentMessage.getCreator(); + if(creator != null) { + replyMsgLayout.contextPut("identity", creator); + portraitCtr = new DisplayPortraitController(ureq, getWindowControl(), creator, true, true); + replyMsgLayout.put("portrait", portraitCtr.getInitialComponent()); } } @@ -296,10 +324,10 @@ public class MessageEditController extends FormBasicController { // add already existing attachments: if (message.getKey() != null) { VFSContainer msgContainer = fm.getMessageContainer(message.getForum().getKey(), message.getKey()); - attachments.addAll(msgContainer.getItems(exclFilter)); + attachments.addAll(msgContainer.getItems(new VFSLeafButSystemFilter())); } // add files from TempFolder - attachments.addAll(getTempFolderFileList()); + attachments.addAll(getTempFolderFileList(new VFSLeafButSystemFilter())); Collections.sort(attachments, new Comparator<VFSItem>(){ final Collator c = Collator.getInstance(getLocale()); @@ -321,7 +349,7 @@ public class MessageEditController extends FormBasicController { int attNr = 1; for (VFSItem tmpFile : attachments) { FormLink tmpLink = uifactory.addFormLink(CMD_DELETE_ATTACHMENT + attNr, tmpLayout, Link.BUTTON_XSMALL); - if (!(foCallback.mayEditMessageAsModerator() || ((userIsMsgCreator) && (msgHasChildren == false)))) { + if (!(foCallback.mayEditMessageAsModerator() || (userIsMsgCreator && !msgHasChildren))) { tmpLink.setEnabled(false); tmpLink.setVisible(false); } @@ -331,9 +359,6 @@ public class MessageEditController extends FormBasicController { } } - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#doDispose() - */ @Override protected void doDispose() { removeTempUploadedFiles(); @@ -349,7 +374,7 @@ public class MessageEditController extends FormBasicController { @Override protected boolean validateFormLogic(UserRequest ureq) { - boolean allOk = true; + boolean allOk = super.validateFormLogic(ureq); if(usePseudonymEl != null) { pseudonymEl.clearError(); passwordEl.clearError(); @@ -368,7 +393,7 @@ public class MessageEditController extends FormBasicController { } } } - return allOk & super.validateFormLogic(ureq); + return allOk; } private boolean validatePseudonym(String value) { @@ -408,7 +433,7 @@ public class MessageEditController extends FormBasicController { if(StringHelper.containsNonWhitespace(password)) { List<Pseudonym> pseudonyms = fm.getPseudonyms(value); - if(pseudonyms.size() > 0) { + if(!pseudonyms.isEmpty()) { boolean authenticated = false; for(Pseudonym pseudonym:pseudonyms) { if(fm.authenticatePseudonym(pseudonym, password)) { @@ -455,11 +480,28 @@ public class MessageEditController extends FormBasicController { } // set values from form to message + commitBody(); + commitPseudonym(ureq); + + if(editMode == EditMode.newThread) { + commitNewThreadMode(); + } else if(editMode == EditMode.edit) { + commitEditMode(); + } else if(editMode == EditMode.reply) { + commitReplyMode(); + } + } + + private void commitBody() { message.setTitle(titleEl.getValue()); String body = bodyEl.getValue(); body = body.replace("<p> ", "<p>"); - + String editorMapperUri = Settings.createServerURI() + bodyEl.getEditorConfiguration().getMapperURI(); + body = body.replace(editorMapperUri, "media/"); message.setBody(body.trim()); + } + + private void commitPseudonym(UserRequest ureq) { if(usePseudonymEl != null && (usePseudonymEl.isAtLeastSelected(1) || guestOnly)) { String password = passwordEl.getValue(); String pseudonym = pseudonymEl.getValue(); @@ -492,58 +534,61 @@ public class MessageEditController extends FormBasicController { } else if(message.getCreator() != null && message.getCreator().equals(getIdentity())) { message.setPseudonym(null); } - - if(editMode == EditMode.newThread) { - if(foCallback.mayOpenNewThread()) { - // save a new thread - message = fm.addTopMessage(message); - fm.markNewMessageAsRead(getIdentity(), forum, message); - persistTempUploadedFiles(message); - // if notification is enabled -> notify the publisher about news - notifiySubscription(); - addLoggingResourceable(LoggingResourceable.wrap(message)); - //commit before sending events - DBFactory.getInstance().commit(); - ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.NEW_MESSAGE, message.getKey(), message.getKey(), getIdentity()); - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, forum); - ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_MESSAGE_CREATE, getClass()); - } else { - showWarning("may.not.save.msg.as.author"); - } - - } else if(editMode == EditMode.edit) { - boolean children = fm.countMessageChildren(message.getKey()) > 0; - if (foCallback.mayEditMessageAsModerator() || (userIsMsgCreator && !children)) { - message.setModifier(getIdentity()); - message = fm.updateMessage(message, true); - persistTempUploadedFiles(message); - notifiySubscription(); - //commit before sending events - DBFactory.getInstance().commit(); - Long threadTopKey = message.getThreadtop() == null ? null : message.getThreadtop().getKey(); - ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.CHANGED_MESSAGE, threadTopKey, message.getKey(), getIdentity()); - CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, forum); - ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_MESSAGE_EDIT, getClass(), - LoggingResourceable.wrap(message)); - } else { - showWarning("may.not.save.msg.as.author"); - } - } else if(editMode == EditMode.reply) { - message = fm.replyToMessage(message, parentMessage); + } + + private void commitNewThreadMode() { + if(foCallback.mayOpenNewThread()) { + // save a new thread + message = fm.addTopMessage(message); fm.markNewMessageAsRead(getIdentity(), forum, message); persistTempUploadedFiles(message); + // if notification is enabled -> notify the publisher about news notifiySubscription(); - Long threadTopKey = message.getThreadtop() == null ? null : message.getThreadtop().getKey(); - + addLoggingResourceable(LoggingResourceable.wrap(message)); //commit before sending events DBFactory.getInstance().commit(); - ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.NEW_MESSAGE, threadTopKey, message.getKey(), getIdentity()); + ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.NEW_MESSAGE, message.getKey(), message.getKey(), getIdentity()); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, forum); - ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_REPLY_MESSAGE_CREATE, getClass(), + ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_MESSAGE_CREATE, getClass()); + } else { + showWarning("may.not.save.msg.as.author"); + } + } + + private void commitEditMode() { + boolean children = fm.countMessageChildren(message.getKey()) > 0; + if (foCallback.mayEditMessageAsModerator() || (userIsMsgCreator && !children)) { + message.setModifier(getIdentity()); + message = fm.updateMessage(message, true); + persistTempUploadedFiles(message); + notifiySubscription(); + //commit before sending events + DBFactory.getInstance().commit(); + Long threadTopKey = message.getThreadtop() == null ? null : message.getThreadtop().getKey(); + ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.CHANGED_MESSAGE, threadTopKey, message.getKey(), getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, forum); + ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_MESSAGE_EDIT, getClass(), LoggingResourceable.wrap(message)); + } else { + showWarning("may.not.save.msg.as.author"); } } + private void commitReplyMode() { + message = fm.replyToMessage(message, parentMessage); + fm.markNewMessageAsRead(getIdentity(), forum, message); + persistTempUploadedFiles(message); + notifiySubscription(); + Long threadTopKey = message.getThreadtop() == null ? null : message.getThreadtop().getKey(); + + //commit before sending events + DBFactory.getInstance().commit(); + ForumChangedEvent event = new ForumChangedEvent(ForumChangedEvent.NEW_MESSAGE, threadTopKey, message.getKey(), getIdentity()); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, forum); + ThreadLocalUserActivityLogger.log(ForumLoggingAction.FORUM_REPLY_MESSAGE_CREATE, getClass(), + LoggingResourceable.wrap(message)); + } + private void notifiySubscription() { if (foCallback.getSubscriptionContext() != null) { notificationsManager.markPublisherNews(foCallback.getSubscriptionContext(), getIdentity(), true); @@ -570,7 +615,7 @@ public class MessageEditController extends FormBasicController { // checking tmp-folder and msg-container for filename boolean fileExists = false; - if (getTempFolderFileList().contains(fileName)) { + if (getTempFolderFilenameList(new VFSSystemItemFilter()).contains(fileName)) { fileExists = true; } if (message.getKey() != null) { @@ -612,7 +657,7 @@ public class MessageEditController extends FormBasicController { confirmDeleteAttachmentCtrl = activateYesNoDialog(ureq, null, translate("reallydeleteAtt"), confirmDeleteAttachmentCtrl); confirmDeleteAttachmentCtrl.setUserObject(file); } else { - if ((userIsMsgCreator) && (msgHasChildren == true)) { + if (userIsMsgCreator && msgHasChildren) { // user is author of the current message but it has already at // least one child showWarning("may.not.delete.att.as.author"); @@ -626,22 +671,27 @@ public class MessageEditController extends FormBasicController { } } - private List<VFSItem> getTempFolderFileList() { + private List<VFSItem> getTempFolderFileList(VFSItemFilter filter) { if (tempUploadFolder == null) { tempUploadFolder = VFSManager.olatRootContainer(File.separator + "tmp/" + CodeHelper.getGlobalForeverUniqueID() + "/", null); } - return tempUploadFolder.getItems(exclFilter); + return tempUploadFolder.getItems(filter); + } + + private List<String>getTempFolderFilenameList(VFSItemFilter filter) { + List<VFSItem> items = getTempFolderFileList(filter); + List<String> filenames = new ArrayList<>(items.size()); + for(VFSItem item:items) { + filenames.add(item.getName()); + } + return filenames; } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) - */ @Override protected void event(UserRequest ureq, Controller source, Event event) { super.event(ureq, source, event); if (source == confirmDeleteAttachmentCtrl) { - if (DialogBoxUIFactory.isYesEvent(event)) { // ok to really delete this // attachment + if (DialogBoxUIFactory.isYesEvent(event)) { // ok to really delete this attachment Object userObj = confirmDeleteAttachmentCtrl.getUserObject(); if (userObj instanceof VFSLeaf) { ((VFSLeaf)userObj).delete(); @@ -680,23 +730,40 @@ public class MessageEditController extends FormBasicController { * @param tmpMessage */ public void persistTempUploadedFiles(Message tmpMessage) { - if (tmpMessage == null) throw new AssertException("Message may not be null to persist temp files"); + if (tmpMessage == null) return; + VFSContainer msgContainer = fm.getMessageContainer(forum.getKey(), message.getKey()); if (msgContainer != null) { - List<VFSItem> tmpFList = getTempFolderFileList(); + List<VFSItem> tmpFList = getTempFolderFileList(new VFSSystemItemFilter()); for (VFSItem file : tmpFList) { - VFSLeaf leaf = (VFSLeaf) file; - try { - VFSLeaf targetFile = msgContainer.createChildLeaf(leaf.getName()); - VFSManager.copyContent(leaf, targetFile, false); - } catch (Exception e) { - removeTempUploadedFiles(); - throw new RuntimeException ("I/O error saving uploaded file:" + msgContainer + "/" + leaf.getName()); + if(file instanceof VFSLeaf) { + copyTempContent((VFSLeaf) file, msgContainer); + } else if(file instanceof VFSContainer && "media".equals(file.getName())) { + copyTempMediaContent((VFSContainer)file, msgContainer); } } } removeTempUploadedFiles(); } + + private void copyTempMediaContent(VFSContainer tempContainer, VFSContainer msgContainer) { + List<VFSItem> tempEmbededFList = tempContainer.getItems(new VFSSystemItemFilter()); + VFSContainer mediaContainer = VFSManager.getOrCreateContainer(msgContainer, "media"); + for(VFSItem file:tempEmbededFList) { + if(file instanceof VFSLeaf) { + copyTempContent((VFSLeaf) file, mediaContainer); + } + } + } + + private void copyTempContent(VFSLeaf leaf, VFSContainer msgContainer) { + try { + VFSLeaf targetFile = msgContainer.createChildLeaf(leaf.getName()); + VFSManager.copyContent(leaf, targetFile, false); + } catch (Exception e) { + logError("Cannot move files", e); + } + } private void removeTempUploadedFiles() { if (tempUploadFolder != null) { @@ -704,4 +771,30 @@ public class MessageEditController extends FormBasicController { tempUploadFolder = null; } } + + private class BodyMediaMapper implements Mapper { + + private final VFSContainer mediaContainer; + + public BodyMediaMapper(VFSContainer mediaContainer) { + this.mediaContainer = mediaContainer; + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + String[] query = relPath.split("/"); // expected path looks like this /messageId/attachmentUUID/filename + MediaResource resource = null; + if (query.length == 4) { + VFSItem item = mediaContainer.resolve(query[3]); + if(item instanceof VFSLeaf) { + resource = new VFSMediaResource((VFSLeaf)item); + } + } + // In any error case, send not found + if(resource == null) { + resource = new NotFoundMediaResource(); + } + return resource; + } + } } diff --git a/src/main/java/org/olat/modules/fo/ui/MessageListController.java b/src/main/java/org/olat/modules/fo/ui/MessageListController.java index 566214a0616..7d05902624a 100644 --- a/src/main/java/org/olat/modules/fo/ui/MessageListController.java +++ b/src/main/java/org/olat/modules/fo/ui/MessageListController.java @@ -78,13 +78,14 @@ import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; +import org.olat.core.util.filter.FilterFactory; import org.olat.core.util.prefs.Preferences; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; -import org.olat.core.util.vfs.filters.VFSItemMetaFilter; +import org.olat.core.util.vfs.filters.VFSLeafButSystemFilter; import org.olat.course.nodes.FOCourseNode; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; @@ -99,6 +100,7 @@ import org.olat.modules.fo.export.FinishCallback; import org.olat.modules.fo.export.SendMailStepForm; import org.olat.modules.fo.export.Step_1_SelectCourse; import org.olat.modules.fo.manager.ForumManager; +import org.olat.modules.fo.manager.QuoterFilter; import org.olat.modules.fo.portfolio.ForumMediaHandler; import org.olat.modules.fo.ui.MessageEditController.EditMode; import org.olat.modules.fo.ui.events.DeleteMessageEvent; @@ -440,8 +442,11 @@ public class MessageListController extends BasicController implements GenericEve for(MarkResourceStat stat:statList) { stats.put(stat.getSubPath(), stat); } - - MessageView view = new MessageView(message, userPropertyHandlers, getLocale()); + + String body = message.getBody(); + String messageMapperUri = thumbnailMapper + "/" + message.getKey() + "/"; + body = FilterFactory.getBaseURLToMediaRelativeURLFilter(messageMapperUri).filter(body); + MessageView view = new MessageView(message, body, userPropertyHandlers, getLocale()); view.setNumOfChildren(0); addMessageToCurrentMessagesAndVC(ureq, message, view, marks, stats, rms); return view; @@ -475,7 +480,10 @@ public class MessageListController extends BasicController implements GenericEve List<MessageView> views = new ArrayList<>(messages.size()); Map<Long,MessageView> keyToViews = new HashMap<>(); for(MessageLight msg:messages) { - MessageView view = new MessageView(msg, userPropertyHandlers, getLocale()); + String body = msg.getBody(); + String messageMapperUri = thumbnailMapper + "/" + msg.getKey() + "/"; + body = FilterFactory.getBaseURLToMediaRelativeURLFilter(messageMapperUri).filter(body); + MessageView view = new MessageView(msg, body, userPropertyHandlers, getLocale()); view.setNumOfChildren(0); views.add(view); keyToViews.put(msg.getKey(), view); @@ -610,7 +618,13 @@ public class MessageListController extends BasicController implements GenericEve VFSContainer msgContainer = forumManager.getMessageContainer(forum.getKey(), m.getKey()); if(msgContainer != null) { messageView.setMessageContainer(msgContainer); - List<VFSItem> attachments = new ArrayList<>(msgContainer.getItems(new VFSItemMetaFilter())); + List<VFSItem> attachmentsItem = msgContainer.getItems(new VFSLeafButSystemFilter()); + List<VFSLeaf> attachments = new ArrayList<>(attachmentsItem.size()); + for(VFSItem attachmentItem:attachmentsItem) { + if(attachmentItem instanceof VFSLeaf) { + attachments.add((VFSLeaf)attachmentItem); + } + } messageView.setAttachments(attachments); } else { messageView.setAttachments(new ArrayList<>()); @@ -806,11 +820,11 @@ public class MessageListController extends BasicController implements GenericEve String messageKey = cmd.substring(index + 1); int position = Integer.parseInt(attachmentPosition); - Long key = new Long(messageKey); + Long key = Long.valueOf(messageKey); for(MessageView view:backupViews) { if(view.getKey().equals(key)) { - List<VFSItem> attachments = view.getAttachments(); - VFSLeaf attachment = (VFSLeaf)attachments.get(position - 1);//velocity counter start with 1 + List<VFSLeaf> attachments = view.getAttachments(); + VFSLeaf attachment = attachments.get(position - 1);//velocity counter start with 1 VFSMediaResource fileResource = new VFSMediaResource(attachment); fileResource.setDownloadable(true); // prevent XSS attack res = fileResource; @@ -949,26 +963,8 @@ public class MessageListController extends BasicController implements GenericEve } newMessage.setTitle(reString + parentMessage.getTitle()); if (quote) { - // load message to form as quotation - StringBuilder quoteSb = new StringBuilder(); - quoteSb.append("<p></p><div class=\"o_quote_wrapper\"><div class=\"o_quote_author mceNonEditable\">"); - String date = formatter.formatDateAndTime(parentMessage.getCreationDate()); - String creatorName; - if(StringHelper.containsNonWhitespace(parentMessage.getPseudonym())) { - creatorName = parentMessage.getPseudonym(); - } else if(parentMessage.isGuest()) { - creatorName = translate("guest"); - } else { - User creator = parentMessage.getCreator().getUser(); - creatorName = creator.getProperty(UserConstants.FIRSTNAME, getLocale()) + " " + creator.getProperty(UserConstants.LASTNAME, getLocale()); - } - - quoteSb.append(translate("msg.quote.intro", new String[]{ date, creatorName})) - .append("</div><blockquote class=\"o_quote\">") - .append(parentMessage.getBody()) - .append("</blockquote></div>") - .append("<p></p>"); - newMessage.setBody(quoteSb.toString()); + String quoted = buildReplyWithQuote(parentMessage); + newMessage.setBody(quoted); } replyMessageCtrl = new MessageEditController(ureq, getWindowControl(), forum, foCallback, newMessage, parentMessage, EditMode.reply); @@ -983,6 +979,31 @@ public class MessageListController extends BasicController implements GenericEve } } + private String buildReplyWithQuote(Message parentMessage) { + // load message to form as quotation + StringBuilder quoteSb = new StringBuilder(); + quoteSb.append("<p></p><div class=\"o_quote_wrapper\"><div class=\"o_quote_author mceNonEditable\">"); + String date = formatter.formatDateAndTime(parentMessage.getCreationDate()); + String creatorName; + if(StringHelper.containsNonWhitespace(parentMessage.getPseudonym())) { + creatorName = parentMessage.getPseudonym(); + } else if(parentMessage.isGuest()) { + creatorName = translate("guest"); + } else { + User creator = parentMessage.getCreator().getUser(); + creatorName = creator.getProperty(UserConstants.FIRSTNAME, getLocale()) + " " + creator.getProperty(UserConstants.LASTNAME, getLocale()); + } + + String originalBody = parentMessage.getBody(); + String filteredBody = new QuoterFilter().filter(originalBody); + quoteSb.append(translate("msg.quote.intro", new String[]{ date, creatorName})) + .append("</div><blockquote class=\"o_quote\">") + .append(filteredBody) + .append("</blockquote></div>") + .append("<p></p>"); + return quoteSb.toString(); + } + private void doConfirmDeleteMessage(UserRequest ureq, MessageView message) { // user has clicked on button 'delete' // -> display modal dialog 'Do you really want to delete this message?' @@ -1114,9 +1135,7 @@ public class MessageListController extends BasicController implements GenericEve private void doArchiveThread(UserRequest ureq, Message currMsg) { Message m = currMsg.getThreadtop(); Long topMessageId = (m == null) ? currMsg.getKey() : m.getKey(); - - VFSContainer forumContainer = forumManager.getForumContainer(forum.getKey()); - ForumDownloadResource download = new ForumDownloadResource("Forum", forum, foCallback, topMessageId, forumContainer, getLocale()); + ForumDownloadResource download = new ForumDownloadResource("Forum", forum, foCallback, topMessageId, getLocale()); ureq.getDispatchResult().setResultingMediaResource(download); } @@ -1490,37 +1509,70 @@ public class MessageListController extends BasicController implements GenericEve @Override public MediaResource handle(String relPath, HttpServletRequest request) { String[] query = relPath.split("/"); // expected path looks like this /messageId/attachmentUUID/filename + + MediaResource resource = null; if (query.length == 4) { - try { - Long mId = Long.valueOf(Long.parseLong(query[1])); - MessageView view = null; - for (MessageView m : backupViews) { - // search for message in current message map - if (m.getKey().equals(mId)) { - view = m; - break; - } - } - if (view != null) { - List<VFSItem> attachments = view.getAttachments(); - for (VFSItem vfsItem : attachments) { - VFSMetadata meta = vfsItem.getMetaInfo(); - if (meta instanceof VFSLeaf && meta.getUuid().equals(query[2])) { - VFSLeaf thumb = vfsRepositoryService.getThumbnail((VFSLeaf)vfsItem, meta, 200, 200, false); - if(thumb != null) { - // Positive lookup, send as response - return new VFSMediaResource(thumb); - } - break; - } - } + MessageView view = getView(query[1]); + if (view != null) { + if("media".equals(query[2])) { + resource = getMedia(view, query[3]); + } else { + resource = getThumbnail(view, query[2]); } - } catch (NumberFormatException e) { - // } } // In any error case, send not found - return new NotFoundMediaResource(); + if(resource == null) { + resource = new NotFoundMediaResource(); + } + return resource; + } + + private MessageView getView(String queryParam) { + MessageView view = null; + try { + Long mId = Long.valueOf(Long.parseLong(queryParam )); + for (MessageView m : backupViews) { + // search for message in current message map + if (m.getKey().equals(mId)) { + view = m; + break; + } + } + } catch (NumberFormatException e) { + // + } + return view; + } + + private MediaResource getMedia(MessageView view, String queryParam) { + VFSContainer messageContainer = view.getMessageContainer(); + if(messageContainer == null) return null; + VFSItem mediaItem = messageContainer.resolve("media"); + if(mediaItem instanceof VFSContainer) { + VFSContainer mediaContainer = (VFSContainer)mediaItem; + VFSItem media = mediaContainer.resolve(queryParam); + if(media instanceof VFSLeaf) { + return new VFSMediaResource((VFSLeaf)media); + } + } + return null; + } + + private MediaResource getThumbnail(MessageView view, String queryParam) { + List<VFSLeaf> attachments = view.getAttachments(); + for (VFSLeaf attachment : attachments) { + VFSMetadata meta = attachment.getMetaInfo(); + if (meta.getUuid().equals(queryParam)) { + VFSLeaf thumb = vfsRepositoryService.getThumbnail(attachment, meta, 200, 200, false); + if(thumb != null) { + // Positive lookup, send as response + return new VFSMediaResource(thumb); + } + break; + } + } + return null; } } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/fo/ui/MessageView.java b/src/main/java/org/olat/modules/fo/ui/MessageView.java index 64bace8dce5..c92e46ad79b 100644 --- a/src/main/java/org/olat/modules/fo/ui/MessageView.java +++ b/src/main/java/org/olat/modules/fo/ui/MessageView.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Locale; import org.olat.core.util.vfs.VFSContainer; -import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; import org.olat.modules.fo.MessageLight; import org.olat.user.DisplayPortraitController; import org.olat.user.propertyhandlers.UserPropertyHandler; @@ -54,14 +54,14 @@ public class MessageView extends MessageLightView { private boolean closed; private boolean moved; - private List<VFSItem> attachments; + private List<VFSLeaf> attachments; private VFSContainer messageContainer; private DisplayPortraitController portrait; - public MessageView(MessageLight message, List<UserPropertyHandler> userPropertyHandlers, Locale locale) { + public MessageView(MessageLight message, String body, List<UserPropertyHandler> userPropertyHandlers, Locale locale) { super(message, userPropertyHandlers, locale); - body = message.getBody(); + this.body = body; } public String getBody() { @@ -165,16 +165,16 @@ public class MessageView extends MessageLightView { this.closed = closed; } - public List<VFSItem> getAttachments() { + public List<VFSLeaf> getAttachments() { return attachments; } - public void setAttachments(List<VFSItem> attachments) { + public void setAttachments(List<VFSLeaf> attachments) { this.attachments = attachments; } public boolean hasAttachments() { - return attachments != null && attachments.size() > 0; + return attachments != null && !attachments.isEmpty(); } public VFSContainer getMessageContainer() { diff --git a/src/main/java/org/olat/modules/fo/ui/ThreadListController.java b/src/main/java/org/olat/modules/fo/ui/ThreadListController.java index 6c3f9eef2b3..c58cf5733f2 100644 --- a/src/main/java/org/olat/modules/fo/ui/ThreadListController.java +++ b/src/main/java/org/olat/modules/fo/ui/ThreadListController.java @@ -48,7 +48,6 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.id.Identity; import org.olat.core.util.Util; -import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.Message; @@ -260,8 +259,7 @@ public class ThreadListController extends FormBasicController { } private void doArchiveForum(UserRequest ureq) { - VFSContainer forumContainer = forumManager.getForumContainer(forum.getKey()); - ForumDownloadResource download = new ForumDownloadResource("Forum", forum, foCallback, null, forumContainer, getLocale()); + ForumDownloadResource download = new ForumDownloadResource("Forum", forum, foCallback, null, getLocale()); ureq.getDispatchResult().setResultingMediaResource(download); } -- GitLab