diff --git a/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_de.properties b/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_de.properties index 898e9faf321e4a3269fd9b95287e658d72fb42fe..3f0c3d3be7b74d80fdce37bef0f0c399bd34abf5 100644 --- a/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_de.properties +++ b/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_de.properties @@ -1,3 +1,3 @@ #Mon Mar 02 09:54:03 CET 2009 -site.title=Intranet -site.title.alt=ZAG Intranet +site.title=Infos +site.title.alt=Infos diff --git a/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_en.properties b/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_en.properties index e84a774e3ec6243e8eb02239420cf84929c678cf..2809c7dd455dbf500a911521d57e176271d26211 100644 --- a/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_en.properties +++ b/src/main/java/ch/goodsolutions/olat/intranetsite/_i18n/LocalStrings_en.properties @@ -1,3 +1,3 @@ #Mon Mar 02 09:54:17 CET 2009 -site.title=Intranet -site.title.alt=ZAG Intranet +site.title=Infos +site.title.alt=Infos diff --git a/src/main/java/de/bps/course/nodes/ll/LLEditController.java b/src/main/java/de/bps/course/nodes/ll/LLEditController.java index 8bfb3f4af92c8422483919194c0167afe82e24ca..0f026c1a24dd3468650005c983a07e7b31d4c465 100644 --- a/src/main/java/de/bps/course/nodes/ll/LLEditController.java +++ b/src/main/java/de/bps/course/nodes/ll/LLEditController.java @@ -25,6 +25,7 @@ import org.olat.core.gui.control.ControllerEventListener; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController; +import org.olat.core.util.StringHelper; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.condition.Condition; @@ -71,7 +72,7 @@ public class LLEditController extends ActivateableTabbableDefaultController impl userCourseEnv); this.listenTo(accessibilityCondContr); - llFormContr = new LLEditForm(ureq, getWindowControl(), this.moduleConfiguration); + llFormContr = new LLEditForm(ureq, getWindowControl(), this.moduleConfiguration, course.getCourseEnvironment()); llFormContr.addControllerListener(this); editVc.put("llEditForm", llFormContr.getInitialComponent()); @@ -146,6 +147,10 @@ public class LLEditController extends ActivateableTabbableDefaultController impl List<LLModel> linkList = (List<LLModel>) moduleConfig.get(LLCourseNode.CONF_LINKLIST); if (linkList != null) { for (LLModel link : linkList) { + if (link.isIntern() && StringHelper.containsNonWhitespace(link.getTarget()) && + StringHelper.containsNonWhitespace(link.getDescription())) { + return true; + } if (link.getTarget().isEmpty() || link.getDescription().isEmpty()) { return false; } URL target = null; try { diff --git a/src/main/java/de/bps/course/nodes/ll/LLEditForm.java b/src/main/java/de/bps/course/nodes/ll/LLEditForm.java index 9d15a14c332e27feea6414afe9038f91b01126b6..f416e3b4704c765f8aeab8d37aa56b8b70fffd3b 100644 --- a/src/main/java/de/bps/course/nodes/ll/LLEditForm.java +++ b/src/main/java/de/bps/course/nodes/ll/LLEditForm.java @@ -18,7 +18,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import org.olat.core.commons.controllers.linkchooser.MediaChooserController; +import org.olat.core.commons.controllers.linkchooser.URLChoosenEvent; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; import org.olat.core.gui.components.form.ValidationError; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -33,9 +36,14 @@ import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; import org.olat.core.gui.components.form.flexible.impl.elements.ItemValidatorProvider; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.helpers.Settings; +import org.olat.core.util.StringHelper; +import org.olat.core.util.vfs.VFSContainer; import org.olat.course.editor.NodeEditController; +import org.olat.course.run.environment.CourseEnvironment; import org.olat.modules.ModuleConfiguration; import de.bps.course.nodes.LLCourseNode; @@ -60,12 +68,17 @@ public class LLEditForm extends FormBasicController { private List<TextElement> lDescriptionInputList; private List<TextElement> lCommentInputList; private List<FormLink> lDelButtonList; + private List<FormLink> lCustomMediaButtonList; private List<LLModel> linkList; private List<FormLink> lAddButtonList; private FormLayoutContainer titleContainer; private long counter = 0; + private MediaChooserController mediaChooserController; + private CloseableModalController mediaDialogBox; + private LLModel currentLink; + private final CourseEnvironment courseEnv; - public LLEditForm(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfig) { + public LLEditForm(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfig, CourseEnvironment courseEnv) { super(ureq, wControl); this.moduleConfig = moduleConfig; // read existing links from config @@ -82,6 +95,11 @@ public class LLEditForm extends FormBasicController { this.lAddButtonList = new ArrayList<FormLink>(linkList.size()); // list of all link deletion action buttons this.lDelButtonList = new ArrayList<FormLink>(linkList.size()); + //list of all custom media buttons + this.lCustomMediaButtonList = new ArrayList<FormLink>(linkList.size()); + + this.courseEnv = courseEnv; + initForm(this.flc, this, ureq); } @@ -102,7 +120,12 @@ public class LLEditForm extends FormBasicController { for (int i = 0; i < lTargetInputList.size(); i++) { LLModel link = (LLModel) lTargetInputList.get(i).getUserObject(); String linkValue = lTargetInputList.get(i).getValue(); - if(!linkValue.contains("://")) { + if(link.isIntern()) { + if(!linkValue.contains("://") && !linkValue.startsWith("/")) { + linkValue = "/".concat(linkValue.trim()); + lTargetInputList.get(i).setValue(linkValue); + } + } else if(!linkValue.contains("://")) { linkValue = "http://".concat(linkValue.trim()); lTargetInputList.get(i).setValue(linkValue); } @@ -151,18 +174,81 @@ public class LLEditForm extends FormBasicController { if (linkList.size() == 1) { // clear this line lTargetInputList.get(0).setValue(""); + lTargetInputList.get(0).setEnabled(true); lDescriptionInputList.get(0).setValue(""); lCommentInputList.get(0).setValue(""); } else { final LLModel link = (LLModel) ((FormLink) source).getUserObject(); removeFormLink(link); } + } else if (lCustomMediaButtonList.contains(source)) { + currentLink = (LLModel) ((FormLink) source).getUserObject(); + + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + + VFSContainer courseContainer = courseEnv.getCourseFolderContainer(); + mediaChooserController = new MediaChooserController(ureq, getWindowControl(), courseContainer, null, null, "", null); + listenTo(mediaChooserController); + + mediaDialogBox = new CloseableModalController(getWindowControl(), translate("choose"), mediaChooserController.getInitialComponent()); + mediaDialogBox.activate(); + listenTo(mediaDialogBox); } } super.formInnerEvent(ureq, source, event); fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); } + @Override + public void event(UserRequest ureq, Component source, Event event) { + super.event(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == mediaDialogBox) { + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + mediaDialogBox = null; + mediaChooserController = null; + } else if (source == mediaChooserController) { + if(event instanceof URLChoosenEvent) { + URLChoosenEvent choosenEvent = (URLChoosenEvent)event; + String url = choosenEvent.getURL(); + if(url.startsWith(Settings.getServerContextPathURI())) { + //doesn't allow absolute path -> the mapper check if the link is in the list! + url = url.substring(Settings.getServerContextPathURI().length()); + } + currentLink.setTarget(url); + currentLink.setIntern(true); + currentLink.setHtmlTarget("_blank"); + if(StringHelper.containsNonWhitespace(choosenEvent.getDisplayName())) { + currentLink.setDescription(choosenEvent.getDisplayName()); + } + + int index = 0; + for(TextElement targetEl:lTargetInputList) { + if(currentLink.equals(targetEl.getUserObject())) { + targetEl.setValue(url); + targetEl.setEnabled(false); + lDescriptionInputList.get(index).setValue(currentLink.getDescription()); + lHtmlTargetInputList.get(index).select(BLANK_KEY, true); + break; + } + index++; + } + } + mediaDialogBox.deactivate(); + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + mediaDialogBox = null; + mediaChooserController = null; + } + + super.event(ureq, source, event); + } + /** * {@inheritDoc} */ @@ -185,6 +271,7 @@ public class LLEditForm extends FormBasicController { titleContainer.contextPut("lCommentInputList", lCommentInputList); titleContainer.contextPut("lAddButtonList", lAddButtonList); titleContainer.contextPut("lDelButtonList", lDelButtonList); + titleContainer.contextPut("lCustomMediaButtonList", lCustomMediaButtonList); subm = new FormSubmit("subm", "submit"); @@ -211,6 +298,7 @@ public class LLEditForm extends FormBasicController { // add link target TextElement lTarget = uifactory.addTextElement("target" + counter, null, -1, link.getTarget(), titleContainer); lTarget.clearError(); + lTarget.setEnabled(!link.isIntern()); lTarget.setDisplaySize(40); lTarget.setMandatory(true); lTarget.setNotEmptyCheck("ll.table.target.error"); @@ -262,6 +350,13 @@ public class LLEditForm extends FormBasicController { delButton.setUserObject(link); titleContainer.add(delButton); lDelButtonList.add(index, delButton); + // custom media action button + FormLink mediaButton = new FormLinkImpl("media" + counter, "media" + counter, " ", Link.NONTRANSLATED); + mediaButton.setCustomEnabledLinkCSS("b_small o_ll_browse"); + mediaButton.setUserObject(link); + titleContainer.add(mediaButton); + lCustomMediaButtonList.add(index, mediaButton); + // increase the counter to enable unique component names counter++; } @@ -307,5 +402,11 @@ public class LLEditForm extends FormBasicController { } } titleContainer.remove(lDelButtonList.remove(i)); + for (i = 0; i < lCustomMediaButtonList.size(); i++) { + if (lCustomMediaButtonList.get(i).getUserObject().equals(link)) { + break; + } + } + titleContainer.remove(lCustomMediaButtonList.remove(i)); } } diff --git a/src/main/java/de/bps/course/nodes/ll/LLModel.java b/src/main/java/de/bps/course/nodes/ll/LLModel.java index 203078e913e560f6160c065436055f2f4789d552..9546374e3af1b420ad5b196f8d8be9d185c1a1be 100644 --- a/src/main/java/de/bps/course/nodes/ll/LLModel.java +++ b/src/main/java/de/bps/course/nodes/ll/LLModel.java @@ -32,15 +32,17 @@ public class LLModel implements Serializable { private String description = ""; private String comment = ""; private String htmlTarget = "_blank"; + private boolean intern = false; public LLModel() { // nothing to do } - public LLModel(final String target, final String description, final String comment) { + public LLModel(final String target, final String description, final String comment, final boolean intern) { this.target = target; this.description = description; this.comment = comment; + this.intern = intern; } /** @@ -99,4 +101,17 @@ public class LLModel implements Serializable { this.comment = comment; } + /** + * @return True if it's an intern link + */ + public boolean isIntern() { + return intern; + } + + /** + * @param intern + */ + public void setIntern(boolean intern) { + this.intern = intern; + } } diff --git a/src/main/java/de/bps/course/nodes/ll/LLRunController.java b/src/main/java/de/bps/course/nodes/ll/LLRunController.java index 5b3c0d0d723b4bf96758f511a1679e37f3d467c0..04df7c61a5a616094b7652e135dd821b24aa4e78 100644 --- a/src/main/java/de/bps/course/nodes/ll/LLRunController.java +++ b/src/main/java/de/bps/course/nodes/ll/LLRunController.java @@ -14,12 +14,25 @@ package de.bps.course.nodes.ll; import java.util.List; +import javax.servlet.http.HttpServletRequest; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.controllers.linkchooser.CustomMediaChooserController; +import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.media.ForbiddenMediaResource; +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; +import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.ModuleConfiguration; @@ -37,14 +50,29 @@ import de.bps.course.nodes.LLCourseNode; public class LLRunController extends BasicController { private VelocityContainer runVC; + public LLRunController(UserRequest ureq, WindowControl wControl, ModuleConfiguration moduleConfig, LLCourseNode llCourseNode, UserCourseEnvironment userCourseEnv, boolean showLinkComments) { super(ureq, wControl); this.runVC = this.createVelocityContainer("run"); - final List<LLModel> linkList = (List<LLModel>) llCourseNode.getModuleConfiguration().get(LLCourseNode.CONF_LINKLIST); + final List<LLModel> linkList = (List<LLModel>) moduleConfig.get(LLCourseNode.CONF_LINKLIST); this.runVC.contextPut("linkList", linkList); this.runVC.contextPut("showLinkComments", Boolean.valueOf(showLinkComments)); + + + CourseEnvironment courseEnv = userCourseEnv.getCourseEnvironment(); + VFSContainer courseContainer = courseEnv.getCourseFolderContainer(); + Mapper customMapper = null; + if (CoreSpringFactory.containsBean(CustomMediaChooserController.class.getName())) { + CustomMediaChooserController customMediaChooserFactory = (CustomMediaChooserController) CoreSpringFactory.getBean(CustomMediaChooserController.class.getName()); + customMapper = customMediaChooserFactory.getMapperInstance(courseContainer, null, null); + } + String mapperID = courseEnv.getCourseResourceableId() + "/" + llCourseNode.getIdent(); + String mapperBaseUrl = registerCacheableMapper(mapperID, new LLMapper(linkList, customMapper, courseContainer)); + + runVC.contextPut("mapperBaseUrl", mapperBaseUrl); + putInitialPanel(runVC); } @@ -58,5 +86,39 @@ public class LLRunController extends BasicController { protected void event(UserRequest ureq, Component source, Event event) { // nothing to do here } + + public class LLMapper implements Mapper { + private final List<LLModel> linkList; + private final Mapper customMediaMapper; + private final VFSContainer courseContainer; + + public LLMapper(List<LLModel> linkList, Mapper customMediaMapper, VFSContainer courseContainer) { + this.linkList = linkList; + this.customMediaMapper = customMediaMapper; + this.courseContainer = courseContainer; + } + public MediaResource handle(String relPath, HttpServletRequest request) { + boolean ok = false; + for(LLModel link:linkList) { + if(relPath.equals(link.getTarget())) { + ok = true; + break; + } + } + + if(ok) { + //is this a file in course directory + VFSItem item = courseContainer.resolve(relPath); + if(item instanceof VFSLeaf) { + return new VFSMediaResource((VFSLeaf)item); + } else if(customMediaMapper != null) { + return customMediaMapper.handle(relPath, request); + } + return new NotFoundMediaResource(relPath); + } else { + return new ForbiddenMediaResource(relPath); + } + } + } } diff --git a/src/main/java/de/bps/course/nodes/ll/_content/editForm.html b/src/main/java/de/bps/course/nodes/ll/_content/editForm.html index 06592ea5ada781dfb77c2ff6e7d386a188546eb2..07fe57a777941f65b82ba445d4e9f87a5ef01b24 100644 --- a/src/main/java/de/bps/course/nodes/ll/_content/editForm.html +++ b/src/main/java/de/bps/course/nodes/ll/_content/editForm.html @@ -1,11 +1,14 @@ ## <table> - <tr> - <td>$r.translate("ll.table.target")</td> - <td>$r.translate("ll.table.html_target")</td> - <td>$r.translate("ll.table.description")</td> - <td colspan="2">$r.translate("ll.table.comment")</td> - </tr> + <thead> + <tr> + <td>$r.translate("ll.table.target")</td> + #if($lCustomMediaButtonList) <td></td> #end + <td>$r.translate("ll.table.html_target")</td> + <td>$r.translate("ll.table.description")</td> + <td colspan="2">$r.translate("ll.table.comment")</td> + </tr> + </thead> #foreach($link in $linkList) #set( $iter = $velocityCount - 1) #set( $target = $lTargetInputList.get($iter).getName() ) @@ -26,6 +29,7 @@ <tr> <td>$r.render($lTargetInputList.get($iter).getName())</td> + #if($lCustomMediaButtonList) <td>$r.render($lCustomMediaButtonList.get($iter).getName())</td> #end <td>$r.render($lHtmlTargetInputList.get($iter).getName())</td> <td>$r.render($lDescriptionInputList.get($iter).getName())</td> <td>$r.render($lCommentInputList.get($iter).getName())</td> diff --git a/src/main/java/de/bps/course/nodes/ll/_content/run.html b/src/main/java/de/bps/course/nodes/ll/_content/run.html index 78bb7e22bf6cfae8b7cec3c8b81ca095a1dcdf87..cdd5dd2e3dc7c93a02088fd2846c2e2f4d0ef245 100644 --- a/src/main/java/de/bps/course/nodes/ll/_content/run.html +++ b/src/main/java/de/bps/course/nodes/ll/_content/run.html @@ -2,7 +2,7 @@ <ul> #foreach($link in $linkList) <li> - <a href="$link.getTarget()" target="$link.getHtmlTarget()" class="b_link_extern">$link.getDescription()</a> + <a href="#if($link.intern) $mapperBaseUrl$link.getTarget() #else $link.getTarget() #end" target="$link.getHtmlTarget()" class="b_link_extern">$link.getDescription()</a> #if($showLinkComments) <div> $link.getComment() diff --git a/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_de.properties index 3706d27a4eeac6041124e48da02e631d5a7245c2..c631f7b7a453876198720935a114a8be2cc32727 100644 --- a/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_de.properties +++ b/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_de.properties @@ -12,6 +12,7 @@ ll.table.description = Beschreibung ll.table.comment = Kommentar ll.table.delete = - ll.table.add = + +ll.table.media = Media ll.table.target.error = Bitte URL eintragen ll.table.target.error.format = Ungültiges URL Format diff --git a/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_en.properties index c1a0cbdb2297f096aa0b19ccea31bc012bd97765..39ae9c3b24664dd3474b96f50fc3447e243de7b1 100644 --- a/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_en.properties +++ b/src/main/java/de/bps/course/nodes/ll/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 21 13:27:02 CET 2010 +#Thu May 26 09:52:33 CEST 2011 condition.accessibility.title=Access config.header=Link list config.nolinks.long=You have not created any links in this list yet. @@ -10,6 +10,7 @@ ll.table.description=Description ll.table.description.error=Please fill in ll.table.html_target=New window ll.table.mandatory=* ... Mandatory field +ll.table.media=Media ll.table.target=Link target ll.table.target.error=Please insert URL ll.table.target.error.format=Invalid URL format diff --git a/src/main/java/de/bps/olat/portal/links/LinksPortlet.java b/src/main/java/de/bps/olat/portal/links/LinksPortlet.java index d2f2ac7be13db24d8f889edfd0a9c0e71bb3e915..b2cea2a0d2b8c5177effc6a47d7e4e326aa383fb 100644 --- a/src/main/java/de/bps/olat/portal/links/LinksPortlet.java +++ b/src/main/java/de/bps/olat/portal/links/LinksPortlet.java @@ -15,10 +15,13 @@ package de.bps.olat.portal.links; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; import org.dom4j.Document; import org.dom4j.Element; @@ -29,10 +32,16 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.portal.AbstractPortlet; import org.olat.core.gui.control.generic.portal.Portlet; +import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; +import org.olat.core.util.xml.XStreamHelper; + +import com.thoughtworks.xstream.XStream; public class LinksPortlet extends AbstractPortlet { @@ -54,10 +63,13 @@ public class LinksPortlet extends AbstractPortlet { private static final String ELEM_LINK_TITLE = "Title"; private static final String ELEM_LINK_URL = "URL"; private static final String ELEM_LINK_DESC = "Description"; + private static final String ELEM_LINK_IDENT = "Identifier"; private static final String ELEM_LINK_TARGET = "Target"; private static final String ELEM_LINK_LANG = "Language"; private static HashMap<String, PortletInstitution> content; + + private static File fxConfXStreamFile; private Controller runCtr; /** @@ -76,41 +88,133 @@ public class LinksPortlet extends AbstractPortlet { return p; } - private void init() { + private static void init() { OLog logger = Tracing.createLoggerFor(LinksPortlet.class); if(logger.isDebug()) logger.debug("START: Loading remote portlets content."); File configurationFile = new File(WebappHelper.getContextRoot() + CONFIG_FILE); + // fxdiff: have file outside of war/olatapp + File fxConfFolder = new File(WebappHelper.getUserDataRoot() + "/customizing/linksPortlet"); + if (!fxConfFolder.exists()) { + fxConfFolder.mkdir(); + } + File fxConfFile = new File(fxConfFolder + "/olat_portals_links.xml"); + fxConfXStreamFile = new File(fxConfFolder + "/olat_portals_xstream.xml"); + if (!fxConfFile.exists() && !fxConfXStreamFile.exists()) { + try { + fxConfFile.createNewFile(); + FileUtils.copyFileToFile(configurationFile, fxConfFile, false); + logger.info("portal links portlet: copied initial config from " + CONFIG_FILE); + } catch (IOException e) { + new AssertException("could not copy an initial portal links config to olatdata", e); + } + } // this map contains the whole data HashMap<String, PortletInstitution> portletMap = new HashMap<String, PortletInstitution>(); - SAXReader reader = new SAXReader(); - try { - Document doc = reader.read(configurationFile); - Element rootElement = doc.getRootElement(); - List<Element> lstInst = rootElement.elements(ELEM_INSTITUTION); - for( Element instElem : lstInst ) { - String inst = instElem.attributeValue(ATTR_INSTITUTION_NAME); - List<Element> lstTmpLinks = instElem.elements(ELEM_LINK); - List<PortletLink> lstLinks = new ArrayList<PortletLink>(lstTmpLinks.size()); - for( Element linkElem: lstTmpLinks ) { - String title = linkElem.elementText(ELEM_LINK_TITLE); - String url = linkElem.elementText(ELEM_LINK_URL); - String target = linkElem.elementText(ELEM_LINK_TARGET); - String lang = linkElem.elementText(ELEM_LINK_LANG); - String desc = linkElem.elementText(ELEM_LINK_DESC); - lstLinks.add(new PortletLink(title, url, target, lang, desc)); + if (!fxConfXStreamFile.exists()){ + SAXReader reader = new SAXReader(); + try { + // fxdiff: read from fx-config-file in olatdata + Document doc = reader.read(fxConfFile); + Element rootElement = doc.getRootElement(); + List<Element> lstInst = rootElement.elements(ELEM_INSTITUTION); + for( Element instElem : lstInst ) { + String inst = instElem.attributeValue(ATTR_INSTITUTION_NAME); + List<Element> lstTmpLinks = instElem.elements(ELEM_LINK); + List<PortletLink> lstLinks = new ArrayList<PortletLink>(lstTmpLinks.size()); + for( Element linkElem: lstTmpLinks ) { + String title = linkElem.elementText(ELEM_LINK_TITLE); + String url = linkElem.elementText(ELEM_LINK_URL); + String target = linkElem.elementText(ELEM_LINK_TARGET); + String lang = linkElem.elementText(ELEM_LINK_LANG); + String desc = linkElem.elementText(ELEM_LINK_DESC); + String identifier = linkElem.elementText(ELEM_LINK_IDENT); + lstLinks.add(new PortletLink(title, url, target, lang, desc, identifier)); + } + portletMap.put(inst, new PortletInstitution(inst, lstLinks)); } - portletMap.put(inst, new PortletInstitution(inst, lstLinks)); + } catch (Exception e) { + logger.error("Error reading configuration file", e); + } finally { + content = portletMap; + } + // lazy migrate to new format + saveLinkList(content); + FileUtils.copyFileToFile(fxConfFile, new File(fxConfFile + ".bak"), true); + } else { + XStream xstream = XStreamHelper.createXStreamInstance(); + xstream.alias("LinksPortlet", Map.class); + xstream.alias(ELEM_LINK, PortletLink.class); + xstream.alias(ELEM_INSTITUTION, PortletInstitution.class); + xstream.aliasAttribute(PortletInstitution.class, ATTR_INSTITUTION_NAME, ATTR_INSTITUTION_NAME); + content = (HashMap<String, PortletInstitution>) XStreamHelper.readObject(xstream, fxConfXStreamFile); + } + } + + public static boolean saveLinkList(HashMap<String, PortletInstitution> portletMap){ + XStream xstream = XStreamHelper.createXStreamInstance(); + xstream.alias("LinksPortlet", Map.class); + xstream.alias(ELEM_LINK, PortletLink.class); + xstream.alias(ELEM_INSTITUTION, PortletInstitution.class); + xstream.aliasAttribute(PortletInstitution.class, ATTR_INSTITUTION_NAME, ATTR_INSTITUTION_NAME); + String output = xstream.toXML(portletMap); + XStreamHelper.writeObject(xstream, fxConfXStreamFile, portletMap); + return (output.length() != 0); + } + + public static PortletLink getLinkByIdentifier(String identifier){ + for (Iterator<String> iterator = content.keySet().iterator(); iterator.hasNext();) { + String inst = iterator.next(); + PortletInstitution portletsForInst = content.get(inst); + List<PortletLink> instLinks = portletsForInst.getLinks(); + for (PortletLink portletLink : instLinks) { + if (portletLink.getIdentifier().equals(identifier)) return portletLink; + } + } + return null; + } + + public static void removeLink(PortletLink link){ + if (link == null) return; + for (Iterator<String> iterator = content.keySet().iterator(); iterator.hasNext();) { + String inst = iterator.next(); + PortletInstitution portletsForInst = content.get(inst); + List<PortletLink> instLinks = portletsForInst.getLinks(); + for (PortletLink portletLink : instLinks) { + if (portletLink.getIdentifier().equals(link.getIdentifier())) { + instLinks.remove(link); + break; + } + } + } + saveLinkList(content); + } + + public static void updateLink(PortletLink link){ + if (link == null) return; + for (Iterator<String> iterator = content.keySet().iterator(); iterator.hasNext();) { + String inst = iterator.next(); + PortletInstitution portletsForInst = content.get(inst); + List<PortletLink> instLinks = portletsForInst.getLinks(); + boolean existingLink = false; + for (PortletLink portletLink : instLinks) { + if (portletLink.getIdentifier().equals(link.getIdentifier())) { + portletLink = link; + existingLink = true; + break; + } + } + if (!existingLink && portletsForInst == link.getInstitution()) { + portletsForInst.addLink(link); + break; } - } catch (Exception e) { - logger.error("Error reading configuration file", e); - } finally { - content = portletMap; } + saveLinkList(content); } + /** * @see org.olat.gui.control.generic.portal.Portlet#getTitle() */ @@ -118,6 +222,11 @@ public class LinksPortlet extends AbstractPortlet { return getTranslator().translate("portlet.title"); } + public static void reInit(UserRequest ureq){ + content = null; + init(); + } + /** * @see org.olat.gui.control.generic.portal.Portlet#getDescription() */ @@ -223,13 +332,24 @@ class PortletInstitution { class PortletLink { private String title, url, target, language, description; + private String identifier; + private transient PortletInstitution institution; - public PortletLink(String title, String url, String target, String language, String description) { - this.title = title; - this.url = url; - this.target = target; - this.language = language; - this.description = description; + public PortletLink(String title, String url, String target, String language, String description, String identifier) { + setTitle(title); + setUrl(url); + setTarget(target); + setLanguage(language); + setDescription(description); + setIdentifier(identifier); + } + + public PortletInstitution getInstitution() { + return institution; + } + + public void setInstitution(PortletInstitution institution) { + this.institution = institution; } public String getTitle() { @@ -271,4 +391,19 @@ class PortletLink { public void setDescription(String description) { this.description = description; } + + public void setIdentifier(String identifier){ + if (identifier == null) { + this.identifier = UUID.randomUUID().toString().replace("-", ""); + } else { + this.identifier = identifier; + } + } + + public String getIdentifier(){ + if (!StringHelper.containsNonWhitespace(identifier)){ + setIdentifier(null); + } + return identifier; + } } diff --git a/src/main/java/de/bps/olat/portal/links/LinksPortletEditController.java b/src/main/java/de/bps/olat/portal/links/LinksPortletEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..7de19d942f894c52e4394bf6d2b6feebab045024 --- /dev/null +++ b/src/main/java/de/bps/olat/portal/links/LinksPortletEditController.java @@ -0,0 +1,191 @@ +/** +* 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. +* <p> +*/ +package de.bps.olat.portal.links; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.ValidationError; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.RichTextElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.ItemValidatorProvider; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.i18n.I18nManager; + +/** + * Description:<br> + * TODO: rhaag Class Description for LinksPortletEditController + * + * <P> + * Initial Date: 08.06.2011 <br> + * @author rhaag + */ +public class LinksPortletEditController extends FormBasicController { + + private static final String TARGET_BLANK = "blank"; + private static final String TARGET_SELF = "self"; + private PortletLink portletLink; + private MultipleSelectionElement openPopup; + private SingleSelection language; + private SingleSelection institution; + private RichTextElement desc; + private TextElement title; + private TextElement linkURL; + + public LinksPortletEditController(UserRequest ureq, WindowControl wControl, PortletLink portletLink) { + super(ureq, wControl); + this.portletLink = portletLink; + + 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) { + + title = uifactory.addTextElement("link.title", "link.title", 200, portletLink.getTitle(), formLayout); + title.setMandatory(true); + title.setNotEmptyCheck("link.title.not.empty"); + desc = uifactory.addRichTextElementForStringDataMinimalistic("link.desc", "link.desc", portletLink.getDescription(), 5, -1, false, formLayout, ureq.getUserSession(), getWindowControl()); + desc.setExtDelay(true); + linkURL = uifactory.addTextElement("link.url", "link.url", 1024, portletLink.getUrl(), formLayout); + linkURL.setMandatory(true); + linkURL.setNotEmptyCheck("link.url.not.empty"); + linkURL.setItemValidatorProvider(new ItemValidatorProvider() { + @Override + public boolean isValidValue(String value, final ValidationError validationError, @SuppressWarnings("unused") final Locale locale) { + try { + if (!value.contains("://")) { + value = "http://".concat(value); + } + new URL(value); + } catch (final MalformedURLException e) { + validationError.setErrorKey("link.url.not.empty"); + return false; + } + return true; + } + }); + openPopup = uifactory.addCheckboxesHorizontal("link.open.new.window", "link.open.new.window", formLayout, new String[]{TARGET_BLANK}, new String[]{""}, new String[]{""}); + if (portletLink.getTarget().equals(TARGET_BLANK)) { + openPopup.selectAll(); + } + + // language + Map<String, String> locdescs = I18nManager.getInstance().getEnabledLanguagesTranslated(); + Set<String> lkeys = locdescs.keySet(); + String[] languageKeys = new String[lkeys.size()+1]; + String[] languageValues = new String[lkeys.size()+1]; + languageKeys[0] = "*"; + languageValues[0] = translate("link.lang.all"); + int p = 1; + for (Iterator<String> iter = lkeys.iterator(); iter.hasNext();) { + String key = iter.next(); + languageKeys[p] = key; + languageValues[p] = locdescs.get(key); + p++; + } + language = uifactory.addDropdownSingleselect("link.language", formLayout, languageKeys, languageValues, null); + String langKey = portletLink.getLanguage(); + if(Arrays.asList(languageKeys).contains(langKey)){ + language.select(langKey, true); + } + + uifactory.addFormSubmitButton("save", formLayout); + + } + + + + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + // TODO Auto-generated method stub + super.formInnerEvent(ureq, source, event); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + return super.validateFormLogic(ureq); +// URL target = null; +// try { +// target = new URL(linkURL.getValue()); +// } catch (final MalformedURLException e) { +// // +// } +// if (target != null) { +// return true; +// } else { +// return false; +// } + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) + */ + @Override + protected void formOK(UserRequest ureq) { + + // persist changes + portletLink.setTitle(title.getValue()); + portletLink.setDescription(desc.getValue()); + String urlToSet = linkURL.getValue(); + if (!urlToSet.contains("://")) { + urlToSet = "http://".concat(urlToSet); + } + portletLink.setUrl(urlToSet); + if (openPopup.isSelected(0)) { + portletLink.setTarget(TARGET_BLANK); + } else { + portletLink.setTarget(TARGET_SELF); + } + portletLink.setLanguage(language.getSelectedKey()); + + LinksPortlet.updateLink(portletLink); + fireEvent(ureq, Event.DONE_EVENT); + } + + /** + * @see org.olat.core.gui.control.DefaultController#doDispose() + */ + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/de/bps/olat/portal/links/LinksPortletRunController.java b/src/main/java/de/bps/olat/portal/links/LinksPortletRunController.java index 1bdb17d8e42c5f6da261ed811ec4020c77524830..90be22946ffa46a1b09ac27aef816aae5495187c 100644 --- a/src/main/java/de/bps/olat/portal/links/LinksPortletRunController.java +++ b/src/main/java/de/bps/olat/portal/links/LinksPortletRunController.java @@ -14,30 +14,69 @@ */ package de.bps.olat.portal.links; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.UserConstants; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; public class LinksPortletRunController extends BasicController { + private static final String LINKADD = "linkadd"; + private static final String LINKID = "linkid"; + private static final String LINKDEL = "linkdel"; private VelocityContainer portletVC; + private Link editButton; + private Panel viewPanel; + private LinksPortletEditController editorCtrl; + private CloseableCalloutWindowController linkEditorCalloutCtr; + private Link backLink; + private DialogBoxController delLinkCtrl; protected LinksPortletRunController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); portletVC = this.createVelocityContainer("portlet"); - String lang = I18nManager.getInstance().getLocaleKey(ureq.getLocale()); - if (lang == null) lang = I18nManager.getInstance().getLocaleKey(I18nModule.getDefaultLocale()); + initOrUpdatePortletView(ureq); + + //edit link + if (ureq.getUserSession().getRoles().isOLATAdmin()){ + editButton = LinkFactory.createButtonXSmall("editor.button", portletVC, this); + } - boolean isGuest = !ureq.getUserSession().isAuthenticated(); + viewPanel = new Panel("view"); + viewPanel.setContent(portletVC); + putInitialPanel(viewPanel); + } + + private void initOrUpdatePortletView(UserRequest ureq){ + String lang = I18nManager.getInstance().getLocaleKey(ureq.getLocale()); + if (lang == null) { + lang = I18nManager.getInstance().getLocaleKey(I18nModule.getDefaultLocale()); + } + // fxdiff: compare with language-base not with variant... + int underlinePos = lang.indexOf("_"); + if (underlinePos != -1){ + lang = lang.substring(0,underlinePos); + } + + boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); String inst = new String(); if(!isGuest) inst = ureq.getIdentity().getUser().getProperty(UserConstants.INSTITUTIONALNAME, getLocale()); @@ -63,14 +102,12 @@ public class LinksPortletRunController extends BasicController { } } - // In Template einfügen if (sb.length() > 0) { String portletContent = "<ul>" + sb.toString() + "</ul>"; portletVC.contextPut("content", portletContent); } else { portletVC.contextPut("content", translate("no.content.found")); } - putInitialPanel(this.portletVC); } /** @@ -82,8 +119,15 @@ public class LinksPortletRunController extends BasicController { */ private void appendContentFor(Map<String, PortletInstitution> content, String inst, String lang, StringBuffer sb) { + String linkLang = ""; + int underlinePos = -1; for( PortletLink link : content.get(inst).getLinks() ) { - if(link.getLanguage().equals(lang) | link.getLanguage().equals(LinksPortlet.LANG_ALL)) + linkLang = link.getLanguage(); + underlinePos = linkLang.indexOf("_"); + if (underlinePos != -1){ + linkLang= linkLang.substring(0,underlinePos); + } + if(linkLang.equals(lang) | linkLang.equals(LinksPortlet.LANG_ALL)) appendContent(link, sb); } } @@ -116,7 +160,7 @@ public class LinksPortletRunController extends BasicController { sb.append(target); sb.append("\">"); sb.append(title); - sb.append("</a> - "); + sb.append("</a>"); sb.append(descr); return sb.toString(); } @@ -125,7 +169,107 @@ public class LinksPortletRunController extends BasicController { * @see org.olat.gui.control.DefaultController#event(org.olat.gui.UserRequest, org.olat.gui.components.Component, org.olat.gui.control.Event) */ public void event(UserRequest ureq, Component source, Event event) { - // no events to catch + if (source == editButton){ + buildEditorPanel(); + } else if (source == backLink) { + LinksPortlet.reInit(ureq); + initOrUpdatePortletView(ureq); + viewPanel.setContent(portletVC); + } else if (source instanceof Link) { + // clicked on a link in editor-mode -> open editor in callout + Link link = (Link) source; + String linkName = link.getComponentName(); + if (linkName.contains(LINKID)){ + String identifier = linkName.substring(LINKID.length()); + PortletLink portLink = LinksPortlet.getLinkByIdentifier(identifier); + if (portLink != null) { + popupLinkEditor(ureq, portLink, link); + } else { + showError("error.link.not.found"); + } + } else if (linkName.contains(LINKADD)){ + // add a link to institution: + PortletLink newLink = new PortletLink("", "", "", I18nManager.getInstance().getLocaleKey(ureq.getLocale()), "", null); + // find institution and port in link! + String institution = link.getCommand().substring(LINKADD.length()); + PortletInstitution inst = LinksPortlet.getContent().get(institution); + newLink.setInstitution(inst); + popupLinkEditor(ureq, newLink, link); + } else if (linkName.contains(LINKDEL)){ + String identifier = linkName.substring(LINKDEL.length()); + PortletLink portLink = LinksPortlet.getLinkByIdentifier(identifier); + delLinkCtrl = activateYesNoDialog(ureq, translate("del.link.title"), translate("del.link.text", portLink.getTitle()), delLinkCtrl); + delLinkCtrl.setUserObject(portLink); + } + } + } + + private void popupLinkEditor(UserRequest ureq, PortletLink portLink, Link glueLink) { + String title = translate("link.editor.title"); + removeAsListenerAndDispose(editorCtrl); + editorCtrl = new LinksPortletEditController(ureq, getWindowControl(), portLink); + listenTo(editorCtrl); + + removeAsListenerAndDispose(linkEditorCalloutCtr); + linkEditorCalloutCtr = new CloseableCalloutWindowController(ureq, getWindowControl(), editorCtrl.getInitialComponent(), glueLink, title, true, null); + listenTo(linkEditorCalloutCtr); + linkEditorCalloutCtr.activate(); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == editorCtrl && event == Event.DONE_EVENT) { + LinksPortlet.reInit(ureq); + linkEditorCalloutCtr.deactivate(); + buildEditorPanel(); + } else if (source == delLinkCtrl && DialogBoxUIFactory.isYesEvent(event) ){ + LinksPortlet.removeLink( (PortletLink) delLinkCtrl.getUserObject() ); + showInfo("del.link.success"); + buildEditorPanel(); + } + } + + private void buildEditorPanel(){ + VelocityContainer editorVC = this.createVelocityContainer("editorLinkOverview"); + Map<String, PortletInstitution> content = LinksPortlet.getContent(); + if (content != null && !content.isEmpty() ) { + ArrayList<String> allInst = new ArrayList<String>(); + ArrayList<String> allInstTranslated = new ArrayList<String>(); + HashMap<Integer, ArrayList<String>> allInstWithLinkIds = new HashMap<Integer, ArrayList<String>>(); + int instCount = 1; + for (Iterator<String> iterator = content.keySet().iterator(); iterator.hasNext();) { + String inst = iterator.next(); + allInst.add(inst); + String instTranslated = inst; + if (inst.equals(LinksPortlet.ACCESS_ALL)) instTranslated = translate("access.all"); + if (inst.equals(LinksPortlet.ACCESS_REG)) instTranslated = translate("access.registered.users"); + if (inst.equals(LinksPortlet.ACCESS_GUEST)) instTranslated = translate("access.guests"); + allInstTranslated.add(instTranslated); + + PortletInstitution portletsForInst = content.get(inst); + // collect identifiers to find them in VC. + ArrayList<String> instLinksIdentifiers = new ArrayList<String>(); + + // add add-link per institution + LinkFactory.createCustomLink(LINKADD + inst, LINKADD + inst, "add.link", Link.BUTTON_XSMALL, editorVC, this); + + for (PortletLink link : portletsForInst.getLinks()) { + String linkID = link.getIdentifier(); + LinkFactory.createCustomLink(LINKID + linkID, "inst" + inst, link.getTitle(), Link.LINK + Link.NONTRANSLATED, editorVC, this); + // add remove-links + LinkFactory.createCustomLink(LINKDEL + linkID, "inst" + inst, "-", Link.BUTTON_XSMALL + Link.NONTRANSLATED, editorVC, this); + instLinksIdentifiers.add(linkID); + } + allInstWithLinkIds.put(instCount, instLinksIdentifiers); + instCount++; + } + editorVC.contextPut("allInst", allInst); + editorVC.contextPut("allInstTranslated", allInstTranslated); + editorVC.contextPut("allInstWithLinkIds", allInstWithLinkIds); + } + + backLink = LinkFactory.createLinkBack(editorVC, this); + viewPanel.setContent(editorVC); } /** diff --git a/src/main/java/de/bps/olat/portal/links/_content/editorLinkOverview.html b/src/main/java/de/bps/olat/portal/links/_content/editorLinkOverview.html new file mode 100644 index 0000000000000000000000000000000000000000..1942f9f6baf69b7f56a5d54c4f5a76d9c7be5c0a --- /dev/null +++ b/src/main/java/de/bps/olat/portal/links/_content/editorLinkOverview.html @@ -0,0 +1,19 @@ +$r.render("backLink") <br/> +<p>$r.translate("link.editor.overview.intro")</p> + +#if ($!allInst) + #foreach( $instTrans in $allInstTranslated ) + #set ($id = ($velocityCount - 1) ) + #set ( $inst = $allInst.get($id) ) + <br/> + <b>$r.translate("link.institution"): <i>$instTrans</i> </b> + <div class="b_float_right"> $r.render("linkadd$inst") </div><br/> + <ul> + #foreach( $linkID in $allInstWithLinkIds.get($velocityCount) ) + #set($linkEditorLink = "linkid$linkID") + #set($linkDelLink = "linkdel$linkID") + <li>$r.render("$linkEditorLink") $r.render("$linkDelLink")</li> + #end + </ul> + #end +#end \ No newline at end of file diff --git a/src/main/java/de/bps/olat/portal/links/_content/portlet.html b/src/main/java/de/bps/olat/portal/links/_content/portlet.html index 4de8956efab4c76f963467717cdcc32e678338ca..d91a425e0030dcab4bac6df307a14616489b3cbc 100644 --- a/src/main/java/de/bps/olat/portal/links/_content/portlet.html +++ b/src/main/java/de/bps/olat/portal/links/_content/portlet.html @@ -1,3 +1,8 @@ +#if($r.available("editor.button")) + <div class="b_float_right"> + $r.render("editor.button") + </div> +#end <div class="o_links_portlet"> - $content -</div> \ No newline at end of file + $content +</div> diff --git a/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_de.properties index c49886a4b3e7647528d78c1168828d3f43d71c24..027d013e224c4b39fcebfbf86c81c419167159fd 100644 --- a/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_de.properties +++ b/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_de.properties @@ -2,3 +2,24 @@ no.content.found=Keine Links gefunden. portlet.description=Interessante Links au\u00DFerhalb von OLAT. portlet.title=Interessante Links +link.language=Sprache +link.url=Adresse +link.open.new.window=In neuem Fenster öffnen +link.title=Titel +link.title.not.empty=Bitte einen Titel für den Link angeben. +link.desc=Beschreibung +link.institution=Sichtbar für +link.viewable.for=Sichtbarkeitsbeschränkungen +editor.button=Bearbeiten +error.link.not.found=Der Link konnte nicht geladen werden! Bitte xml-Dateien im olatdata prüfen. +link.lang.all=Alle Sprachen +link.editor.title=Link bearbeiten +link.editor.overview.intro=Klicken Sie auf einen Link, um diesen zu bearbeiten. In dieser Übersicht werden alle Links angezeigt, Benutzer sehen die Links aber nur entsprechend den Zugangsbeschränkungen und ihrer Sprache. +access.all=Alle OLAT Benutzer inkl. Gäste +access.registered.users=Alle registrierten OLAT Benutzer +access.guests=Nur Gäste +add.link=+ Hinzufügen +del.link.title=Link löschen +del.link.text=Möchten Sie den Link "{0}" wirklich löschen? +del.link.success=Der Link wurde gelöscht. +link.url.not.empty=Bitte eine gültige URL angeben. diff --git a/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_en.properties index c1f4dc302100d5b4cd441ea2a90eb9b8321e2e86..630dabea7a5433ddde2fb5dc6106e020f37c1ecf 100644 --- a/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_en.properties +++ b/src/main/java/de/bps/olat/portal/links/_i18n/LocalStrings_en.properties @@ -2,3 +2,24 @@ no.content.found=No links found. portlet.description=Interesting links outside of OLAT. portlet.title=Interesting links +link.language=Language +link.url=Link target +link.open.new.window=Open in new window +link.title=Title +link.title.not.empty=Please provide a title for this link. +link.desc=Description +link.institution=Visible for +link.viewable.for=Visibility restrictions +editor.button=Edit +error.link.not.found=Could not find this link in config! Please check the xml-files in olatdata manually. +link.lang.all=All languages +link.editor.title=Edit a link +link.editor.overview.intro=Click on a link to edit details. Here you can see all links, an OLAT user would only see the links matching the visibility restriction and his language. +access.all=All OLAT-users as also guests +access.registered.users=All registered OLAT-users +access.guests=Guests only +add.link=+ Add link +del.link.title=Delete a link +del.link.text=Do you really want to delete the link "{0}" ? +del.link.success=The link has been deleted. +link.url.not.empty=Please provide a valid URL for this link. diff --git a/src/main/java/de/bps/olat/user/ChangeEMailExecuteController.java b/src/main/java/de/bps/olat/user/ChangeEMailExecuteController.java index b6bf2f553831105c84b5b6258e0ce8978ae984ff..1fc438795edf78e201632976eee9179f76ce5ae4 100644 --- a/src/main/java/de/bps/olat/user/ChangeEMailExecuteController.java +++ b/src/main/java/de/bps/olat/user/ChangeEMailExecuteController.java @@ -19,6 +19,12 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; +import org.olat.core.id.User; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.home.InviteeHomeMainController; +import org.olat.login.SupportsAfterLoginInterceptor; +import org.olat.user.ProfileAndHomePageEditController; import org.olat.user.UserManager; import com.thoughtworks.xstream.XStream; @@ -32,7 +38,12 @@ import com.thoughtworks.xstream.XStream; * Initial Date: 19.05.2009 <br> * @author bja */ -public class ChangeEMailExecuteController extends ChangeEMailController { +public class ChangeEMailExecuteController extends ChangeEMailController implements SupportsAfterLoginInterceptor { + + private static final String PRESENTED_EMAIL_CHANGE_REMINDER = "presentedemailchangereminder"; + + + protected static final String PACKAGE_HOME = ProfileAndHomePageEditController.class.getPackage().getName(); public ChangeEMailExecuteController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); @@ -50,6 +61,35 @@ public class ChangeEMailExecuteController extends ChangeEMailController { } } + @Override + //fxdiff BAKS-20 instantiate controllers + public boolean isInterceptionRequired(UserRequest ureq) { + User user = ureq.getIdentity().getUser(); + if(StringHelper.containsNonWhitespace(user.getProperty("emchangeKey", null))) { + if (isLinkTimeUp()) { + deleteRegistrationKey(); + return false; + } else { + if (isLinkClicked()) { + changeEMail(getWindowControl()); + } else { + Boolean alreadySeen = ((Boolean)ureq.getUserSession().getEntry(PRESENTED_EMAIL_CHANGE_REMINDER)); + if (alreadySeen == null) { + getWindowControl().setWarning(getPackageTranslator().translate("email.change.reminder")); + ureq.getUserSession().putEntry(PRESENTED_EMAIL_CHANGE_REMINDER, Boolean.TRUE); + } + } + } + } else { + String value = user.getProperty("emailDisabled", null); + if (value != null && value.equals("true")) { + Translator translator = Util.createPackageTranslator(InviteeHomeMainController.class, ureq.getLocale()); + getWindowControl().setWarning(translator.translate("email.disabled")); + } + } + return false; + } + /** * change email * @param wControl diff --git a/src/main/java/de/bps/security/SSLConfigurationModule.java b/src/main/java/de/bps/security/SSLConfigurationModule.java index fdc4dbc38275808c59ac70c8cb4a1b2dcb73f31a..0a808d9a87682acdb77f802ceb908e01d95b7d35 100644 --- a/src/main/java/de/bps/security/SSLConfigurationModule.java +++ b/src/main/java/de/bps/security/SSLConfigurationModule.java @@ -1,4 +1,3 @@ - /** * * BPS Bildungsportal Sachsen GmbH<br> @@ -21,7 +20,6 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import org.olat.core.configuration.Initializable; import org.olat.core.logging.Tracing; public class SSLConfigurationModule { diff --git a/src/main/java/org/olat/admin/AdminModuleDispatcher.java b/src/main/java/org/olat/admin/AdminModuleDispatcher.java index dda1056da1fd1128eee547b4ef4e04b297db8c40..8c57cd8ad6da50d5bce418a3d234186a2bdbb662 100644 --- a/src/main/java/org/olat/admin/AdminModuleDispatcher.java +++ b/src/main/java/org/olat/admin/AdminModuleDispatcher.java @@ -24,6 +24,8 @@ package org.olat.admin; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.olat.admin.sysinfo.InfoMessageManager; +import org.olat.core.CoreSpringFactory; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.dispatcher.DispatcherAction; import org.olat.core.gui.media.ServletUtil; @@ -46,7 +48,8 @@ public class AdminModuleDispatcher implements Dispatcher { private final static String PARAMETER_NBR_SESSIONS = "nbrsessions"; private final static String PARAMETER_SESSIONTIMEOUT ="sec"; - private final static String CMD_SET_MAINTENANCE_MESSAGE = "setmaintenancemessage"; + private final static String CMD_SET_MAINTENANCE_MESSAGE = "setmaintenancemessage"; + private final static String CMD_SET_INFO_MESSAGE = "setinfomessage"; private final static String CMD_SET_LOGIN_BLOCKED = "setloginblocked"; private final static String CMD_SET_LOGIN_NOT_BLOCKED = "setloginnotblocked"; private final static String CMD_SET_MAX_SESSIONS = "setmaxsessions"; @@ -58,10 +61,10 @@ public class AdminModuleDispatcher implements Dispatcher { /** * @see org.olat.core.dispatcher.Dispatcher#execute(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.String) */ - public void execute(HttpServletRequest request, HttpServletResponse response, String uriPrefix) { + public void execute(HttpServletRequest request, HttpServletResponse response, @SuppressWarnings("unused") String uriPrefix) { String cmd = request.getParameter(PARAMETER_CMD); - if (cmd.equalsIgnoreCase(CMD_SET_MAINTENANCE_MESSAGE)) { - handleSetMaintenanceMessage(request, response); + if (cmd.equalsIgnoreCase(CMD_SET_MAINTENANCE_MESSAGE) || cmd.equalsIgnoreCase(CMD_SET_INFO_MESSAGE)) { + handleSetMaintenanceOrInfoMessage(request, response, cmd); } else { if (AdminModule.checkSessionAdminToken(request, response)) { handleSessionsCommand(request, response, cmd); @@ -157,11 +160,17 @@ public class AdminModuleDispatcher implements Dispatcher { * @param request * @param response */ - private void handleSetMaintenanceMessage(HttpServletRequest request, HttpServletResponse response) { + private void handleSetMaintenanceOrInfoMessage(HttpServletRequest request, HttpServletResponse response, String cmd) { if (AdminModule.checkMaintenanceMessageToken(request, response)) { String message = request.getParameter(PARAMETER_MSG); - AdminModule.setMaintenanceMessage(message); - ServletUtil.serveStringResource(request, response, "Ok, new maintenanceMessage is::" + message); + if (cmd.equalsIgnoreCase(CMD_SET_INFO_MESSAGE)){ + InfoMessageManager mrg = (InfoMessageManager) CoreSpringFactory.getBean(InfoMessageManager.class); + mrg.setInfoMessage(message); + ServletUtil.serveStringResource(request, response, "Ok, new infoMessage is::" + message); + } else if (cmd.equalsIgnoreCase(CMD_SET_MAINTENANCE_MESSAGE)){ + AdminModule.setMaintenanceMessage(message); + ServletUtil.serveStringResource(request, response, "Ok, new maintenanceMessage is::" + message); + } } else { DispatcherAction.sendForbidden(request.getPathInfo(), response); } diff --git a/src/main/java/org/olat/admin/UserAdminMainController.java b/src/main/java/org/olat/admin/UserAdminMainController.java index 6f4d04cf8e9312381f8be860969775986cdcf479..52c20227c3ba83019a857e16cfbf109c4e1d04ad 100644 --- a/src/main/java/org/olat/admin/UserAdminMainController.java +++ b/src/main/java/org/olat/admin/UserAdminMainController.java @@ -108,7 +108,8 @@ public class UserAdminMainController extends MainLayoutBasicController implement */ public UserAdminMainController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); - olatMenuTree = new MenuTree("olatMenuTree"); + olatMenuTree = new MenuTree("olatMenuTree"); + olatMenuTree.setExpandSelectedNode(false); TreeModel tm = buildTreeModel(ureq); olatMenuTree.setTreeModel(tm); TreeNode firstNode = (TreeNode)tm.getRootNode().getChildAt(0); diff --git a/src/main/java/org/olat/admin/instantMessaging/InstantMessagingAdminController.java b/src/main/java/org/olat/admin/instantMessaging/InstantMessagingAdminController.java index 7019ed20b5f21332bc0435b2e009eef0538d7597..5773c5dd31c017e7e31f0e8fc40b5c401da0487f 100644 --- a/src/main/java/org/olat/admin/instantMessaging/InstantMessagingAdminController.java +++ b/src/main/java/org/olat/admin/instantMessaging/InstantMessagingAdminController.java @@ -31,8 +31,11 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.components.form.flexible.impl.elements.FormLinkImpl; import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.instantMessaging.AdminUserConnection; +import org.olat.instantMessaging.InstantMessaging; import org.olat.instantMessaging.InstantMessagingModule; /** @@ -52,6 +55,7 @@ public class InstantMessagingAdminController extends FormBasicController { private FormSubmit submit; private FormLinkImpl checkPlugin; private FormLinkImpl reconnectAdminUser; + private FormLink doUserSyncButton; public InstantMessagingAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl, "index"); @@ -61,18 +65,33 @@ public class InstantMessagingAdminController extends FormBasicController { @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + InstantMessaging im = InstantMessagingModule.getAdapter(); if (source == doSyncButton) { - boolean hasError = InstantMessagingModule.getAdapter().synchronizeAllBuddyGroupsWithIMServer(); - InstantMessagingModule.getAdapter().synchronizeLearningGroupsWithIMServer(); - if (!hasError) { - showInfo("imadmin.sync.failed"); + showInfo("imadmin.sync.cmd.dosync.caption"); + boolean allOk = im.synchronizeAllBuddyGroupsWithIMServer(); + allOk &= im.synchronizeLearningGroupsWithIMServer(); + if (!allOk) { + refreshAndSetConnectionStatus(); + showError("imadmin.sync.failed"); + doSyncButton.setEnabled(false); } } else if (source == checkPlugin) { - String ok= InstantMessagingModule.getAdapter().checkServerPlugin(); + String ok= im.checkServerPlugin(); showInfo("imadmin.plugin.version", ok); } else if (source == reconnectAdminUser) { - InstantMessagingModule.getAdapter().resetAdminConnection(); + try { + im.resetAdminConnection(); + } catch (Exception e) { + refreshAndSetConnectionStatus(); + getWindowControl().setError("Connection not possible: " + e.getMessage()); + return; + } + refreshAndSetConnectionStatus(); + doSyncButton.setEnabled(true); showInfo("imadmin.plugin.admin.connection.done"); + } else if (source == doUserSyncButton) { + String result = im.synchronizeAllOLATUsers(); + getWindowControl().setWarning(result); } } @@ -91,13 +110,20 @@ public class InstantMessagingAdminController extends FormBasicController { InstantMessagingModule.setIDLE_POLLTIME(idlePollTime.getIntValue()); } + private void refreshAndSetConnectionStatus(){ + AdminUserConnection connection = InstantMessagingModule.getAdapter().getAdminUserConnection(); + boolean connectionSuccessfull = (connection!=null && connection.getConnection()!= null && connection.getConnection().isConnected()); + flc.contextPut("IMConnectionStatus", connectionSuccessfull); + } + + @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { FormLayoutContainer mainLayout = FormLayoutContainer.createDefaultFormLayout("mainLayout", getTranslator()); formLayout.add(mainLayout); String imServerName = InstantMessagingModule.getAdapter().getConfig().getServername(); - String imAdminUsername = InstantMessagingModule.getAdapter().getConfig().getAdminUsername(); + String imAdminUsername = InstantMessagingModule.getAdapter().getConfig().getAdminName(); String imAdminPw = InstantMessagingModule.getAdapter().getConfig().getAdminPassword(); @@ -105,6 +131,7 @@ public class InstantMessagingAdminController extends FormBasicController { flc.contextPut("IMServerAdminUsername", imAdminUsername); flc.contextPut("IMServerAdminPw", imAdminPw); + refreshAndSetConnectionStatus(); checkPlugin = new FormLinkImpl("imadmin.plugin.check"); checkPlugin.setCustomEnabledLinkCSS("b_button"); @@ -113,14 +140,14 @@ public class InstantMessagingAdminController extends FormBasicController { reconnectAdminUser = new FormLinkImpl("imadmin.plugin.admin.reconnect"); reconnectAdminUser.setCustomEnabledLinkCSS("b_button"); formLayout.add(reconnectAdminUser); - - - //doSyncButton = LinkFactory.createButton("imadmin.sync.cmd.dosync", imAdminVC, this); + doSyncButton = new FormLinkImpl("imadmin.sync.cmd.dosync"); doSyncButton.setCustomEnabledLinkCSS("b_button"); doSyncButton.setCustomDisabledLinkCSS("b_button b_button_disabled"); formLayout.add(doSyncButton); + doUserSyncButton = uifactory.addFormLink("sync.all.users", formLayout, Link.BUTTON); + idlePollTime = uifactory.addIntegerElement("idlepolltime", "imadming.idlepolltime", InstantMessagingModule.getIDLE_POLLTIME(), mainLayout); idlePollTime.setExampleKey("imadming.idlepolltime.default", new String[]{""+InstantMessagingModule.getAdapter().getConfig().getIdlePolltime()}); idlePollTime.showExample(true); diff --git a/src/main/java/org/olat/admin/instantMessaging/_content/index.html b/src/main/java/org/olat/admin/instantMessaging/_content/index.html index bb730c75a0e79e17b826720cda3c036fa68fecbc..a49e18d64a8e3381819ccbbc42c5d66945c81d7c 100644 --- a/src/main/java/org/olat/admin/instantMessaging/_content/index.html +++ b/src/main/java/org/olat/admin/instantMessaging/_content/index.html @@ -4,11 +4,20 @@ <p> $r.translate("imadmin.plugin.intro") </p> + <p>Status: + #if($IMConnectionStatus) + <font color="green">ALL OK!</font> + #else + <font color="red">No Connection to Server!!</font> + #end + </p> <ul> - #set ($info= ["$IMServerAdminGUI", "$IMServerAdminUsername", "$IMServerAdminPw"]) + ##fxdiff: hide admin-im-pw + #set ($info= ["$IMServerAdminGUI", "$IMServerAdminUsername", "onlyServerAdminKnowsThis"]) <li>$r.translate("imadmin.plugin.webconsole", $info)</li> <li>$r.render("imadmin.plugin.check") $r.translate("imadmin.plugin.check.info")</li> <li>$r.render("imadmin.plugin.admin.reconnect") $r.translate("imadmin.plugin.admin.connection")</li> + <li>$r.render("sync.all.users") $r.translate("imadmin.plugin.sync.allusers")</li> </ul> </fieldset> diff --git a/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_de.properties index 80fb982eb286b29ba221ab0ed4d991987a3a3b1a..e421308cac54f65dc15d6ec414552de0fae416f6 100644 --- a/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_de.properties @@ -19,3 +19,5 @@ imadming.chatpolltime=Chat Poll Zeit in [ms] imadming.chatpolltime.default=default {0} millisekunden imadming.idlepolltime=Idle Poll Zeit in [ms] imadming.idlepolltime.default=default {0} millisekunden +sync.all.users=Alle Benutzer synchronisieren +imadmin.plugin.sync.allusers=Es werden alle OLAT Benutzer mit dem IM-Server synchronisiert. Achtung: Invalide Benutzer werden gelöscht und mit einem neuen Passwort wieder angelegt. \ No newline at end of file diff --git a/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_en.properties index 6b1669db47a6c4d26a06fd61ad587fcdb78657a8..06b1b8fce8c30821feeebade79f64d63ea3ef1a8 100644 --- a/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/instantMessaging/_i18n/LocalStrings_en.properties @@ -19,3 +19,5 @@ imadming.chatpolltime=Chat poll time in [msec] imadming.chatpolltime.default=Default {0} milliseconds imadming.idlepolltime=Idle poll time in [msec] imadming.idlepolltime.default=Default {0} milliseconds +sync.all.users=Sync all OLAT users +imadmin.plugin.sync.allusers=All OLAT users will be synchronized with the IM-server. Attention: Invalid users will be deleted and recreated again with a new password! \ No newline at end of file diff --git a/src/main/java/org/olat/admin/layout/LayoutAdminController.java b/src/main/java/org/olat/admin/layout/LayoutAdminController.java index 706d0d8ef7d174a02d7976ff8a83f07b65573922..e144972d6d5bfef6cefd3c128df246ab9bf28381 100644 --- a/src/main/java/org/olat/admin/layout/LayoutAdminController.java +++ b/src/main/java/org/olat/admin/layout/LayoutAdminController.java @@ -69,7 +69,14 @@ public class LayoutAdminController extends FormBasicController { String[] keys = getThemes(); String enabledTheme = Settings.getGuiThemeIdentifyer(); themeSelection = uifactory.addDropdownSingleselect("themeSelection", "form.theme", themeAdminFormContainer, keys, keys, null); - themeSelection.select(enabledTheme, true); + // fxdiff + // select current theme if available but don't break on unavailable theme + for (String theme : keys) { + if (theme.equals(enabledTheme)) { + themeSelection.select(enabledTheme, true); + break; + } + } themeSelection.addActionListener(listener, FormEvent.ONCHANGE); } diff --git a/src/main/java/org/olat/admin/search/SearchAdminController.java b/src/main/java/org/olat/admin/search/SearchAdminController.java index 8aae012f1a830bbf9340ec275dde22ec431faa9f..3409a208e981420654afdca6c0380aeb4db68ebf 100644 --- a/src/main/java/org/olat/admin/search/SearchAdminController.java +++ b/src/main/java/org/olat/admin/search/SearchAdminController.java @@ -21,6 +21,9 @@ package org.olat.admin.search; +import org.apache.log4j.Level; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.search.SearchModule; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -31,8 +34,9 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.logging.LogRealTimeViewerController; import org.olat.search.service.SearchServiceFactory; -import org.olat.search.service.SearchServiceImpl; +import org.olat.search.service.indexer.IndexCronGenerator; /** * Description:<br> @@ -61,6 +65,13 @@ public class SearchAdminController extends BasicController { startIndexingButton = LinkFactory.createButtonSmall("button.startindexing", myContent, this); stopIndexingButton = LinkFactory.createButtonSmall("button.stopindexing", myContent, this); myContent.contextPut("searchstatus", SearchServiceFactory.getService().getStatus()); + + IndexCronGenerator generator = (IndexCronGenerator)CoreSpringFactory.getBean("&searchIndexCronGenerator"); + if(generator.isCronEnabled()) { + myContent.contextPut("cronExpression", generator.getCron()); + } else { + myContent.contextPut("cronExpression", Boolean.FALSE); + } searchAdminForm = new SearchAdminForm(ureq, wControl); listenTo(searchAdminForm); @@ -68,9 +79,20 @@ public class SearchAdminController extends BasicController { searchAdminForm.setIndexInterval( SearchServiceFactory.getService().getIndexInterval() ); + + SearchModule searchModule = (SearchModule)CoreSpringFactory.getBean("searchModule"); + searchAdminForm.setFileBlackList(searchModule.getCustomFileBlackList()); + searchAdminForm.setExcelFileEnabled(searchModule.getExcelFileEnabled()); + searchAdminForm.setPptFileEnabled(searchModule.getPptFileEnabled()); + searchAdminForm.setPdfFileEnabled(searchModule.getPdfFileEnabled()); myContent.put("searchAdminForm", searchAdminForm.getInitialComponent()); main.setContent(myContent); + + LogRealTimeViewerController logViewController = new LogRealTimeViewerController(ureq, wControl, "org.olat.search", Level.DEBUG, true); + listenTo(logViewController); + myContent.put("logViewController", logViewController.getInitialComponent()); + putInitialPanel(main); } @@ -96,6 +118,12 @@ public class SearchAdminController extends BasicController { if (source == searchAdminForm) { if (event == Event.DONE_EVENT) { SearchServiceFactory.getService().setIndexInterval(searchAdminForm.getIndexInterval()); + + SearchModule searchModule = (SearchModule)CoreSpringFactory.getBean("searchModule"); + searchModule.setCustomFileBlackList(searchAdminForm.getFileBlackList()); + searchModule.setExcelFileEnabled(searchAdminForm.isExcelFileEnabled()); + searchModule.setPptFileEnabled(searchAdminForm.isPptFileEnabled()); + searchModule.setPdfFileEnabled(searchAdminForm.isPdfFileEnabled()); return; } } diff --git a/src/main/java/org/olat/admin/search/SearchAdminForm.java b/src/main/java/org/olat/admin/search/SearchAdminForm.java index 95bddae7840f609faf72e934e41802d329b504fc..b5131124bf30a441464188fa9b5cd0a793f1cf5c 100644 --- a/src/main/java/org/olat/admin/search/SearchAdminForm.java +++ b/src/main/java/org/olat/admin/search/SearchAdminForm.java @@ -21,15 +21,22 @@ package org.olat.admin.search; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.IntegerElement; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; /** * Fulltext search input form. @@ -39,6 +46,10 @@ import org.olat.core.gui.control.WindowControl; public class SearchAdminForm extends FormBasicController { private TextElement indexInterval; + private TextElement blackList; + private MultipleSelectionElement excelFileEnabled; + private MultipleSelectionElement pptFileEnabled; + private MultipleSelectionElement pdfFileEnabled; private FormSubmit submit; /** @@ -58,6 +69,54 @@ public class SearchAdminForm extends FormBasicController { indexInterval.setValue(Long.toString(v)); } + public List<String> getFileBlackList() { + String value = blackList.getValue(); + if(StringHelper.containsNonWhitespace(value)) { + String[] files = value.split(","); + Set<String> list = new HashSet<String>(); + for(String file:files) { + list.add(file); + } + return new ArrayList<String>(list); + } + return Collections.emptyList(); + } + + public void setFileBlackList(List<String> files) { + if(files == null) return; + + StringBuilder sb = new StringBuilder(); + for(String file:files) { + if(sb.length() > 0) sb.append(','); + sb.append(file); + } + blackList.setValue(sb.toString()); + } + + public boolean isPptFileEnabled() { + return pptFileEnabled.isMultiselect() && pptFileEnabled.isSelected(0); + } + + public void setPptFileEnabled(boolean enabled) { + pptFileEnabled.select("on", enabled); + } + + public boolean isExcelFileEnabled() { + return excelFileEnabled.isMultiselect() && excelFileEnabled.isSelected(0); + } + + public void setExcelFileEnabled(boolean enabled) { + excelFileEnabled.select("on", enabled); + } + + public boolean isPdfFileEnabled() { + return pdfFileEnabled.isMultiselect() && pdfFileEnabled.isSelected(0); + } + + public void setPdfFileEnabled(boolean enabled) { + pdfFileEnabled.select("on", enabled); + } + @Override protected void formOK(UserRequest ureq) { fireEvent(ureq, Event.DONE_EVENT); @@ -71,6 +130,14 @@ public class SearchAdminForm extends FormBasicController { indexInterval.setRegexMatchCheck("\\d+", "error.index.interval.must.be.number"); //indexInterval.setMinValueCheck(0, "error.index.interval.must.be.number"); indexInterval.setDisplaySize(4); + + blackList = uifactory.addTextAreaElement("search.admin.label.blackList", 3, 80, "", formLayout); + blackList.setExampleKey("search.admin.label.blackList.example", null); + + excelFileEnabled = uifactory.addCheckboxesHorizontal("search.admin.label.enableExcel", formLayout, new String[]{"on"}, new String[]{""}, null); + pptFileEnabled = uifactory.addCheckboxesHorizontal("search.admin.label.enablePpt", formLayout, new String[]{"on"}, new String[]{""}, null); + pdfFileEnabled = uifactory.addCheckboxesHorizontal("search.admin.label.enablePdf", formLayout, new String[]{"on"}, new String[]{""}, null); + submit = new FormSubmit("submit", "submit"); formLayout.add(submit); } diff --git a/src/main/java/org/olat/admin/search/_content/index.html b/src/main/java/org/olat/admin/search/_content/index.html index 58c51bf1ca1b95ffb25425f6dc76bb773713bc45..518c83f6e55df5c3d386b2ff75c8f35281212f53 100644 --- a/src/main/java/org/olat/admin/search/_content/index.html +++ b/src/main/java/org/olat/admin/search/_content/index.html @@ -21,6 +21,11 @@ #if ($searchstatus.IndexExists == true) $r.translate("search.label.index.creation.date") : $searchstatus.indexCreationDate<br /> #end + #if ($cronExpression == false) + $r.translate("index.cron") : $r.translate("index.cron.disabled")<br /> + #else + $r.translate("index.cron") : $cronExpression<br /> + #end </fieldset> <fieldset> @@ -44,4 +49,6 @@ $r.render("searchAdminForm") <legend>$r.translate("fullindexer.file.counters.title")</legend> $searchstatus.fullIndexerStatus.fileTypeCounters<br /> $r.translate("fullindexer.doc.excluded.document.count") : $searchstatus.fullIndexerStatus.excludedDocumentCount -</fieldset> \ No newline at end of file +</fieldset> + +$r.render("logViewController") \ No newline at end of file diff --git a/src/main/java/org/olat/admin/search/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/search/_i18n/LocalStrings_de.properties index 1d3607e3bf2bac0a151d19a39292e61ebbd29c4f..e53824d923826fafa5ce564cc7565ffb5b790564 100644 --- a/src/main/java/org/olat/admin/search/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/search/_i18n/LocalStrings_de.properties @@ -19,7 +19,14 @@ fullindexer.label.time=Index erzeugt in fullindexer.status.title=Indexer Status index.label.exist=Index existiert index.status.title=Index Status +index.cron=Cron expression +index.cron.disabled=deaktiviert search.admin.label.index.interval=Index Intervall [ms] +search.admin.label.blackList=Black list +search.admin.label.blackList.example=Komma getrennt (imsmanifest.xml,dangerous.pdf) +search.admin.label.enablePpt=Powerpoint Dateien indizieren +search.admin.label.enableExcel=Excel Dateien indizieren +search.admin.label.enablePdf=PDF Dateien indizieren search.admin.title=Administration der Volltextsuche search.label.index.creation.date=Index erzeugt search.label.query=Anzahl verarbeiteter Suchanfragen diff --git a/src/main/java/org/olat/admin/search/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/search/_i18n/LocalStrings_en.properties index 88092043fdd87c9f97f25acfc7db8617cddbaa8b..d0644780520573f5ed2d651c233f986011eca765 100644 --- a/src/main/java/org/olat/admin/search/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/search/_i18n/LocalStrings_en.properties @@ -19,8 +19,15 @@ fullindexer.label.time=Index generated in fullindexer.status.title=Indexer status index.label.exist=Index exists index.status.title=Index status +index.cron=Cron expression +index.cron.disabled=disabled search.admin.form.title=Settings search.admin.label.index.interval=Index interval [ms] +search.admin.label.blackList=File black list +search.admin.label.blackList.example=Comma separated (imsmanifest.xml,dangerous.pdf) +search.admin.label.enablePpt=Index Powerpoint files +search.admin.label.enableExcel=Index Excel files +search.admin.label.enablePdf=Index PDF files search.admin.title=Administration of full-text search search.label.index.creation.date=Index created search.label.query=Number of processed search queries diff --git a/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java b/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java index 923f4b9ff26a33e2c33f0eae119b473f1c05c514..4702148be1fdba7adfb0664f208b886c2944f193 100644 --- a/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java +++ b/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java @@ -59,6 +59,8 @@ import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.logging.AssertException; +import org.olat.core.util.mail.MailContext; +import org.olat.core.util.mail.MailContextImpl; import org.olat.core.util.mail.MailHelper; import org.olat.core.util.mail.MailNotificationEditController; import org.olat.core.util.mail.MailTemplate; @@ -491,7 +493,9 @@ public class GroupController extends BasicController { } else { ccIdentities = null; } - MailerResult mailerResult = mailer.sendMailAsSeparateMails(toBeRemoved, ccIdentities, null, mailTemplate, sender); + //fxdiff VCRP-16: intern mail system + MailContext context = new MailContextImpl(getWindowControl().getBusinessControl().getAsString()); + MailerResult mailerResult = mailer.sendMailAsSeparateMails(context, toBeRemoved, ccIdentities, null, mailTemplate, sender); MailHelper.printErrorsAndWarnings(mailerResult, getWindowControl(), ureq.getLocale()); } } @@ -537,7 +541,9 @@ public class GroupController extends BasicController { } else { ccIdentities = null; } - MailerResult mailerResult = mailer.sendMailAsSeparateMails(identitiesAddedEvent.getAddedIdentities(), ccIdentities, null, mailTemplate, sender); + //fxdiff VCRP-16: intern mail system + MailContext context = new MailContextImpl(getWindowControl().getBusinessControl().getAsString()); + MailerResult mailerResult = mailer.sendMailAsSeparateMails(context, identitiesAddedEvent.getAddedIdentities(), ccIdentities, null, mailTemplate, sender); MailHelper.appendErrorsAndWarnings(mailerResult, errorMessage, infoMessage, ureq.getLocale()); } // report any errors on screen diff --git a/src/main/java/org/olat/admin/securitygroup/gui/multi/UsersToGroupWizardController.java b/src/main/java/org/olat/admin/securitygroup/gui/multi/UsersToGroupWizardController.java index ecc27ddd52d61c5d4229f2e9a618402b94fd8a39..6eb7be711fd64d355c55df1112f1a35f514fefc1 100644 --- a/src/main/java/org/olat/admin/securitygroup/gui/multi/UsersToGroupWizardController.java +++ b/src/main/java/org/olat/admin/securitygroup/gui/multi/UsersToGroupWizardController.java @@ -45,10 +45,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.WizardController; -import org.olat.core.gui.translator.PackageTranslator; -import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; -import org.olat.core.util.Util; import org.olat.core.util.mail.MailNotificationEditController; import org.olat.core.util.mail.MailTemplate; @@ -150,7 +147,7 @@ public class UsersToGroupWizardController extends WizardController { if (event == Event.DONE_EVENT) { // calc stuff, preview - List existIdents = securityManager.getIdentitiesOfSecurityGroup(securityGroup); + List<Identity> existIdents = securityManager.getIdentitiesOfSecurityGroup(securityGroup); oks = new ArrayList<Identity>(); List<String> isanonymous = new ArrayList<String>(); List<String> notfounds = new ArrayList<String>(); @@ -207,11 +204,11 @@ public class UsersToGroupWizardController extends WizardController { } } - private String listNames(List names) { + private String listNames(List<String> names) { StringBuilder sb = new StringBuilder(); int cnt = names.size(); for (int i = 0; i < cnt; i++) { - String identname = (String) names.get(i); + String identname = names.get(i); sb.append(identname); if (i < cnt - 1) sb.append(", "); } @@ -260,6 +257,7 @@ class UserIdsForm extends FormBasicController { } + @SuppressWarnings("unused") @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { diff --git a/src/main/java/org/olat/admin/sysinfo/InfoMessageControllerSingleVM.java b/src/main/java/org/olat/admin/sysinfo/InfoMessageControllerSingleVM.java index 2c78d2242b281d5a838c1efd1d98b4b169db34d0..8a49343b189490f6836fbe9b32dc884b138e1b26 100644 --- a/src/main/java/org/olat/admin/sysinfo/InfoMessageControllerSingleVM.java +++ b/src/main/java/org/olat/admin/sysinfo/InfoMessageControllerSingleVM.java @@ -20,6 +20,7 @@ */ package org.olat.admin.sysinfo; +import org.olat.admin.AdminModule; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.util.GlobalStickyMessage; import org.olat.core.gui.UserRequest; @@ -32,6 +33,8 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.properties.Property; +import org.olat.properties.PropertyManager; /** * Description:<br> @@ -60,6 +63,10 @@ public class InfoMessageControllerSingleVM extends BasicController { infoMsgEdit = createVelocityContainer("infomsgEdit"); infoMsgView.contextPut("cluster", Boolean.FALSE); infoMsgEdit.contextPut("cluster", Boolean.FALSE); + PropertyManager pm = PropertyManager.getInstance(); + Property p = pm.findProperty(null, null, null, AdminModule.SYSTEM_PROPERTY_CATEGORY, AdminModule.PROPERTY_MAINTENANCE_MESSAGE); + String adminToken = (p == null ? "" : p.getStringValue()); + infoMsgView.contextPut("admintoken", adminToken); infomsgEditButton = LinkFactory.createButton("infomsgEdit", infoMsgView, this); maintenancemsgEditButton = LinkFactory.createButton("maintenancemsgEdit", infoMsgView, this); diff --git a/src/main/java/org/olat/admin/sysinfo/SessionAdministrationController.java b/src/main/java/org/olat/admin/sysinfo/SessionAdministrationController.java index dc0ba9701390b519d4d3b9ba7b5a9c90630e11c1..76fa106b38ef792714a534f7d227604f235a9d32 100644 --- a/src/main/java/org/olat/admin/sysinfo/SessionAdministrationController.java +++ b/src/main/java/org/olat/admin/sysinfo/SessionAdministrationController.java @@ -55,6 +55,8 @@ import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.properties.Property; +import org.olat.properties.PropertyManager; /** * @author Christian Guretzki @@ -104,6 +106,10 @@ public class SessionAdministrationController extends BasicController { rejectDMZReuqestsLink = LinkFactory.createButton("session.admin.reject.dmz.requests.link", myContent, this); allowDMZRequestsLink = LinkFactory.createButton("session.admin.allow.dmz.requests.link", myContent, this); } + PropertyManager pm = PropertyManager.getInstance(); + Property p = pm.findProperty(null, null, null, AdminModule.SYSTEM_PROPERTY_CATEGORY, AdminModule.PROPERTY_SESSION_ADMINISTRATION); + String sessionToken = (p == null ? "" : p.getStringValue()); + myContent.contextPut("sessionToken", sessionToken); allowLoginLink = LinkFactory.createButton("session.admin.allow.login.link", myContent, this); sessionAdminOldestSessionForm = new SessionAdminOldestSessionForm(ureq, wControl, getTranslator()); listenTo(sessionAdminOldestSessionForm); diff --git a/src/main/java/org/olat/admin/sysinfo/SysinfoController.java b/src/main/java/org/olat/admin/sysinfo/SysinfoController.java index 03ebc2c7439c3bc5091170b3b6b426eef9c1c430..3163163509020f2ac6929e0464c6a7a381796477 100644 --- a/src/main/java/org/olat/admin/sysinfo/SysinfoController.java +++ b/src/main/java/org/olat/admin/sysinfo/SysinfoController.java @@ -21,11 +21,12 @@ package org.olat.admin.sysinfo; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; -import java.io.FilenameFilter; import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; +import java.lang.management.MemoryUsage; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; @@ -45,6 +46,7 @@ import org.olat.admin.cache.AllCachesController; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; +import org.olat.basesecurity.SecurityGroup; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.chiefcontrollers.BaseChiefController; import org.olat.core.commons.persistence.DBFactory; @@ -62,7 +64,6 @@ import org.olat.core.gui.control.DefaultController; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; -import org.olat.core.gui.control.creator.AutoCreator; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.helpers.Settings; @@ -172,21 +173,28 @@ public class SysinfoController extends BasicController implements Activateable2 tabbedPane.addTab(ACTION_SESSIONS, usessC.getInitialComponent()); tabbedPane.addTab(ACTION_INFOMSG,infoMsgCtrl.getInitialComponent()); tabbedPane.addTab(ACTION_ERRORS, myErrors); - tabbedPane.addTab(ACTION_LOGLEVELS, myLoglevels); + //fxdiff: FXOLAT-79 check fxadmin-rights + BaseSecurity securityManager = BaseSecurityManager.getInstance(); + String FXADMIN_SECURITY_GROUP = "fxadmins"; // see FrentixOlatModule + SecurityGroup fxAdminGroup = securityManager.findSecurityGroupByName(FXADMIN_SECURITY_GROUP); + boolean isFXAdmin = securityManager.isIdentityInSecurityGroup(ureq.getIdentity(), fxAdminGroup); + if (isFXAdmin) tabbedPane.addTab(ACTION_LOGLEVELS, myLoglevels); tabbedPane.addTab(ACTION_SYSINFO, mySysinfo); tabbedPane.addTab(ACTION_SNOOP, mySnoop); - tabbedPane.addTab("requestloglevel", requestLoglevelController.getInitialComponent()); + if (isFXAdmin) tabbedPane.addTab("requestloglevel", requestLoglevelController.getInitialComponent()); tabbedPane.addTab("usersessions", sessionAdministrationController.getInitialComponent()); tabbedPane.addTab(ACTION_LOCKS, lockController.getInitialComponent()); - tabbedPane.addTab(getTranslator().translate("sess.multiuserevents"), myMultiUserEvents); - tabbedPane.addTab(ACTION_HIBERNATEINFO, myHibernateInfo); + // fxdiff: not usable: tabbedPane.addTab(getTranslator().translate("sess.multiuserevents"), myMultiUserEvents); + if (isFXAdmin) tabbedPane.addTab(ACTION_HIBERNATEINFO, myHibernateInfo); - AutoCreator controllerCreator = (AutoCreator)CoreSpringFactory.getBean("clusterAdminControllerCreator"); - clusterController = controllerCreator.createController(ureq, wControl); - tabbedPane.addTab("Cluster", clusterController.getInitialComponent()); + //fxdiff: no cluster anyway: +// AutoCreator controllerCreator = (AutoCreator)CoreSpringFactory.getBean("clusterAdminControllerCreator"); +// clusterController = controllerCreator.createController(ureq, wControl); +// tabbedPane.addTab("Cluster", clusterController.getInitialComponent()); cachePanel = new Panel("cachepanel"); - tabbedPane.addTab("caches", cachePanel); + //fxdiff: FXOLAT-79 check fxadmin-rights + if (isFXAdmin) tabbedPane.addTab("caches", cachePanel); VelocityContainer myBuildinfo = createVelocityContainer("buildinfo"); fillBuildInfoTab(myBuildinfo); @@ -222,6 +230,11 @@ public class SysinfoController extends BasicController implements Activateable2 m.put("value", Settings.getFullVersionInfo()); properties.add(m); + m = new HashMap<String, String>(); + m.put("key", "HG changeset on build"); + m.put("value", Settings.getRepoRevision()); + properties.add(m); + m = new HashMap<String, String>(); m.put("key", "isClusterMode"); m.put("value", Settings.getClusterMode().equals("Cluster") ? "true" : "false" ); @@ -258,7 +271,7 @@ public class SysinfoController extends BasicController implements Activateable2 m = new HashMap<String, String>(); m.put("key", "jsMathEnabled"); boolean jsMathEnabled = BaseChiefController.isJsMathEnabled(); - m.put("value", Boolean.toString(jsMathEnabled) + (jsMathEnabled ? "" : " (run 'mvn olat:font' to enable)")); + m.put("value", Boolean.toString(jsMathEnabled)); properties.add(m); m = new HashMap<String, String>(); @@ -306,6 +319,25 @@ public class SysinfoController extends BasicController implements Activateable2 appendFormattedKeyValue(sb, "Total Memory", StringHelper.formatMemory(r.totalMemory())); appendFormattedKeyValue(sb, "Free Memory", StringHelper.formatMemory(r.freeMemory())); appendFormattedKeyValue(sb, "Max Memory", StringHelper.formatMemory(r.maxMemory())); + + sb.append("<br />Detailed Memory Information (Init/Used/Max)<br/> "); + Iterator<MemoryPoolMXBean> iter = ManagementFactory.getMemoryPoolMXBeans().iterator(); + while (iter.hasNext()) { + MemoryPoolMXBean item = iter.next(); + String name = item.getName(); + MemoryType type = item.getType(); + appendFormattedKeyValue(sb, name, " Type: " + type); + MemoryUsage usage = item.getUsage(); + appendFormattedKeyValue(sb, "Usage", StringHelper.formatMemory(usage.getInit()) + "/" + StringHelper.formatMemory(usage.getUsed()) + "/" + StringHelper.formatMemory(usage.getMax())); + MemoryUsage peak = item.getPeakUsage(); + appendFormattedKeyValue(sb, "Peak", StringHelper.formatMemory(peak.getInit()) + "/" + StringHelper.formatMemory(peak.getUsed()) + "/" + StringHelper.formatMemory(peak.getMax())); + MemoryUsage collections = item.getCollectionUsage(); + if (collections!= null){ + appendFormattedKeyValue(sb, "Collections", StringHelper.formatMemory(collections.getInit()) + "/" + StringHelper.formatMemory(collections.getUsed()) + "/" + StringHelper.formatMemory(collections.getMax())); + } + sb.append("<hr/>"); + } + int controllerCnt = DefaultController.getControllerCount(); sb.append("<br />Controller Count (active and not disposed):"+controllerCnt); sb.append("<br />Concurrent Dispatching Threads: "+DispatcherAction.getConcurrentCounter()); diff --git a/src/main/java/org/olat/admin/sysinfo/_content/infomsg.html b/src/main/java/org/olat/admin/sysinfo/_content/infomsg.html index d4eaf6e79152daaf134e66755e9c061006e21d1a..4fee23ad4efb58abc4d1eacc863d21286c40928f 100644 --- a/src/main/java/org/olat/admin/sysinfo/_content/infomsg.html +++ b/src/main/java/org/olat/admin/sysinfo/_content/infomsg.html @@ -1,3 +1,4 @@ +<i>Message Admin-Token: $!admintoken</i><br/> <fieldset> <legend>$r.translate("infomsg.title")</legend> #if ($infomsg) diff --git a/src/main/java/org/olat/admin/sysinfo/_content/sessionDetails.html b/src/main/java/org/olat/admin/sysinfo/_content/sessionDetails.html index b695c74782ae5d11a3dda4003e12523200b4c659..b8faf406d982a5d44e9e140732fd2572e8067e68 100644 --- a/src/main/java/org/olat/admin/sysinfo/_content/sessionDetails.html +++ b/src/main/java/org/olat/admin/sysinfo/_content/sessionDetails.html @@ -4,7 +4,8 @@ $r.render("backLink") <fieldset> <legend>Session Information</legend> <table class="b_table"> - <tr><td>$r.translate("sess.authent"):</td><td>$isauth</td></tr> + <tr><td>$r.translate("sess.authent"):</td><td>$isauth</td></tr> + <tr><td>ID:</td><td>$!si.getSession().getId()</td></tr> <tr><td>$r.translate("sess.first"):</td><td>$!si.getFirstname()</td></tr> <tr><td>$r.translate("sess.last"):</td><td>$!si.getLastname()</td></tr> <tr><td>$r.translate("sess.identity"):</td><td>$!si.getLogin()</td></tr> diff --git a/src/main/java/org/olat/admin/sysinfo/_content/sessionadministration.html b/src/main/java/org/olat/admin/sysinfo/_content/sessionadministration.html index cc4c6cb960b0f87a09042ae9750caa4cd2b8693d..dbadf5698439cd6a88a6e459bdc64c2ee657b7cb 100644 --- a/src/main/java/org/olat/admin/sysinfo/_content/sessionadministration.html +++ b/src/main/java/org/olat/admin/sysinfo/_content/sessionadministration.html @@ -1,4 +1,5 @@ <h4>$r.translate("sessionadministration.title")</h4> +<i>Session Admin-Token: $!sessionToken</i> <div class="sessionadmin"> #if ($loginBlocked) $r.render("session.admin.allow.login.link") @@ -19,6 +20,7 @@ $r.render("session.admin.invalidate.all.link") $r.render("session.admin.reject.dmz.requests.link") #end #end +<br/> <br /><br /> $r.render("session.admin.oldest.session.form") <br /><br /> diff --git a/src/main/java/org/olat/admin/user/SendTokenToUserForm.java b/src/main/java/org/olat/admin/user/SendTokenToUserForm.java index 8e11435b514c092965b36861c422d7c553361f87..a7b91ff19b12522c60d2a41c50e63d996f6f1cf8 100644 --- a/src/main/java/org/olat/admin/user/SendTokenToUserForm.java +++ b/src/main/java/org/olat/admin/user/SendTokenToUserForm.java @@ -119,15 +119,18 @@ public class SendTokenToUserForm extends FormBasicController { Preferences prefs = user.getUser().getPreferences(); Locale locale = I18nManager.getInstance().getLocaleOrDefault(prefs.getLanguage()); String emailAdress = user.getUser().getProperty(UserConstants.EMAIL, locale); - dummyKey = Encoder.encrypt(emailAdress); + if (emailAdress != null) { + dummyKey = Encoder.encrypt(emailAdress); - String serverpath = Settings.getServerContextPathURI(); - Translator userTrans = Util.createPackageTranslator(RegistrationManager.class, locale) ; - String body = userTrans.translate("pwchange.intro", new String[] { user.getName() }) - + userTrans.translate("pwchange.body", new String[] { - serverpath, dummyKey, I18nManager.getInstance().getLocaleKey(locale) - }); - return escapeLanguage(body); + String serverpath = Settings.getServerContextPathURI(); + Translator userTrans = Util.createPackageTranslator(RegistrationManager.class, locale) ; + String body = userTrans.translate("pwchange.intro", new String[] { user.getName() }) + + userTrans.translate("pwchange.body", new String[] { + serverpath, dummyKey, I18nManager.getInstance().getLocaleKey(locale) + }); + return escapeLanguage(body); + } + else return "This function is not available for users without an email-adress!"; } private void sendToken(UserRequest ureq, String text) { @@ -135,7 +138,7 @@ public class SendTokenToUserForm extends FormBasicController { // check if user has an OLAT provider token, otherwhise a pwd change makes no sense Authentication auth = BaseSecurityManager.getInstance().findAuthentication(user, BaseSecurityModule.getDefaultAuthProviderIdentifier()); if (auth == null) { - showWarning("password.cantchange"); + showWarning("changeuserpwd.failed"); return; } @@ -150,7 +153,7 @@ public class SendTokenToUserForm extends FormBasicController { tk = rm.createTemporaryKeyByEmail(emailAdress, ip, rm.PW_CHANGE); } if(text.indexOf(dummyKey) < 0) { - showWarning("password.cantchange"); + showWarning("changeuserpwd.failed"); return; } String body = text.replace(dummyKey, tk.getRegistrationKey()); diff --git a/src/main/java/org/olat/admin/user/UserAdminController.java b/src/main/java/org/olat/admin/user/UserAdminController.java index 967d9d73ee19eb8eb77c6e46d128f37c75840406..19523c38fce3b597f8f021d7a89d4639defe3ec9 100644 --- a/src/main/java/org/olat/admin/user/UserAdminController.java +++ b/src/main/java/org/olat/admin/user/UserAdminController.java @@ -31,6 +31,7 @@ import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.BaseSecurityModule; import org.olat.basesecurity.Constants; +import org.olat.basesecurity.SecurityGroup; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; @@ -223,6 +224,15 @@ public class UserAdminController extends BasicController implements Activateable * @return boolean */ private boolean allowedToManageUser(UserRequest ureq, Identity identity) { + + //fxdiff FXOLAT-184 prevent editing of users that are in frentix-superadmin group (except "frentix" wants to change own profile) + Identity editor = ureq.getUserSession().getIdentity(); + SecurityGroup frentixSuperAdminGroup = BaseSecurityManager.getInstance().findSecurityGroupByName("fxadmins"); + if(BaseSecurityManager.getInstance().isIdentityInSecurityGroup(identity, frentixSuperAdminGroup)){ + if(editor.equals(identity)) return true; + return false; + } + boolean isOlatAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); if (isOlatAdmin) return true; diff --git a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java index 4621075a574091be8dacb5d1638abf5cd0f5a2b4..4bbd1862a110e4d68f5931f94d71e040a4813f38 100644 --- a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java +++ b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java @@ -466,9 +466,13 @@ public class UsermanagerUserSearchController extends BasicController implements if (runContext.containsKey("validChange") && ((Boolean) runContext.get("validChange")).booleanValue()) { HashMap<String, String> attributeChangeMap = (HashMap<String, String>) runContext.get("attributeChangeMap"); HashMap<String, String> roleChangeMap = (HashMap<String, String>) runContext.get("roleChangeMap"); - if (!(attributeChangeMap.size()==0 && roleChangeMap.size()==0)){ + List<Long> ownGroups = (List<Long>) runContext.get("ownerGroups"); + List<Long> partGroups = (List<Long>) runContext.get("partGroups"); + List<Long> mailGroups = (List<Long>) runContext.get("mailGroups"); + if (attributeChangeMap.size() != 0 || roleChangeMap.size() != 0 || ownGroups.size() != 0 || partGroups.size() != 0){ + Identity addingIdentity = ureq1.getIdentity(); ubcMan.changeSelectedIdentities(selectedIdentities, attributeChangeMap, roleChangeMap, notUpdatedIdentities, - isAdministrativeUser, getTranslator()); + isAdministrativeUser, ownGroups, partGroups, mailGroups, getTranslator(), addingIdentity); hasChanges = true; } } diff --git a/src/main/java/org/olat/admin/user/bulkChange/GroupAddOverviewModel.java b/src/main/java/org/olat/admin/user/bulkChange/GroupAddOverviewModel.java new file mode 100644 index 0000000000000000000000000000000000000000..883f578a71c2da09f348353ac3cd65ce04f46229 --- /dev/null +++ b/src/main/java/org/olat/admin/user/bulkChange/GroupAddOverviewModel.java @@ -0,0 +1,81 @@ +package org.olat.admin.user.bulkChange; + +import java.util.List; + +import org.apache.commons.lang.StringEscapeUtils; +import org.olat.core.gui.components.table.DefaultTableDataModel; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.filter.FilterFactory; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupManager; +import org.olat.group.BusinessGroupManagerImpl; + +/** + * + * Description:<br> + * model for group add overview at last step in bulk-change + * + * <P> + * Initial Date: 09.05.2011 <br> + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +public class GroupAddOverviewModel extends DefaultTableDataModel { + + private Translator translator; + private List<Long> mailGroupIDs; + private List<Long> ownGroupIDs; + private List<Long> partGroupIDs; + private BusinessGroupManager bGM; + + public GroupAddOverviewModel(List<Long> allGroupIDs, List<Long> ownGroupIDs, List<Long> partGroupIDs, List<Long> mailGroups, Translator trans) { + super(allGroupIDs); + this.translator = trans; + this.ownGroupIDs = ownGroupIDs; + this.partGroupIDs = partGroupIDs; + this.mailGroupIDs = mailGroups; + bGM = BusinessGroupManagerImpl.getInstance(); + } + + @Override + public int getColumnCount() { + return 5; + } + + @Override + public Object getValueAt(int row, int col) { + Long key = (Long) getObject(row); + BusinessGroup group = bGM.loadBusinessGroup(key, false); + if (group == null) return "error"; + + switch (col) { + case 0: // name + String name = group.getName(); + name = StringEscapeUtils.escapeHtml(name).toString(); + return name; + case 1: // description + String desc = group.getDescription(); + desc = FilterFactory.getHtmlTagAndDescapingFilter().filter(desc); + return desc; + case 2: // type + return translator.translate(group.getType()); + case 3: // users role + if(partGroupIDs.contains(key) && ownGroupIDs.contains(key)) { + return translator.translate("attende.and.owner"); + } + else if(partGroupIDs.contains(key)) { + return translator.translate("attende"); + } + else if(ownGroupIDs.contains(key)) { + return translator.translate("owner"); + } + case 4: // send email + if (mailGroupIDs.contains(key)){ + return translator.translate("yes"); + } else { + return translator.translate("no"); + } + default: + return "error"; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeManager.java b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeManager.java index 217729a0c2f762662474cda21a67e176ebe00b6a..c47411d8e3086b6dea47afe31085f8aba4882a2f 100644 --- a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeManager.java +++ b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeManager.java @@ -35,6 +35,7 @@ import org.apache.velocity.exception.MethodInvocationException; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.RuntimeConstants; +import org.olat.admin.user.groups.GroupAddManager; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; @@ -92,7 +93,7 @@ public class UserBulkChangeManager extends BasicManager { } public void changeSelectedIdentities(List<Identity> selIdentities, HashMap<String, String> attributeChangeMap, - HashMap<String, String> roleChangeMap, ArrayList<String> notUpdatedIdentities, boolean isAdministrativeUser, Translator trans) { + HashMap<String, String> roleChangeMap, ArrayList<String> notUpdatedIdentities, boolean isAdministrativeUser, List<Long> ownGroups, List<Long> partGroups, List<Long> mailGroups, Translator trans, Identity addingIdentity) { Translator transWithFallback = UserManager.getInstance().getPropertyHandlerTranslator(trans); String usageIdentifyer = UserBulkChangeStep00.class.getCanonicalName(); @@ -104,7 +105,7 @@ public class UserBulkChangeManager extends BasicManager { String[] securityGroups = { Constants.GROUP_USERMANAGERS, Constants.GROUP_GROUPMANAGERS, Constants.GROUP_AUTHORS, Constants.GROUP_ADMIN }; UserManager um = UserManager.getInstance(); BaseSecurity secMgr = BaseSecurityManager.getInstance(); - + GroupAddManager groupAddMgr = GroupAddManager.getInstance(); // loop over users to be edited: for (Identity identity : selIdentities) { @@ -186,6 +187,11 @@ public class UserBulkChangeManager extends BasicManager { } } + // FXOLAT-101: add identity to new groups: + if (ownGroups.size() != 0 || partGroups.size() != 0){ + groupAddMgr.addIdentityToGroups(ownGroups, partGroups, mailGroups, identity, addingIdentity); + } + // set status if (roleChangeMap.containsKey("Status")) { Integer status = Integer.parseInt(roleChangeMap.get("Status")); @@ -206,7 +212,7 @@ public class UserBulkChangeManager extends BasicManager { // commit changes for this user db.intermediateCommit(); - } + } // for identities } diff --git a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01.java b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01.java index b11f858aef67714f6c3cae106b888e063602f1d9..c619039c267e0b3da53b70ed5d16f8b1a4c90813 100644 --- a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01.java +++ b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01.java @@ -57,7 +57,7 @@ import org.olat.user.UserManager; * <P> * Initial Date: 30.01.2008 <br> * - * @author rhaag + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com */ class UserBulkChangeStep01 extends BasicStep { @@ -68,7 +68,7 @@ class UserBulkChangeStep01 extends BasicStep { public UserBulkChangeStep01(UserRequest ureq) { super(ureq); setI18nTitleAndDescr("step1.description", null); - setNextStep(new UserBulkChangeStep02(ureq)); + setNextStep(new UserBulkChangeStep01a(ureq)); } /** @@ -163,7 +163,6 @@ class UserBulkChangeStep01 extends BasicStep { return true; } - @SuppressWarnings("synthetic-access") @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("step1.title"); diff --git a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01a.java b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01a.java new file mode 100644 index 0000000000000000000000000000000000000000..8d383a9ef9be81ae69374aa6793196099acd1357 --- /dev/null +++ b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep01a.java @@ -0,0 +1,67 @@ +/** + * 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 frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.admin.user.bulkChange; + +import org.olat.admin.user.groups.GroupSearchController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.impl.Form; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.wizard.BasicStep; +import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; +import org.olat.core.gui.control.generic.wizard.StepFormController; +import org.olat.core.gui.control.generic.wizard.StepsRunContext; + +/** + * Description:<br> + * fxdiff: 101 Allow group adding + * second step: add users to group + * <P> + * Initial Date: 09.05.2011 <br> + * + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +class UserBulkChangeStep01a extends BasicStep { + + public UserBulkChangeStep01a(final UserRequest ureq) { + super(ureq); + setI18nTitleAndDescr("step1a.description", null); + setNextStep(new UserBulkChangeStep02(ureq)); + } + + /** + * @see org.olat.core.gui.control.generic.wizard.Step#getInitialPrevNextFinishConfig() + */ + @Override + public PrevNextFinishConfig getInitialPrevNextFinishConfig() { + return new PrevNextFinishConfig(true, true, false); + } + + /** + * @see org.olat.core.gui.control.generic.wizard.Step#getStepController(org.olat.core.gui.UserRequest, org.olat.core.gui.control.WindowControl, + * org.olat.core.gui.control.generic.wizard.StepsRunContext, org.olat.core.gui.components.form.flexible.impl.Form) + */ + @Override + public StepFormController getStepController(final UserRequest ureq, final WindowControl windowControl, final StepsRunContext stepsRunContext, final Form form) { + StepFormController stepI = new GroupSearchController(ureq, windowControl, form, stepsRunContext, FormBasicController.LAYOUT_VERTICAL, null); + return stepI; + } + +} diff --git a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep02.java b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep02.java index c806eac4cb37f693b9a9dd1b70739983b088857c..44c0458e143f5c7a9e14c14c623325b6a2bf70a7 100644 --- a/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep02.java +++ b/src/main/java/org/olat/admin/user/bulkChange/UserBulkChangeStep02.java @@ -21,7 +21,9 @@ package org.olat.admin.user.bulkChange; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Properties; @@ -29,6 +31,7 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.apache.velocity.runtime.RuntimeConstants; +import org.olat.admin.user.groups.GroupSearchController; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; @@ -50,8 +53,11 @@ import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.Roles; +import org.olat.core.util.Util; +import org.olat.group.ui.BusinessGroupTableModel; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; @@ -119,8 +125,11 @@ class UserBulkChangeStep02 extends BasicStep { super(ureq, control, rootForm, runContext, LAYOUT_VERTICAL, null); // use custom translator with fallback to user properties translator UserManager um = UserManager.getInstance(); - setTranslator(um.getPropertyHandlerTranslator(getTranslator())); - flc.setTranslator(getTranslator()); + Translator pt1 = um.getPropertyHandlerTranslator(getTranslator()); + Translator pt2 = Util.createPackageTranslator(BusinessGroupTableModel.class, ureq.getLocale(), pt1); + Translator pt3 = Util.createPackageTranslator(GroupSearchController.class, ureq.getLocale(), pt2); + setTranslator(pt3); + flc.setTranslator(pt3); initForm(ureq); } @@ -263,6 +272,30 @@ class UserBulkChangeStep02 extends BasicStep { colPos + 1), tableColumnModel); uifactory.addTableElement("newUsers", tableDataModel, formLayoutVertical); + + //fxdiff: 101 add group overview + HashSet<Long> allGroups = new HashSet<Long>(); + List<Long> ownGroups = (List<Long>) getFromRunContext("ownerGroups"); + List<Long> partGroups = (List<Long>) getFromRunContext("partGroups"); + allGroups.addAll(ownGroups); + allGroups.addAll(partGroups); + List<Long> mailGroups = (List<Long>) getFromRunContext("mailGroups"); + + if (allGroups.size() != 0) { + uifactory.addSpacerElement("space", formLayout, true); + uifactory.addStaticTextElement("add.to.groups", "", formLayout); + FlexiTableColumnModel groupColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + groupColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.group.name")); + groupColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("description")); + groupColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.group.type")); + groupColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.user.role")); + groupColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("send.email")); + + FlexiTableDataModel groupDataModel = FlexiTableDataModelFactory.createFlexiTableDataModel(new GroupAddOverviewModel(Arrays.asList(allGroups.toArray(new Long[]{})), ownGroups, partGroups, mailGroups, getTranslator()), groupColumnModel); + + uifactory.addTableElement("groupOverview", groupDataModel, formLayout); + } + } /** @@ -292,4 +325,5 @@ class UserBulkChangeStep02 extends BasicStep { } } } + } diff --git a/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_de.properties index 333e19f2546477d1e9fad3dff509c6f48704834f..b12f1d91655544de42cc90da4c5a246912277511 100644 --- a/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_de.properties @@ -30,4 +30,6 @@ bulk.psw.newpsw=Passwort bulk.psw.submit=Ausführen bulk.psw.done={0} Benutzer bearbeitet. bulk.auto.disc=Nutzungsbedingungen akzeptieren -bulk.lang.german=Systemsprache auf Deutsch setzen \ No newline at end of file +bulk.lang.german=Systemsprache auf Deutsch setzen +step1a.description=Gruppe(n) auswählen +add.to.groups=Die Benutzer werden ausserdem folgenden Gruppen hinzugefügt. \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_en.properties index f57c9bbdbed89393ba4ffa1e0a50b3d4a0074de1..70180b28950e0db584e61d312ac67601757fd43a 100644 --- a/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/user/bulkChange/_i18n/LocalStrings_en.properties @@ -31,3 +31,5 @@ table.role.status=Status table.role.useradmin=User administrator table.user.login=User name title=Modification of user attributes +step1a.description=Choose group(s) +add.to.groups=The chosen users will additionally be added to the following groups. \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java index 7ca6f95246b939fac5cd645642b032f7df72d2af..fd2ecd87acc946a8e61df2836897e102242b275a 100644 --- a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java +++ b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java @@ -27,6 +27,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -59,11 +60,15 @@ import org.olat.core.util.resource.OresHelper; import org.olat.course.assessment.EfficiencyStatementManager; import org.olat.properties.Property; import org.olat.properties.PropertyManager; +import org.olat.registration.RegistrationManager; +import org.olat.registration.TemporaryKeyImpl; import org.olat.repository.delete.service.DeletionModule; import org.olat.user.UserDataDeletable; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; +import com.thoughtworks.xstream.XStream; + /** * Manager for user-deletion. @@ -304,6 +309,14 @@ public class UserDeletionManager extends BasicManager { logInfo("Removing user=" + identity + " from security group=" + secGroup.toString()); } + // fxdiff: FXOLAT-44 delete emails still in change-workflow + RegistrationManager rm = RegistrationManager.getInstance(); + String key = identity.getUser().getProperty("emchangeKey", null); + TemporaryKeyImpl tempKey = rm.loadTemporaryKeyByRegistrationKey(key); + if (tempKey != null) { + rm.deleteTemporaryKey(tempKey); + } + // can be used, if there is once the possibility to delete identities without db-constraints... //if neither email nor login should be kept, REALLY DELETE Identity /*if (!keepUserEmailAfterDeletion & !keepUserLoginAfterDeletion){ diff --git a/src/main/java/org/olat/admin/user/groups/AddToGroupsEvent.java b/src/main/java/org/olat/admin/user/groups/AddToGroupsEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..dd30689a5986719a4f615eaecb5f573a78f389cd --- /dev/null +++ b/src/main/java/org/olat/admin/user/groups/AddToGroupsEvent.java @@ -0,0 +1,60 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ +package org.olat.admin.user.groups; + +import java.util.List; + +import org.olat.core.gui.control.Event; + +/** + * Description:<br> + * transport selected groups to add an identity to + * + * <P> + * Initial Date: 12.04.2011 <br> + * + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +public class AddToGroupsEvent extends Event { + + private List<Long> ownerList; + private List<Long> participantList; + private List<Long> mailForGroupsList; + + public AddToGroupsEvent(List<Long> ownLong, List<Long> partLong, List<Long> mailLong) { + super("addToGroups"); + this.ownerList = ownLong; + this.participantList = partLong; + this.mailForGroupsList = mailLong; + } + + public List<Long> getOwnerGroupKeys() { + return ownerList; + } + + public List<Long> getParticipantGroupKeys() { + return participantList; + } + + public List<Long> getMailForGroupsList() { + return mailForGroupsList; + } + +} diff --git a/src/main/java/org/olat/admin/user/groups/GroupAddManager.java b/src/main/java/org/olat/admin/user/groups/GroupAddManager.java new file mode 100644 index 0000000000000000000000000000000000000000..87002d251c77f62a33391081803f7b9933896470 --- /dev/null +++ b/src/main/java/org/olat/admin/user/groups/GroupAddManager.java @@ -0,0 +1,148 @@ +/** +* 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) 1999-2011 at frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.admin.user.groups; + +import java.util.ArrayList; +import java.util.List; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.id.Identity; +import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.manager.BasicManager; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.coordinate.SyncerExecutor; +import org.olat.core.util.mail.MailTemplate; +import org.olat.core.util.mail.MailerResult; +import org.olat.core.util.mail.MailerWithTemplate; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupManager; +import org.olat.group.BusinessGroupManagerImpl; +import org.olat.group.ui.BGConfigFlags; +import org.olat.group.ui.BGMailHelper; +import org.olat.util.logging.activity.LoggingResourceable; + +/** + * Description:<br> + * Functions to add an identity to multiple groups at once. + * + * <P> + * Initial Date: 09.05.2011 <br> + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +public class GroupAddManager extends BasicManager { + + private static GroupAddManager INSTANCE = new GroupAddManager(); + + public static final GroupAddManager getInstance() { + return INSTANCE; + } + + private GroupAddManager(){ + // + } + + /** + * add identity to given groups as owner and/or participant + * @param ownGroups + * @param partGroups + * @param mailGroups + * @param ident + * @param addingIdentity + * @return + */ + public String[] addIdentityToGroups(List<Long> ownGroups, List<Long> partGroups, List<Long> mailGroups, final Identity ident, final Identity addingIdentity){ + AddToGroupsEvent groupsEv = new AddToGroupsEvent(ownGroups, partGroups, mailGroups); + return addIdentityToGroups(groupsEv, ident, addingIdentity); + } + + /** + * add identity to given groups as owner and/or participant + * @param groupsEv + * @param ident the identity to be added + * @param addingIdentity the identity who does this action + * @return + */ + public String[] addIdentityToGroups(AddToGroupsEvent groupsEv, final Identity ident, final Identity addingIdentity){ + final BusinessGroupManager bgm = BusinessGroupManagerImpl.getInstance(); + BaseSecurity securityManager = BaseSecurityManager.getInstance(); + final BGConfigFlags flags = BGConfigFlags.createBuddyGroupDefaultFlags(); + String[] resultTextArgs = new String[2]; + boolean addToAnyGroup = false; + + // notify user about add for following groups: + List<Long> notifyAboutAdd = new ArrayList<Long>(); + List<Long> mailKeys = groupsEv.getMailForGroupsList(); + + // add to owner groups + List<Long> ownerKeys = groupsEv.getOwnerGroupKeys(); + String ownerGroupnames = ""; + for (Long groupKey : ownerKeys) { + BusinessGroup group = bgm.loadBusinessGroup(groupKey, false); + if (group != null && !securityManager.isIdentityInSecurityGroup(ident, group.getOwnerGroup())){ +// seems not to work, but would be the way to go! +// ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group)); + bgm.addOwnerAndFireEvent(addingIdentity, ident, group, flags, false); + ownerGroupnames += group.getName() + ", "; + addToAnyGroup = true; + if (!notifyAboutAdd.contains(groupKey) && mailKeys.contains(groupKey)) notifyAboutAdd.add(groupKey); + } + } + resultTextArgs[0] = ownerGroupnames.substring(0, ownerGroupnames.length() > 0 ? ownerGroupnames.length() - 2 : 0); + + // add to participant groups + List<Long> participantKeys = groupsEv.getParticipantGroupKeys(); + String participantGroupnames = ""; + for (Long groupKey : participantKeys) { + BusinessGroup group = bgm.loadBusinessGroup(groupKey, false); + if (group != null && !securityManager.isIdentityInSecurityGroup(ident, group.getPartipiciantGroup())) { + final BusinessGroup toAddGroup = group; +// seems not to work, but would be the way to go! +// ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(group)); + CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(group, new SyncerExecutor(){ + public void execute() { + bgm.addParticipantAndFireEvent(addingIdentity, ident, toAddGroup, flags, false); + }}); + participantGroupnames += group.getName() + ", "; + addToAnyGroup = true; + if (!notifyAboutAdd.contains(groupKey) && mailKeys.contains(groupKey)) notifyAboutAdd.add(groupKey); + } + } + resultTextArgs[1] = participantGroupnames.substring(0, participantGroupnames.length() > 0 ? participantGroupnames.length() - 2 : 0); + + // send notification mails + for (Long notifKey : notifyAboutAdd) { + BusinessGroup group = bgm.loadBusinessGroup(notifKey, false); + MailTemplate mailTemplate = BGMailHelper.createAddParticipantMailTemplate(group, addingIdentity); + MailerWithTemplate mailer = MailerWithTemplate.getInstance(); + MailerResult mailerResult = mailer.sendMail(null, ident, null, null, mailTemplate, null); + if (mailerResult.getReturnCode() != MailerResult.OK){ + logDebug("Problems sending Group invitation mail for identity: " + ident.getName() + " and group: " + + group.getName() + " key: " + group.getKey() + " mailerresult: " + mailerResult.getReturnCode(), null); + } + } + + if (addToAnyGroup) { + return resultTextArgs; + } else { + return null; + } + } +} diff --git a/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java b/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java index 57ffc9f3ec3f9446b7debc17073e2d141236d4f3..037d298717092bb9c38f87a9a3b013377fc0b265 100644 --- a/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java +++ b/src/main/java/org/olat/admin/user/groups/GroupOverviewController.java @@ -27,9 +27,13 @@ import java.util.List; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.SecurityGroup; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.table.DefaultColumnDescriptor; +import org.olat.core.gui.components.table.StaticColumnDescriptor; import org.olat.core.gui.components.table.Table; import org.olat.core.gui.components.table.TableController; import org.olat.core.gui.components.table.TableEvent; @@ -38,12 +42,24 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalWindowWrapperController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.util.Util; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.coordinate.SyncerExecutor; +import org.olat.core.util.mail.MailHelper; +import org.olat.core.util.mail.MailTemplate; +import org.olat.core.util.mail.MailerResult; +import org.olat.core.util.mail.MailerWithTemplate; +import org.olat.core.util.notifications.NotificationHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupManager; import org.olat.group.BusinessGroupManagerImpl; +import org.olat.group.ui.BGConfigFlags; import org.olat.group.ui.BGControllerFactory; +import org.olat.group.ui.BGMailHelper; import org.olat.group.ui.BusinessGroupTableModel; /** @@ -57,11 +73,17 @@ import org.olat.group.ui.BusinessGroupTableModel; * @author Roman Haag, frentix GmbH, roman.haag@frentix.com */ public class GroupOverviewController extends BasicController { + private static final String TABLE_ACTION_UNSUBSCRIBE = "unsubscribe"; private VelocityContainer vc; private TableController tblCtr; private GroupOverviewModel tableDataModel; private WindowControl wControl; private Identity identity; + private Link addGroups; + private CloseableModalWindowWrapperController calloutCtrl; + private GroupSearchController groupsCtrl; + private DialogBoxController removeFromGrpDlg; + private DialogBoxController sendMailDlg; private static String TABLE_ACTION_LAUNCH ; public GroupOverviewController(UserRequest ureq, WindowControl control, Identity identity, Boolean canStartGroups) { @@ -76,6 +98,7 @@ public class GroupOverviewController extends BasicController { vc = createVelocityContainer("groupoverview"); buildTableController(ureq, control); + addGroups = LinkFactory.createButton("add.groups", vc, this); vc.put("table.groups", tblCtr.getInitialComponent()); putInitialPanel(vc); } @@ -96,7 +119,8 @@ public class GroupOverviewController extends BasicController { tblCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.group.name", 1, TABLE_ACTION_LAUNCH, ureq.getLocale())); tblCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.user.role", 2, null, ureq.getLocale())); tblCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.user.joindate", 3, null, ureq.getLocale())); - + tblCtr.addColumnDescriptor(new StaticColumnDescriptor(TABLE_ACTION_UNSUBSCRIBE, "table.user.unsubscribe", translate("table.user.unsubscribe"))); + //build data model BusinessGroupManager bgm = BusinessGroupManagerImpl.getInstance(); BaseSecurity sm = BaseSecurityManager.getInstance(); @@ -161,9 +185,17 @@ public class GroupOverviewController extends BasicController { * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ @Override - @SuppressWarnings("unused") protected void event( UserRequest ureq, Component source, Event event) { - // no events to catch + if (source == addGroups){ + groupsCtrl = new GroupSearchController(ureq, getWindowControl()); + listenTo(groupsCtrl); + + calloutCtrl = new CloseableModalWindowWrapperController(ureq, getWindowControl(), translate("add.groups"), groupsCtrl.getInitialComponent(), "ccgroupadd"); + calloutCtrl.setInitialWindowSize(500, 400); +// calloutCtrl = new CloseableCalloutWindowController(ureq, wControl, groupsCtrl.getInitialComponent(), addGroups, translate("add.groups"), false, null); + listenTo(calloutCtrl); + calloutCtrl.activate(); + } } /** @@ -184,15 +216,112 @@ public class GroupOverviewController extends BasicController { if (currBusinessGroup==null) { //group seems to be removed meanwhile, reload table and show error showError("group.removed"); - buildTableController(ureq, wControl); - vc.put("table.groups", tblCtr.getInitialComponent()); + updateGroupsTable(ureq); } else { BGControllerFactory.getInstance().createRunControllerAsTopNavTab(currBusinessGroup, ureq, getWindowControl(), true, null); } + } else if (actionid.equals(TABLE_ACTION_UNSUBSCRIBE)){ + // fxdiff: FXOLAT-101 see similar doBuddyGroupLeave() in BGMainController + String groupName = currBusinessGroup.getName(); + BaseSecurity securityManager = BaseSecurityManager.getInstance(); + List<Identity> ownerList = securityManager.getIdentitiesOfSecurityGroup(currBusinessGroup.getOwnerGroup()); + List<Identity> partList = securityManager.getIdentitiesOfSecurityGroup(currBusinessGroup.getPartipiciantGroup()); + + String rmText = translate("unsubscribe.text", new String[]{NotificationHelper.getFormatedName(identity), groupName}); + if ((ownerList.size() == 1 && partList.size() == 0) || (ownerList.size() == 0 && partList.size() == 1)) { + rmText += " <br/>" + translate("unsubscribe.group.del"); + } + removeFromGrpDlg = activateYesNoDialog(ureq, translate("unsubscribe.title"), rmText, removeFromGrpDlg); + removeFromGrpDlg.setUserObject(currBusinessGroup); } } - } + } else if (source == groupsCtrl && event instanceof AddToGroupsEvent){ + AddToGroupsEvent groupsEv = (AddToGroupsEvent) event; + if (groupsEv.getOwnerGroupKeys().isEmpty() && groupsEv.getParticipantGroupKeys().isEmpty()){ + // no groups selected + showWarning("group.add.result.none"); + } else { + if (calloutCtrl != null) calloutCtrl.deactivate(); + String[] resultTextArgs = GroupAddManager.getInstance().addIdentityToGroups(groupsEv, identity, getIdentity()); + if (resultTextArgs != null){ + String message = translate("group.add.result", resultTextArgs); + getWindowControl().setInfo(message); + } else { + showWarning("group.add.result.none"); + } + updateGroupsTable(ureq); + } + } else if (source == removeFromGrpDlg && DialogBoxUIFactory.isYesEvent(event)){ + //fxdiff: FXOLAT-138 let user decide to send notif-mail or not + sendMailDlg = activateYesNoDialog(ureq, translate("unsubscribe.title"), translate("send.email.notif"), sendMailDlg); + } else if (source == sendMailDlg){ + if (DialogBoxUIFactory.isYesEvent(event)) + removeUserFromGroup(ureq, true); + else + removeUserFromGroup(ureq, false); + } + + } + + /** + * + * @param ureq + * @param doSendMail + */ + private void removeUserFromGroup(UserRequest ureq, boolean doSendMail) { + // fxdiff: FXOLAT-101 see similar doBuddyGroupLeave() in BGMainController + final BusinessGroupManager bgm = BusinessGroupManagerImpl.getInstance(); + BusinessGroup currBusinessGroup = (BusinessGroup) removeFromGrpDlg.getUserObject(); + String groupName = currBusinessGroup.getName(); + BaseSecurity securityManager = BaseSecurityManager.getInstance(); + final BGConfigFlags flags = BGConfigFlags.createBuddyGroupDefaultFlags(); + SecurityGroup owners = currBusinessGroup.getOwnerGroup(); + List<Identity> ownerList = securityManager.getIdentitiesOfSecurityGroup(owners); + List<Identity> partList = securityManager.getIdentitiesOfSecurityGroup(currBusinessGroup.getPartipiciantGroup()); + + if ((ownerList.size() == 1 && partList.size() == 0) || (ownerList.size() == 0 && partList.size() == 1)) { + // really delete the group as it has no more owners/participants + if(doSendMail) + bgm.deleteBusinessGroupWithMail(currBusinessGroup, wControl, ureq, getTranslator(), null); + else + bgm.deleteBusinessGroup(currBusinessGroup); + } else { + // 1) remove as owner + if (securityManager.isIdentityInSecurityGroup(identity, owners)) { + bgm.removeOwnerAndFireEvent(ureq.getIdentity(), identity, currBusinessGroup, flags, false); + } + + // 2) remove as participant + final BusinessGroup toRemFromGroup = currBusinessGroup; + CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(currBusinessGroup, new SyncerExecutor() { + public void execute() { + bgm.removeParticipantAndFireEvent(getIdentity(), identity, toRemFromGroup, flags, false); + } + }); + + // 3) notify user about this action: + if(doSendMail){ + MailTemplate mailTemplate = BGMailHelper.createRemoveParticipantMailTemplate(currBusinessGroup, getIdentity()); + MailerWithTemplate mailer = MailerWithTemplate.getInstance(); + MailerResult mailerResult = mailer.sendMail(null, identity, null, null, mailTemplate, null); + MailHelper.printErrorsAndWarnings(mailerResult, wControl, getLocale()); + } + } + + updateGroupsTable(ureq); + showInfo("unsubscribe.successful", groupName); } + + /** + * @param ureq + * update Table + */ + private void updateGroupsTable(UserRequest ureq) { + buildTableController(ureq, wControl); + vc.put("table.groups", tblCtr.getInitialComponent()); + } + + } diff --git a/src/main/java/org/olat/admin/user/groups/GroupSearchController.java b/src/main/java/org/olat/admin/user/groups/GroupSearchController.java new file mode 100644 index 0000000000000000000000000000000000000000..013eb7bc45638059e58cd649da79f78984bb0e22 --- /dev/null +++ b/src/main/java/org/olat/admin/user/groups/GroupSearchController.java @@ -0,0 +1,243 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ +package org.olat.admin.user.groups; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.Form; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.wizard.StepFormBasicController; +import org.olat.core.gui.control.generic.wizard.StepsEvent; +import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.group.BusinessGroupManagerImpl; +import org.olat.group.ui.BusinessGroupTableModel; + +/** + * Description:<br> + * Searches for groups from the whole system. + * + * <P> + * Initial Date: 11.04.2011 <br> + * + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +public class GroupSearchController extends StepFormBasicController { + + private TextElement search; + private FormLink searchButton; + private FormLayoutContainer resTable; + private ArrayList<MultipleSelectionElement> parts; + private ArrayList<MultipleSelectionElement> mails; + private ArrayList<MultipleSelectionElement> owners; + private FormItem errorComp; + private String lastSearchValue; + + // constructor to be used like a normal FormBasicController + public GroupSearchController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, FormBasicController.LAYOUT_VERTICAL); + Translator pT = Util.createPackageTranslator(BusinessGroupTableModel.class, ureq.getLocale(), getTranslator()); + this.flc.setTranslator(pT); + initForm(ureq); + } + + // constructor for use in steps-wizzard + public GroupSearchController(UserRequest ureq, WindowControl wControl, Form form, StepsRunContext stepsRunContext, int layoutVertical, String pageName) { + super(ureq, wControl, form, stepsRunContext, layoutVertical, pageName); + Translator pT = Util.createPackageTranslator(BusinessGroupTableModel.class, ureq.getLocale(), getTranslator()); + this.flc.setTranslator(pT); + 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) { + setFormDescription("group.search.description"); + + search = uifactory.addTextElement("search.field", "search.field", 100, "", formLayout); + searchButton = uifactory.addFormLink("search", formLayout, Link.BUTTON_SMALL); + + resTable = FormLayoutContainer.createCustomFormLayout("resultsTable", getTranslator(), this.velocity_root + "/resulttable.html"); + formLayout.add(resTable); + resTable.contextPut("bGM", BusinessGroupManagerImpl.getInstance()); + + if (!isUsedInStepWizzard()) uifactory.addSpacerElement("space", formLayout, false); + errorComp = uifactory.createSimpleErrorText("error", ""); + formLayout.add(errorComp); + if (!isUsedInStepWizzard()) uifactory.addFormSubmitButton("save", formLayout); + } + + + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == searchButton || source == search) { + String searchValue = search.getValue(); + doSearchGroups(searchValue, ureq); + lastSearchValue = searchValue; + } + } + + /** + * Perform a search for the given search value in the search result providers + * and clear any GUI errors that might be on the page + * + * @param searchValue + * @param ureq + */ + private void doSearchGroups(String searchValue, UserRequest ureq) { + if (StringHelper.containsNonWhitespace(searchValue)){ + GroupSearchResultProvider searchProvider = new GroupSearchResultProvider(ureq.getIdentity(), getLocale(), null); + Map<String, String> resMap = new HashMap<String, String>(); + searchProvider.getAutoCompleteContent(searchValue, resMap); + updateResultTable(resMap); + errorComp.clearError(); + } + } + + private void updateResultTable(Map<String, String> resMap){ + owners = new ArrayList<MultipleSelectionElement>(); + parts = new ArrayList<MultipleSelectionElement>(); + mails = new ArrayList<MultipleSelectionElement>(); + + for (Entry<String, String> entry : resMap.entrySet()) { + // prepare checkboxes + String dummyLabel = " "; + MultipleSelectionElement owner = uifactory.addCheckboxesHorizontal("owner"+entry.getValue(), "", resTable, new String[]{entry.getValue()}, new String[]{dummyLabel}, new String[]{""}); + MultipleSelectionElement part = uifactory.addCheckboxesHorizontal("part"+entry.getValue(), "", resTable, new String[]{entry.getValue()}, new String[]{dummyLabel}, new String[]{""}); + MultipleSelectionElement mail = uifactory.addCheckboxesHorizontal("mail"+entry.getValue(), "", resTable, new String[]{entry.getValue()}, new String[]{dummyLabel}, new String[]{""}); + owners.add(owner); + parts.add(part); + mails.add(mail); + } + resTable.contextPut("resMap", resMap); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + String searchValue = search.getValue(); + if ((lastSearchValue == null && searchValue != null) || (lastSearchValue != null && !lastSearchValue.equals(searchValue))) { + // User pressed enter in input field to search for groups, no group + // selected yet. Just search for groups that matches for this input + doSearchGroups(searchValue, ureq); + lastSearchValue = searchValue; + return false; + } + if (isUsedInStepWizzard()) return true; + errorComp.clearError(); + boolean result = false; + List<String> ownerGroups = getCheckedKeys(owners); + List<String> partGroups = getCheckedKeys(parts); + result = (ownerGroups.size() !=0 || partGroups.size() != 0); + if (!result) { + errorComp.setErrorKey("error.choose.one", null); + } + return result; + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) + */ + @Override + protected void formOK(UserRequest ureq) { + List<String> ownerGroups = getCheckedKeys(owners); + List<Long> ownLong = convertStringToLongList(ownerGroups); + + List<String> partGroups = getCheckedKeys(parts); + List<Long> partLong = convertStringToLongList(partGroups); + + List<String> mailGroups = getCheckedKeys(mails); + List<Long> mailLong = convertStringToLongList(mailGroups); + + if (isUsedInStepWizzard()){ + // might be used in wizzard during user import or user bulk change. allow next/finish according to previous steps. + addToRunContext("ownerGroups", ownLong); + addToRunContext("partGroups", partLong); + addToRunContext("mailGroups", mailLong); + boolean groupsChoosen = (ownerGroups.size() !=0 || partGroups.size() != 0); + boolean validImport = getFromRunContext("validImport") != null && ((Boolean) getFromRunContext("validImport")); + boolean validBulkChange = getFromRunContext("validChange") != null && ((Boolean) getFromRunContext("validChange")); + + boolean isValid = groupsChoosen || (validImport || validBulkChange) ; + addToRunContext("validGroupAdd",isValid ); + //fxdiff: FXOLAT-245 notify userbulkchange-wizard about valid change + addToRunContext("validChange",isValid ); + fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); + } else { + fireEvent(ureq, new AddToGroupsEvent(ownLong, partLong, mailLong)); + } + } + + + private List<String> getCheckedKeys(List<MultipleSelectionElement> items){ + List<String> selected = new ArrayList<String>(); + if (items == null) return selected; + for (MultipleSelectionElement formItem : items) { + if (formItem.isSelected(0)) { + selected.add(formItem.getKey(0)); + } + } + return selected; + } + + private List<Long> convertStringToLongList(List<String> groups) { + List<Long> ownLong = new ArrayList<Long>(); + if (groups == null || groups.isEmpty()) return ownLong; + for (String group : groups) { + Long key = null; + try { + key = Long.parseLong(group); + } catch (Exception e) { + // do nothing special + } + if (key != null) ownLong.add(key); + } + return ownLong; + } + + /** + * @see org.olat.core.gui.control.DefaultController#doDispose() + */ + @Override + protected void doDispose() { + // nothing + } + +} diff --git a/src/main/java/org/olat/admin/user/groups/GroupSearchResultProvider.java b/src/main/java/org/olat/admin/user/groups/GroupSearchResultProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..2d54585cab40439db72b64ce1c33de187315fe04 --- /dev/null +++ b/src/main/java/org/olat/admin/user/groups/GroupSearchResultProvider.java @@ -0,0 +1,194 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ +package org.olat.admin.user.groups; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.Policy; +import org.olat.core.gui.components.textboxlist.ResultMapProvider; +import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; +import org.olat.core.id.Roles; +import org.olat.core.util.Util; +import org.olat.course.CourseFactory; +import org.olat.course.CourseModule; +import org.olat.course.ICourse; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupManager; +import org.olat.group.BusinessGroupManagerImpl; +import org.olat.group.context.BGContextManager; +import org.olat.group.context.BGContextManagerImpl; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.resource.OLATResource; + +/** + * Description:<br> + * search for groups by an OR search in groups and courses-LR (each title and description) + * + * <P> + * Initial Date: 02.05.2011 <br> + * + * @author Roman Haag, frentix GmbH, roman.haag@frentix.com + */ +public class GroupSearchResultProvider implements ResultMapProvider { + + private BusinessGroupManager bGM; + private RepositoryManager repoM; + private Translator pT; + private String typeFilter; + private final Identity identity; + private static final int MAX_RESULTS = 50; + + public GroupSearchResultProvider(Identity identity, Locale locale, String typeFilter){ + this.identity = identity; + bGM = BusinessGroupManagerImpl.getInstance(); + repoM = RepositoryManager.getInstance(); + pT = Util.createPackageTranslator(this.getClass(), locale); + this.typeFilter = typeFilter; + } + + /** + * @see org.olat.core.gui.components.textboxlist.ResultMapProvider#getAutoCompleteContent(java.lang.String, + * java.util.Map) + */ + @Override + public void getAutoCompleteContent(String searchValue, Map<String, String> resMap) { + Map<Long, String> tempResult = new HashMap<Long, String>(); + + // split searchterms and search for each of them + String[] searchTerms = searchValue.split(" "); + List<String> searchTermsArr = Arrays.asList(searchTerms); + for (String searchString : searchTermsArr) { + searchForOneTerm(searchString, tempResult); + } + + // search for the whole multi-word string + if (searchTermsArr.size() > 1) searchForOneTerm(searchValue, tempResult); + + // build results + int count = 0; + for (Entry<Long, String> entry : tempResult.entrySet()) { + count++; + Long key = entry.getKey(); + String groupTitle = entry.getValue(); + resMap.put(groupTitle, String.valueOf(key)); + if (count > MAX_RESULTS) { + break; + } + } + } + + + private void searchForOneTerm(String searchValue, Map<Long, String> tempResult){ + // search groups itself + List<BusinessGroup> groups = bGM.findBusinessGroup('%' + searchValue + '%', typeFilter); + for (BusinessGroup group : groups) { + if (group.getOwnerGroup() != null && group.getPartipiciantGroup() != null) { + + BGContextManager contextManager = BGContextManagerImpl.getInstance(); + if (group.getGroupContext() != null) { + @SuppressWarnings("unchecked") + List<RepositoryEntry> repoEntries = contextManager.findRepositoryEntriesForBGContext(group.getGroupContext()); + for (RepositoryEntry rEntry : repoEntries) { + if (!tempResult.containsKey(group.getKey())) { + tempResult.put(group.getKey(), getCombinedRepoName(group, rEntry)); + } + } + } + + List<Policy> ownerPol = BaseSecurityManager.getInstance().getPoliciesOfSecurityGroup(group.getOwnerGroup()); + for (Policy policy : ownerPol) { + OLATResource groupRes = policy.getOlatResource(); + RepositoryEntry repoEntry = repoM.lookupRepositoryEntry(groupRes, false); + if (!tempResult.containsKey(group.getKey())) { + tempResult.put(group.getKey(), getCombinedRepoName(group, repoEntry)); + } + } + + if (group.getType().equals(BusinessGroup.TYPE_BUDDYGROUP)) { + if (!tempResult.containsKey(group.getKey())) { + tempResult.put(group.getKey(), prepareGroupName(group)); + } + } + } + } + + // search by key if it is one + Long key = null; + try { + key = Long.parseLong(searchValue); + } catch (Exception e) { + // no key + } + if (key != null) { + BusinessGroup group = bGM.loadBusinessGroup(key, false); + if (group != null && !tempResult.containsKey(group.getKey())) { + tempResult.put(group.getKey(), prepareGroupName(group)); + } + } + + // do a search for LR of type course, add all contained groups (learn/right) + ArrayList<String> courseTypes = new ArrayList<String>(Arrays.asList(CourseModule.getCourseTypeName())); + Roles searchRoles = new Roles(true, true, true, true, false, true, false); + List<RepositoryEntry> allRepoEntries = new ArrayList<RepositoryEntry>(); + List<RepositoryEntry> repoEntriesByTitle = repoM.genericANDQueryWithRolesRestriction(searchValue, null, null, courseTypes, identity, searchRoles, null); + List<RepositoryEntry> repoEntriesByDesc = repoM.genericANDQueryWithRolesRestriction(null, null, searchValue, courseTypes, identity, searchRoles, null); + allRepoEntries.addAll(repoEntriesByDesc); + allRepoEntries.addAll(repoEntriesByTitle); + + for (RepositoryEntry repositoryEntry : allRepoEntries) { + ICourse course = CourseFactory.loadCourse(repositoryEntry.getOlatResource()); + List<BusinessGroup> allCourseGroups = new ArrayList<BusinessGroup>(); + List<BusinessGroup> courseLGs = course.getCourseEnvironment().getCourseGroupManager().getAllLearningGroupsFromAllContexts(); + List<BusinessGroup> courseRGs = course.getCourseEnvironment().getCourseGroupManager().getAllRightGroupsFromAllContexts(); + allCourseGroups.addAll(courseLGs); + allCourseGroups.addAll(courseRGs); + for (BusinessGroup group : allCourseGroups) { + if (!tempResult.containsKey(group.getKey())) { + tempResult.put(group.getKey(), getCombinedRepoName(group, repositoryEntry)); + } + } + } + } + + + private String getCombinedRepoName(BusinessGroup group, RepositoryEntry repoEntry) { + if (repoEntry != null) { + String groupName = pT.translate("group.result.course", new String[] { repoEntry.getDisplayname(), prepareGroupName(group) } ) ; + return groupName; + } else if (group != null) { + return prepareGroupName(group); + } + return ""; + } + + private String prepareGroupName(BusinessGroup group) { + return pT.translate("group.result.group", new String[]{ group.getName() }); + } + +} diff --git a/src/main/java/org/olat/admin/user/groups/_content/groupoverview.html b/src/main/java/org/olat/admin/user/groups/_content/groupoverview.html index 751c51be55be02296c68cbcf3210f79fa8ca3063..fe6ae159d1356ae77a45385fba312a91b80bcd4d 100644 --- a/src/main/java/org/olat/admin/user/groups/_content/groupoverview.html +++ b/src/main/java/org/olat/admin/user/groups/_content/groupoverview.html @@ -1,2 +1,3 @@ -<h3>$r.translate("title")</h3> +<h3>$r.translate("add.groups.title")</h3> +$r.render("add.groups")<br/> $r.render("table.groups") \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/groups/_content/resulttable.html b/src/main/java/org/olat/admin/user/groups/_content/resulttable.html new file mode 100644 index 0000000000000000000000000000000000000000..d9a8b7fcb72ad0e1e7694c5a238c49e6ef0fb067 --- /dev/null +++ b/src/main/java/org/olat/admin/user/groups/_content/resulttable.html @@ -0,0 +1,33 @@ +#if($!resMap && $resMap.size()>0) +<div class="b_table_wrapper"> +<table> + <thead> + <tr> + <th class="b_first_child">$r.translate("table.group.name")</th> + <th>$r.translate("table.group.type")</th> + <th>$r.translate("description")</th> + <th>$r.translate("group.add.as", $r.translate("owner"))</th> + <th>$r.translate("group.add.as", $r.translate("attende"))</th> + <th class="b_last_child">$r.translate("send.email")</th> + </tr> + </thead> + <tbody> + #foreach($groupentry in $resMap.entrySet()) + #set($title = $groupentry.getKey()) + #set($id = $groupentry.getValue()) + #set($group = $bGM.loadBusinessGroup("$id", false) ) + <tr class="#if($velocityCount % 2 == 0) b_table_odd #end #if($velocityCount >= $resMap.size()) b_last_child #end" > + <td>$title</td> + <td>$r.translate($group.getType()) </td> + <td>$!group.getDescription()</td> + <td style="text-align: center;">$r.render("owner$id")</td> + <td style="text-align: center;">$r.render("part$id")</td> + <td style="text-align: center;">$r.render("mail$id")</td> + </tr> + #end + </tbody> +</table> +</div> +#elseif($resMap) + $r.translate("no.results") +#end \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_de.properties index 87e31e9afc9b98dbb8fb5676e6ab8bac66a0612b..637a8c449cccb5953bdbfcfae2c0d03b9ff51cd6 100644 --- a/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_de.properties @@ -7,5 +7,25 @@ table.group.name=Gruppenname table.group.type=Typ der Gruppe table.user.joindate=Beitrittsdatum table.user.role=Benutzerrolle -title=Gruppen in welchen dieser Benutzer eingetragen ist +add.groups.title=Gruppen in welchen dieser Benutzer eingetragen ist waiting=Auf Warteliste in Position {0} +table.user.unsubscribe=Austragen +add.groups=Zu Gruppen hinzufügen +unsubscribe.successful=Der Benutzer wurde erfolgreich aus der Gruppe "{0}" ausgetragen. +unsubscribe.title=Benutzer aus Gruppe austragen +unsubscribe.text=Wollen Sie den Benutzer "{0}" wirklich aus der Gruppe "{1}" austragen? +unsubscribe.group.del=Dieser Benutzer ist der einzige Besitzer der Gruppe. Die Gruppe wird daher mit diesem Schritt unwiderruflich gelöscht. +group.add.as=Hinzufügen als {0} +group.search.description=Wählen Sie die Gruppen, zu welchen der Benutzer als Besitzer oder Teilnehmer eingetragen werden sollen. +group.add.result=Benutzer wurde den Gruppen hinzugefügt. Als Besitzer in "{0}" und als Teilnehmer in "{1}". +group.add.result.none=Der Benutzer wurde keiner Gruppe hinzugefügt. Entweder wurde keine Auswahl getroffen oder der Benutzer war bereits Gruppenmitglied. +group.result.desc=Beschreibung: {0} +group.result.course=Kurs: {0}, {1} +group.result.group=Gruppe: {0} +search.field=Gruppe suchen: +result=Gefundene Gruppen +send.email=Einladung senden +send.email.notif=Wollen Sie den Benutzer per Mail informieren? +description=Beschreibung +error.choose.one=Bitte wählen Sie mind. eine Gruppe. +no.results=Keine Gruppen zu diesen Suchbegriffen gefunden. \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_en.properties index 607c308da65aeaf2062f9546497f48240fa54209..0daed446a67108a1c7a98377ba921e102be61aeb 100644 --- a/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/user/groups/_i18n/LocalStrings_en.properties @@ -7,5 +7,25 @@ table.group.name=Group name table.group.type=Type of group table.user.joindate=Time of entry table.user.role=User role -title=Groups of which this user is a member +add.groups.title=Groups of which this user is a member waiting=On waiting list in position {0} +table.user.unsubscribe=Unsubscribe +add.groups=add to groups +unsubscribe.successful=You successfully unsubscribed the user from group "{0}". +unsubscribe.title=Unsubscribe user from group +unsubscribe.text=Do you really want to unsubscribe the user "{0}" from the group "{1}" ? +unsubscribe.group.del=This user is the alone owner of this group. This group will therefore be deleted forever by this step. +group.add.as=Add as {0} +group.search.description=Choose the groups, to which the user(s) should be added as owner or participant. +group.add.result=Added user to group(s). As owner in "{0}" and as participant in "{1}". +group.add.result.none=Did not add user to any group. Either none was selected or the user was already member of them. +group.result.desc=Description: {0} +group.result.course=Course: {0}, {1} +group.result.group=Group: {0} +search.field=Search a group: +result=Found Groups +send.email=Send invitational email +send.email.notif=Do you want to send a notification to the user? +description=Description +error.choose.one=Please choose at least one group. +no.results=Couldn't find any groups for this search term(s). \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/imp/ImportStep00.java b/src/main/java/org/olat/admin/user/imp/ImportStep00.java index 7c5adf64f49777dc4d08b8b080cf391d21ffe2a5..67daced1a55822f980d41f362cd2804526d4f60e 100644 --- a/src/main/java/org/olat/admin/user/imp/ImportStep00.java +++ b/src/main/java/org/olat/admin/user/imp/ImportStep00.java @@ -22,8 +22,10 @@ package org.olat.admin.user.imp; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; @@ -51,9 +53,13 @@ import org.olat.core.id.UserConstants; import org.olat.core.util.StringHelper; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; +import org.olat.registration.RegistrationManager; +import org.olat.registration.TemporaryKey; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; +import com.thoughtworks.xstream.XStream; + /** * Description:<br> * First step in user import wizard. @@ -141,6 +147,22 @@ class ImportStep00 extends BasicStep { idents = new ArrayList<Object>(); newIdents = new ArrayList<List<String>>(); + // fxdiff: check also emails in change-workflow, see OLAT-5723 + Set<String> tempEmailsInUse = new HashSet<String>(); + RegistrationManager rm = RegistrationManager.getInstance(); + List<TemporaryKey> tk = rm.loadTemporaryKeyByAction(RegistrationManager.EMAIL_CHANGE); + if (tk != null) { + for (TemporaryKey temporaryKey : tk) { + XStream xml = new XStream(); + Map<String, String> mails = (Map<String, String>) xml.fromXML(temporaryKey.getEmailAddress()); + for(Map.Entry<String, String> mailEntry:mails.entrySet()) { + tempEmailsInUse.add(mailEntry.getKey()); + tempEmailsInUse.add(mailEntry.getValue()); + } + } + } + // fxdiff > + // Note: some values are fix and required: login, pwd and lang, those // can not be configured in the config file // because they are not user properties. @@ -282,7 +304,16 @@ class ImportStep00 extends BasicStep { if (thisKey.equals(UserConstants.EMAIL)) { // check that no user with same email is already in list Integer mailPos = importedEmails.indexOf(thisValue); - if (mailPos != -1) { + // fxdiff + boolean duplicate = mailPos != -1; + if (!duplicate) { + duplicate |= tempEmailsInUse.contains(thisValue); + } + if(!duplicate) { + duplicate |= um.isEmailInUse(thisValue); + } + + if (duplicate) { // fxdiff > mailPos++; textAreaElement.setErrorKey("error.email.douplicate", new String[] { String.valueOf(i + 1), thisValue, mailPos.toString() }); diff --git a/src/main/java/org/olat/admin/user/imp/ImportStep01.java b/src/main/java/org/olat/admin/user/imp/ImportStep01.java index b0de0b3367b01079e7d1a515b9ca726b20b6884c..ba15b21de2bb8af4f0edf0439a1581d98a093dc8 100644 --- a/src/main/java/org/olat/admin/user/imp/ImportStep01.java +++ b/src/main/java/org/olat/admin/user/imp/ImportStep01.java @@ -18,7 +18,6 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.BasicStep; import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; -import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsEvent; @@ -38,13 +37,13 @@ class ImportStep01 extends BasicStep { this.canCreateOLATPassword = canCreateOLATPassword; this.newUsers = newUsers; setI18nTitleAndDescr("step1.description", "step1.short.description"); - setNextStep(Step.NOSTEP); + setNextStep(new ImportStep02(ureq)); //fxdiff: 101 have another step for group addition } @Override public PrevNextFinishConfig getInitialPrevNextFinishConfig() { if (newUsers) { - return new PrevNextFinishConfig(true, false, true); + return new PrevNextFinishConfig(true, true, true); } else { return new PrevNextFinishConfig(true, false, false); } diff --git a/src/main/java/org/olat/admin/user/imp/ImportStep02.java b/src/main/java/org/olat/admin/user/imp/ImportStep02.java new file mode 100644 index 0000000000000000000000000000000000000000..03b9e599b2059a59b4385649e67ce8155aff49c8 --- /dev/null +++ b/src/main/java/org/olat/admin/user/imp/ImportStep02.java @@ -0,0 +1,68 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.admin.user.imp; + +import org.olat.admin.user.groups.GroupSearchController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.impl.Form; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.wizard.BasicStep; +import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; +import org.olat.core.gui.control.generic.wizard.Step; +import org.olat.core.gui.control.generic.wizard.StepFormController; +import org.olat.core.gui.control.generic.wizard.StepsRunContext; + +/** + * + * Description:<br> + * have another step to define groups where new identities should be added to. + * + * <P> + * Initial Date: 09.05.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, www.frentix.com + */ +public class ImportStep02 extends BasicStep { + + public ImportStep02(UserRequest ureq) { + super(ureq); + setI18nTitleAndDescr("step2.description", "step2.short.description"); + setNextStep(Step.NOSTEP); + } + + /** + * @see org.olat.core.gui.control.generic.wizard.BasicStep#getInitialPrevNextFinishConfig() + */ + @Override + public PrevNextFinishConfig getInitialPrevNextFinishConfig() { + return new PrevNextFinishConfig(true, false, true); + } + + /** + * @see org.olat.core.gui.control.generic.wizard.BasicStep#getStepController(org.olat.core.gui.UserRequest, org.olat.core.gui.control.WindowControl, org.olat.core.gui.control.generic.wizard.StepsRunContext, org.olat.core.gui.components.form.flexible.impl.Form) + */ + @Override + public StepFormController getStepController(UserRequest ureq, WindowControl windowControl, StepsRunContext stepsRunContext, Form form) { + StepFormController stepI = new GroupSearchController(ureq, windowControl, form, stepsRunContext, FormBasicController.LAYOUT_VERTICAL, null); + return stepI; + } + +} diff --git a/src/main/java/org/olat/admin/user/imp/UserImportController.java b/src/main/java/org/olat/admin/user/imp/UserImportController.java index 1db105c0f35ab43ad46c04ae262d2d31993ff741..76512de775f146b8cc7354bc1efb64faf54b2376 100644 --- a/src/main/java/org/olat/admin/user/imp/UserImportController.java +++ b/src/main/java/org/olat/admin/user/imp/UserImportController.java @@ -25,7 +25,10 @@ package org.olat.admin.user.imp; import java.util.Iterator; import java.util.List; +import org.olat.admin.user.groups.GroupAddManager; import org.olat.basesecurity.AuthHelper; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -159,6 +162,15 @@ public class UserImportController extends BasicController { List<String> singleUser = it_news.next(); doCreateAndPersistIdentity(singleUser); } + // fxdiff: 101 add to groups + Identity addingIdentity = ureq1.getIdentity(); + List<Long> ownGroups = (List<Long>) runContext.get("ownerGroups"); + List<Long> partGroups = (List<Long>) runContext.get("partGroups"); + List<Long> mailGroups = (List<Long>) runContext.get("mailGroups"); + if (ownGroups.size() != 0 || partGroups.size() != 0){ + List<Object> allIdents = (List<Object>) runContext.get("idents"); + processGroupAdditionForAllIdents(allIdents, ownGroups, partGroups, mailGroups, addingIdentity); + } hasChanges = true; } } catch (Exception any) { @@ -167,6 +179,7 @@ public class UserImportController extends BasicController { // signal correct completion and tell if changes were made or not. return hasChanges ? StepsMainRunController.DONE_MODIFIED : StepsMainRunController.DONE_UNCHANGED; } + }; importStepsController = new StepsMainRunController(ureq, getWindowControl(), start, finish, null, translate("title")); @@ -175,4 +188,28 @@ public class UserImportController extends BasicController { } } + //fxdiff: 101 add idents to groups + void processGroupAdditionForAllIdents(List<Object> allIdents, List<Long> ownGroups, List<Long> partGroups, List<Long> mailGroups, Identity addingIdentity) { + GroupAddManager groupAddMgr = GroupAddManager.getInstance(); + + int counter = 0; + for (Object o : allIdents) { + Identity ident; + if (o instanceof Identity) { + // existing user + ident = (Identity) o; + } else { + List<String> userArray = (List<String>) o; + String identName = userArray.get(1); + ident = BaseSecurityManager.getInstance().findIdentityByName(identName); + } + if(ident != null){ + groupAddMgr.addIdentityToGroups(ownGroups, partGroups, mailGroups, ident, addingIdentity); + counter ++; + if (counter % 5 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + } + } + } } \ No newline at end of file diff --git a/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_de.properties index 7bed335f96b5406af60f6d8d4a1c83be23eee84d..8b67820d419cf8bc94b2520daf9566375b86280c 100644 --- a/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_de.properties @@ -34,8 +34,7 @@ step0.short.descr=User importieren. step1.description=Vorschau der Benutzerdaten step1.nonewusers=Die Tabelle enth\u00E4lt keine neuen Benutzer. Sie k\u00F6nnen keine Benutzer importieren. step1.short.descr=Vorschau -step2.description=Import abgeschlossen -step2.short.descr=Vorschau +step2.description=Gruppe(n) auswählen submit.import=Speichern table.user.existing=Neuer Benutzer? table.user.lang=Sprache diff --git a/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_en.properties index 02c30b79a78f5e3bbfd48056a48acafc9a37c7fb..ff4ae9cef47d2a7d7d15e492bafcd04e1209d4c0 100644 --- a/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/user/imp/_i18n/LocalStrings_en.properties @@ -34,8 +34,7 @@ step0.short.descr=Import user step1.description=Preview of user data step1.nonewusers=There are no new users in this table. You will not be able to import new users. step1.short.descr=Preview -step2.description=Finish import -step2.short.descr=Preview +step2.description=Choose group(s) submit.import=Save table.user.existing=New user? table.user.lang=Language diff --git a/src/main/java/org/olat/admin/version/OrphanVersionsController.java b/src/main/java/org/olat/admin/version/OrphanVersionsController.java new file mode 100644 index 0000000000000000000000000000000000000000..11d2c36c3ecfa41de5ff35e3af18bca93a076bb0 --- /dev/null +++ b/src/main/java/org/olat/admin/version/OrphanVersionsController.java @@ -0,0 +1,199 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.admin.version; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.table.DefaultColumnDescriptor; +import org.olat.core.gui.components.table.StaticColumnDescriptor; +import org.olat.core.gui.components.table.Table; +import org.olat.core.gui.components.table.TableController; +import org.olat.core.gui.components.table.TableDataModel; +import org.olat.core.gui.components.table.TableEvent; +import org.olat.core.gui.components.table.TableGuiConfiguration; +import org.olat.core.gui.components.table.TableMultiSelectEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.util.vfs.version.OrphanVersion; +import org.olat.core.util.vfs.version.VFSRevision; +import org.olat.core.util.vfs.version.VersionsManager; + +/** + * + * Description:<br> + * List all orphans + * + * <P> + * Initial Date: 5 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-127: file versions maintenance tool +public class OrphanVersionsController extends BasicController { + + private static final String CMD_DELETE = "delete"; + private final static DecimalFormat sizeFormat = new DecimalFormat("#0.#", new DecimalFormatSymbols(Locale.ENGLISH)); + + private TableController tableCtr; + private final List<OrphanVersion> orphans; + + public OrphanVersionsController(UserRequest ureq, WindowControl wControl, List<OrphanVersion> orphans) { + super(ureq, wControl); + + TableGuiConfiguration tableConfig = new TableGuiConfiguration(); + tableConfig.setTableEmptyMessage(translate("table.empty")); + tableConfig.setDownloadOffered(true); + tableConfig.setMultiSelect(true); + + this.orphans = orphans; + + tableCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator()); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.file", 0, null, getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.versions", 1, null, getLocale())); + tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.size", 2, null, getLocale())); + tableCtr.addColumnDescriptor(new StaticColumnDescriptor(CMD_DELETE, "delete", translate("delete"))); + tableCtr.setTableDataModel(new OrphanTableModel(orphans)); + + tableCtr.addMultiSelectAction("delete", CMD_DELETE); + + listenTo(tableCtr); + + putInitialPanel(tableCtr.getInitialComponent()); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == tableCtr) { + if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) { + TableEvent te = (TableEvent) event; + String actionid = te.getActionId(); + int rowid = te.getRowId(); + OrphanVersion orphan = (OrphanVersion) tableCtr.getTableDataModel().getObject(rowid); + if (actionid.equals(CMD_DELETE)) { + VersionsManager.getInstance().delete(orphan); + orphans.remove(orphan); + tableCtr.modelChanged(); + } + } else if (event.getCommand().equals(Table.COMMAND_MULTISELECT)) { + // Multiselect events + TableMultiSelectEvent tmse = (TableMultiSelectEvent) event; + BitSet selectedOrphans = tmse.getSelection(); + String actionid = tmse.getAction(); + if (CMD_DELETE.equals(actionid)) { + List<OrphanVersion> toRemove = new ArrayList<OrphanVersion>(); + for (int i=selectedOrphans.nextSetBit(0); i >= 0; i=selectedOrphans.nextSetBit(i+1)) { + int rowCount = tableCtr.getTableDataModel().getRowCount(); + if(i >= 0 && i < rowCount) { + OrphanVersion orphan = (OrphanVersion)tableCtr.getTableDataModel().getObject(i); + VersionsManager.getInstance().delete(orphan); + toRemove.add(orphan); + } + } + for(OrphanVersion orphan:toRemove) { + orphans.remove(orphan); + } + tableCtr.setTableDataModel(new OrphanTableModel(orphans)); + tableCtr.modelChanged(); + } + } + } + } + + private class OrphanTableModel implements TableDataModel { + + private List<OrphanVersion> orphanList; + + public OrphanTableModel(List<OrphanVersion> orphans) { + this.orphanList = orphans; + } + + @Override + public int getColumnCount() { + return 3; + } + + @Override + public int getRowCount() { + return orphanList.size(); + } + + @Override + public Object getValueAt(int row, int col) { + OrphanVersion orphan = getObject(row); + switch(col) { + case 0: return orphan.getOriginalFilePath(); + case 1: { + return orphan.getVersions().getRevisions().size(); + } + case 2: { + List<VFSRevision> versions = orphan.getVersions().getRevisions(); + long size = 0l; + for(VFSRevision revision:versions) { + size += revision.getSize(); + } + + String unit = "KB"; + double humanSize = size / 1024.0d; + if(humanSize > 1024) { + humanSize /= 1024; + unit = "MB"; + } + return sizeFormat.format(humanSize) + " " + unit; + } + default: return "ERROR"; + } + } + + @Override + public OrphanVersion getObject(int row) { + return orphanList.get(row); + } + + @Override + public void setObjects(List objects) { + orphanList = objects; + } + + @Override + public Object createCopyWithEmptyList() { + return new OrphanTableModel(Collections.<OrphanVersion>emptyList()); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/admin/version/VersionAdminController.java b/src/main/java/org/olat/admin/version/VersionAdminController.java index b8e74c01194bbd1d9c59cb4ba00089659ad6f09c..90f79ccbe29a2fe661b512828c5dc2b052f5fa2a 100644 --- a/src/main/java/org/olat/admin/version/VersionAdminController.java +++ b/src/main/java/org/olat/admin/version/VersionAdminController.java @@ -21,16 +21,13 @@ package org.olat.admin.version; import org.olat.admin.SystemAdminMainController; -import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.SingleSelection; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.control.Controller; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; import org.olat.core.util.Util; -import org.olat.core.util.vfs.version.SimpleVersionConfig; /** * @@ -41,66 +38,32 @@ import org.olat.core.util.vfs.version.SimpleVersionConfig; * <P> * Initial Date: 21 sept. 2009 <br> * - * @author srosse + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class VersionAdminController extends FormBasicController { +//fxdiff FXOLAT-127: file versions maintenance tool +public class VersionAdminController extends BasicController { - private SingleSelection numOfVersions; + private final VersionSettingsForm settingsForm; + private final VersionMaintenanceForm maintenanceForm; - private String[] keys = new String[] { - "0","2","3","4","5","10","25","50","-1" - }; - - private String[] values = new String[] { - "0","2","3","4","5","10","25","50","-1" - }; + private VelocityContainer mainVC; public VersionAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); // use combined translator from system admin main setTranslator(Util.createPackageTranslator(SystemAdminMainController.class, ureq.getLocale(), getTranslator())); + + settingsForm = new VersionSettingsForm(ureq, getWindowControl()); + listenTo(settingsForm); - initForm(this.flc, this, ureq); + maintenanceForm = new VersionMaintenanceForm(ureq, this.getWindowControl()); + listenTo(maintenanceForm); - values[0] = getTranslator().translate("version.off"); - values[values.length - 1] = getTranslator().translate("version.unlimited"); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - // First add title and context help - setFormTitle("version.title"); - setFormDescription("version.intro"); - setFormContextHelp(VersionAdminController.class.getPackage().getName(), "version.html", "help.hover.version"); - - numOfVersions = uifactory.addDropdownSingleselect("version.numOfVersions", formLayout, keys, values, null); - Long maxNumber = getNumOfVersions(); - if(maxNumber == null) { - numOfVersions.select("0", true); - } else if (maxNumber.longValue() == -1l) { - numOfVersions.select("-1", true); - } else { - String str = maxNumber.toString(); - boolean found = false; - for(String value:values) { - if(value.equals(str)) { - found = true; - break; - } - } - - if(found) { - numOfVersions.select(str, true); - } else { - //set a default value if the saved number is not in the list, - //normally not possible but... - numOfVersions.select("10", true); - } - } + mainVC = createVelocityContainer("admin"); + mainVC.put("settings", settingsForm.getInitialComponent()); + mainVC.put("maintenance", maintenanceForm.getInitialComponent()); - final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator()); - formLayout.add(buttonLayout); - uifactory.addFormSubmitButton("save", buttonLayout); + putInitialPanel(mainVC); } @Override @@ -108,27 +71,10 @@ public class VersionAdminController extends FormBasicController { // } + @Override - protected void formOK(UserRequest ureq) { - String num = numOfVersions.getSelectedKey(); - if(num == null || num.length() == 0) return; - - try { - int maxNumber = Integer.parseInt(num); - setNumOfVersions(maxNumber); - getWindowControl().setInfo("saved"); - } catch (NumberFormatException e) { - showError("version.notANumber"); - } - } - - public Long getNumOfVersions() { - SimpleVersionConfig config = (SimpleVersionConfig) CoreSpringFactory.getBean(SimpleVersionConfig.class); - return config.getMaxNumberOfVersionsProperty(); - } - - public void setNumOfVersions(int maxNumber) { - SimpleVersionConfig config = (SimpleVersionConfig) CoreSpringFactory.getBean(SimpleVersionConfig.class); - config.setMaxNumberOfVersionsProperty(new Long(maxNumber)); + protected void event(UserRequest ureq, Component source, Event event) { + // } + } diff --git a/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java b/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java new file mode 100644 index 0000000000000000000000000000000000000000..dea6345680e003f6f2eb486bf70024f1a8b1ca36 --- /dev/null +++ b/src/main/java/org/olat/admin/version/VersionMaintenanceForm.java @@ -0,0 +1,145 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.admin.version; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.List; +import java.util.Locale; + +import org.olat.admin.SystemAdminMainController; +import org.olat.core.commons.taskExecutor.TaskExecutorManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.util.Util; +import org.olat.core.util.vfs.version.OrphanVersion; +import org.olat.core.util.vfs.version.VFSRevision; +import org.olat.core.util.vfs.version.VersionsManager; + +/** + * + * Description:<br> + * This is a controller to configure the SimpleVersionConfig, the configuration + * of the versioning system for briefcase. + * + * <P> + * Initial Date: 21 sept. 2009 <br> + * + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ + //fxdiff FXOLAT-127: file versions maintenance tool +public class VersionMaintenanceForm extends FormBasicController { + + private FormLink cleanUp, orphanSize; + private StaticTextElement orphanSizeEl; + private CloseableModalController cmc; + private OrphanVersionsController orphansController; + + public VersionMaintenanceForm(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + // use combined translator from system admin main + setTranslator(Util.createPackageTranslator(SystemAdminMainController.class, ureq.getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + // First add title and context help + setFormTitle("version.maintenance.title"); + setFormDescription("version.maintenance.intro"); + setFormContextHelp(VersionMaintenanceForm.class.getPackage().getName(), "maintenance.html", "help.hover.version"); + + orphanSizeEl = uifactory.addStaticTextElement("version.orphan.size", "version.orphan.size", "???", formLayout); + + FormLayoutContainer buttonsLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonsLayout); + + orphanSize = uifactory.addFormLink("version.orphan.size.calc", buttonsLayout, Link.BUTTON); + cleanUp = uifactory.addFormLink("version.clean.up", buttonsLayout, Link.BUTTON); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == cleanUp) { + List<OrphanVersion> orphans = VersionsManager.getInstance().orphans(); + orphansController = new OrphanVersionsController(ureq, getWindowControl(), orphans); + listenTo(orphansController); + cmc = new CloseableModalController(getWindowControl(), "close", orphansController.getInitialComponent()); + cmc.insertHeaderCss(); + cmc.activate(); + } else if (source == orphanSize) { + orphanSizeEl.setValue(translate("version.orphan.size.calculating")); + TaskExecutorManager.getInstance().runTask(new Runnable() { + public void run() { + calculateOrphanSize(); + } + }); + } + super.formInnerEvent(ureq, source, event); + } + + public final void calculateOrphanSize() { + long size = 0l; + List<OrphanVersion> orphans = VersionsManager.getInstance().orphans(); + for(OrphanVersion orphan:orphans) { + List<VFSRevision> revisions = orphan.getVersions().getRevisions(); + if(revisions != null) { + for(VFSRevision revision:revisions) { + size += revision.getSize(); + } + } + } + + String unit = "KB"; + double humanSize = size / 1024.0d; + if(humanSize > 1024) { + humanSize /= 1024; + unit = "MB"; + } + + DecimalFormat sizeFormat = new DecimalFormat("#0.#", new DecimalFormatSymbols(Locale.ENGLISH)); + String readableSize = sizeFormat.format(humanSize) + " " + unit; + if(orphanSizeEl != null && !isDisposed()) { + orphanSizeEl.setValue(readableSize); + } + } +} diff --git a/src/main/java/org/olat/admin/version/VersionSettingsForm.java b/src/main/java/org/olat/admin/version/VersionSettingsForm.java new file mode 100644 index 0000000000000000000000000000000000000000..3f486c24ca0fbd0bae16b93490b6ecd626260cae --- /dev/null +++ b/src/main/java/org/olat/admin/version/VersionSettingsForm.java @@ -0,0 +1,137 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.admin.version; + +import org.olat.admin.SystemAdminMainController; +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.Util; +import org.olat.core.util.vfs.version.SimpleVersionConfig; + +/** + * + * Description:<br> + * This is a controller to configure the SimpleVersionConfig, the configuration + * of the versioning system for briefcase. + * + * <P> + * Initial Date: 21 sept. 2009 <br> + * + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-127: file versions maintenance tool +public class VersionSettingsForm extends FormBasicController { + + private SingleSelection numOfVersions; + + private String[] keys = new String[] { + "0","2","3","4","5","10","25","50","-1" + }; + + private String[] values = new String[] { + "0","2","3","4","5","10","25","50","-1" + }; + + public VersionSettingsForm(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + // use combined translator from system admin main + setTranslator(Util.createPackageTranslator(SystemAdminMainController.class, ureq.getLocale(), getTranslator())); + + values[0] = getTranslator().translate("version.off"); + values[values.length - 1] = getTranslator().translate("version.unlimited"); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + // First add title and context help + setFormTitle("version.title"); + setFormDescription("version.intro"); + setFormContextHelp(VersionSettingsForm.class.getPackage().getName(), "version.html", "help.hover.version"); + + numOfVersions = uifactory.addDropdownSingleselect("version.numOfVersions", formLayout, keys, values, null); + numOfVersions.addActionListener(this, FormEvent.ONCHANGE); + Long maxNumber = getNumOfVersions(); + if(maxNumber == null) { + numOfVersions.select("0", true); + } else if (maxNumber.longValue() == -1l) { + numOfVersions.select("-1", true); + } else { + String str = maxNumber.toString(); + boolean found = false; + for(String value:values) { + if(value.equals(str)) { + found = true; + break; + } + } + + if(found) { + numOfVersions.select(str, true); + } else { + //set a default value if the saved number is not in the list, + //normally not possible but... + numOfVersions.select("10", true); + } + } + + final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator()); + formLayout.add(buttonLayout); + uifactory.addFormSubmitButton("save", buttonLayout); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + String num = numOfVersions.getSelectedKey(); + if(num == null || num.length() == 0) return; + + try { + int maxNumber = Integer.parseInt(num); + setNumOfVersions(maxNumber); + getWindowControl().setInfo("saved"); + } catch (NumberFormatException e) { + showError("version.notANumber"); + } + } + + public Long getNumOfVersions() { + SimpleVersionConfig config = (SimpleVersionConfig) CoreSpringFactory.getBean(SimpleVersionConfig.class); + return config.getMaxNumberOfVersionsProperty(); + } + + public void setNumOfVersions(int maxNumber) { + SimpleVersionConfig config = (SimpleVersionConfig) CoreSpringFactory.getBean(SimpleVersionConfig.class); + config.setMaxNumberOfVersionsProperty(new Long(maxNumber)); + } +} diff --git a/src/main/java/org/olat/admin/version/_chelp/maintenance.html b/src/main/java/org/olat/admin/version/_chelp/maintenance.html new file mode 100644 index 0000000000000000000000000000000000000000..0519cbdd08988280a2b4be90105487fb7595183e --- /dev/null +++ b/src/main/java/org/olat/admin/version/_chelp/maintenance.html @@ -0,0 +1,2 @@ + <br/> + $r.translate("chelp.version.maintenance") <br/> <br/> diff --git a/src/main/java/org/olat/admin/version/_content/admin.html b/src/main/java/org/olat/admin/version/_content/admin.html new file mode 100644 index 0000000000000000000000000000000000000000..e00d6feffa03de2d1f0fe3a8b8553125e7fe9a60 --- /dev/null +++ b/src/main/java/org/olat/admin/version/_content/admin.html @@ -0,0 +1,2 @@ +$r.render("settings") +$r.render("maintenance") \ No newline at end of file diff --git a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_de.properties index c755cd91d15322360e8edbbd45f4605a86d5ba14..8cd177d3ea9bbd5cb7a88c1b5105295ff90b770c 100644 --- a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_de.properties @@ -6,9 +6,21 @@ version.numOfVersions=Anzahl Versionen version.notANumber=Keine Zahl gewählt version.unlimited=Unlimitiert version.off=Versionierung ausgeschaltet +version.clean.up=Orphan Versionen löschen +version.orphan.size=Orphan Versionen Grösse +version.orphan.size.calc=Grösse rechnen +version.orphan.size.calculating=Grösse am rechnen... +version.maintenance.title=Verwaltung +version.maintenance.intro=Verwaltung von Versionen +table.header.file=Datei +table.header.versions=Anzhal Versionen +table.header.size=Grösse +table.empty=Keine orphan Versionen übrig help.hover.version=Hilfe zur Konfiguration der Dateiversionierung chelp.version.title=Dateiversionierung: Konfiguration chelp.version.intro=In diesem Formular können Sie die Dateiversionierung des Ordnermoduls ein- und ausschalten. Bei eingeschalteter Versionierung werden Dateien nicht überschrieben sondern als neue Version (auch Revision genannt) angelegt. Ältere Versionen eines Dokumentes können heruntergeladen und bei Bedarf wiederhergestellt werden. Werden Dateien gelöscht, so erscheinen Sie in der Liste der gelöschten Dateien und können wiederhergestellt werden. Ist die Versionierungsfunktion eingeschaltet, so können Dateien auch gesperrt werden, z.b. wenn eine Person an einem Dokument arbeitet und verhindern möchte, dass eine andere Person zwischenzeitlich eine neue Version erstellt. chelp.version.scope=Die Versionierung ist in allen Ordnern des Systems vorhanden: persönliche Ordner, Gruppenordner, Kursordner, Ressourcenordner und Kursbausteine 'Ordner'. chelp.version.enable=Um die Versionierung auf diesem System global einzuschalten, wählen Sie aus der Liste die Anzahl der jeweils erlaubten Versionen pro Datei aus oder wählen Sie unlimitiert, um beliebig viele Versionen eines Dokuments zuzulassen. Eine Beschränkung kann sinnvoll sein, um den benötigten Speicherplatz zu limitieren. chelp.version.disable=Um die Versionierung auf diesem System global auszuschalten wählen Sie den entsprechenden Eintrag in der Liste. +chelp.version.maintenance=Versionen von gelöschten Dateien werden aus WebDAV Kompatibilitätsgründen nicht automatisch gelöscht. Sie können diese manuell löschen. +chelp.maintenance.title=Dateiversionierung: Verwaltung \ No newline at end of file diff --git a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_en.properties index d02615e252ddc454e1a317fd9f5a0bc94bb4fd99..be83a12011de90e2536f21fa9598e7ed4b94eb6b 100644 --- a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_en.properties @@ -1,12 +1,24 @@ -#Sun Jan 23 12:33:34 CET 2011 +#Thu May 26 10:39:08 CEST 2011 chelp.version.disable=In order to deactivate versioning on this system globally please select the corresponding entry from the list. chelp.version.enable=In order to activate versioning on this system globally please select from the list the number of allowed versions per file or choose the option "Unlimited" to allow any number of versions for one document. Some restricting might be reasonable to limit the necessary storage space. chelp.version.intro=In this form you can activate or deactivate file versioning of the folder module. If versioning is active files will not be overwritten but saved as a new version (commonly known as revision). Older versions of a document can be downloaded or restored if necessary. Deleted files will appear in a corresponding list and can be restored as well. If the versioning feature is activated files can also be locked, e.g. in case someone editing a document wants to prevent others from creating a new version in the meantime. +chelp.version.maintenance=Versions of deleted files will not automatically deleted because of compatibility issues with WebDAV. You can delete them manually. chelp.version.scope=Versioning is available in all system folders\: personal folders, group folders, course folders, resource folders, as well as in the course elements 'Folder'. chelp.version.title=Configure file versioning +chelp.maintenance.title=Maintenance of file versioning form.version=Versioning help.hover.version=Help regarding the configuration of file versioning +table.empty=No orphan version left +table.header.file=File +table.header.versions=Number of versions +table.header.size=Size +version.clean.up=Delete orphan version +version.orphan.size=Orphan versions size +version.orphan.size.calc=Calculate size +version.orphan.size.calculating=Calculating... version.intro=Please set the maximum number of versions for a folder component file (group folder, course folder, etc.). +version.maintenance.intro=File versions management +version.maintenance.title=Management version.notANumber=No number selected version.numOfVersions=Number of versions version.off=Versioning deactivated diff --git a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_fr.properties index 237c9cc5647a52c896512f29798a6079d0822c11..7ec547c609f2fb3c1cbdd5e4754247a006aa66b4 100644 --- a/src/main/java/org/olat/admin/version/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/admin/version/_i18n/LocalStrings_fr.properties @@ -4,6 +4,7 @@ chelp.version.enable=Pour activer globalement sur ce syst\u00E8me la v\u00E9rifi chelp.version.intro=Dans ce formulaire vous pouvez activer ou d\u00E9sactiver la v\u00E9rification des versions du module du dossier. Si la v\u00E9rification est activ\u00E9e, les fichiers ne sont pas \u00E9cras\u00E9s mais enregistr\u00E9s comme nouvelle version (appel\u00E9 aussi r\u00E9vision). D'anciennes versions du document peuvent \u00EAtre t\u00E9l\u00E9charg\u00E9es et restitu\u00E9es si besoin. Si des fichiers sont effac\u00E9s, ils apparaissent dans la liste des fichiers effac\u00E9s et peuvent \u00EAtre restitu\u00E9s. Si la fonction de v\u00E9rification est activ\u00E9e, les fichiers peuvent \u00EAtre bloqu\u00E9s, par exemple si une personne \u00E9dite un document et veut emp\u00EAcher qu'une autre personne cr\u00E9e un nouvelle version. chelp.version.scope=La v\u00E9rification des versions est disponible dans tous les dossiers du syst\u00E8me\: dossier personnel, dossier de groupe, dossier de cours et \u00E9l\u00E9ments de cours 'Dossier' chelp.version.title=Configurer la v\u00E9rification des versions +chelp.maintenance.title=Maintenance des versions de fichiers form.version=V\u00E9rification des version help.hover.version=Aide \u00E0 la configuration de la v\u00E9rification des versions des fichiers version.intro=Etablir le nombre maximal de versions pour un fichier d'un dossier (dossier de groupe, dossier de cours etc.). diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java index 7ba157ac548d4f98138a38e8606fec179aff652e..63b3f9b3eb8d2b71294abd279d3a7b7f7b51f881 100644 --- a/src/main/java/org/olat/collaboration/CollaborationTools.java +++ b/src/main/java/org/olat/collaboration/CollaborationTools.java @@ -21,17 +21,15 @@ package org.olat.collaboration; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; + import org.olat.admin.quota.QuotaConstants; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; @@ -189,6 +187,15 @@ public class CollaborationTools implements Serializable { * Owners and members have write access to the calendar. */ public static final int CALENDAR_ACCESS_ALL = 1; + /** + * Only owners have write access to the folder. + */ + //fxdiff VCRP-8: collaboration tools folder access control + public static final int FOLDER_ACCESS_OWNERS = 0; + /** + * Owners and members have write access to the folder. + */ + public static final int FOLDER_ACCESS_ALL = 1; /** @@ -196,6 +203,8 @@ public class CollaborationTools implements Serializable { */ private final static String KEY_NEWS = "news"; private final static String KEY_CALENDAR_ACCESS = "cal"; + //fxdiff VCRP-8: collaboration tools folder access control + private final static String KEY_FOLDER_ACCESS = "folder"; //o_clusterOK by guido Hashtable<String, Boolean> cacheToolStates; @@ -352,15 +361,31 @@ public class CollaborationTools implements Serializable { * @return Copnfigured FolderRunController */ public FolderRunController createFolderController(UserRequest ureq, WindowControl wControl, - final SubscriptionContext subsContext) { + BusinessGroup businessGroup, boolean isAdmin, final SubscriptionContext subsContext) { // do not use a global translator since in the fututre a collaborationtools // may be shared among users Translator trans = Util.createPackageTranslator(this.getClass(), ureq.getLocale()); String relPath = getFolderRelPath(); OlatRootFolderImpl rootContainer = new OlatRootFolderImpl(relPath, null); OlatNamedContainerImpl namedContainer = new OlatNamedContainerImpl(trans.translate("folder"), rootContainer); - namedContainer.setLocalSecurityCallback(new CollabSecCallback(relPath, subsContext)); - FolderRunController frc = new FolderRunController(namedContainer, true, true, ureq, wControl); + + //fxdiff VCRP-8: collaboration tools folder access control + boolean writeAccess; + boolean isOwner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(ureq.getIdentity(), businessGroup.getOwnerGroup()); + if (!(isAdmin || isOwner)) { + // check if participants have read/write access + int folderAccess = CollaborationTools.FOLDER_ACCESS_ALL; + Long lFolderAccess = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup).lookupFolderAccess(); + if (lFolderAccess != null) { + folderAccess = lFolderAccess.intValue(); + } + writeAccess = (folderAccess == CollaborationTools.CALENDAR_ACCESS_ALL); + } else { + writeAccess = true; + } + + namedContainer.setLocalSecurityCallback(new CollabSecCallback(writeAccess, relPath, subsContext)); + FolderRunController frc = new FolderRunController(namedContainer, true, true, true, ureq, wControl); return frc; } @@ -725,14 +750,41 @@ public class CollaborationTools implements Serializable { } } + //fxdiff VCRP-8: collaboration tools folder access control + public Long lookupFolderAccess() { + NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores); + Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS); + if (property == null) { // no entry + return null; + } + // read the long value of the existing property + return property.getLongValue(); + } + + //fxdiff VCRP-8: collaboration tools folder access control + public void saveFolderAccess(Long folderrAccess) { + NarrowedPropertyManager npm = NarrowedPropertyManager.getInstance(ores); + Property property = npm.findProperty(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS); + if (property == null) { // create a new one + Property nP = npm.createPropertyInstance(null, null, PROP_CAT_BG_COLLABTOOLS, KEY_FOLDER_ACCESS, null, folderrAccess, null, null); + npm.saveProperty(nP); + } else { // modify the existing one + property.setLongValue(folderrAccess); + npm.updateProperty(property); + } + } + public class CollabSecCallback implements VFSSecurityCallback { - + + //fxdiff VCRP-8: collaboration tools folder access control + private final boolean write; private Quota folderQuota = null; private SubscriptionContext subsContext; - public CollabSecCallback(String relPath, SubscriptionContext subsContext) { + public CollabSecCallback(boolean write, String relPath, SubscriptionContext subsContext) { this.subsContext = subsContext; initFolderQuota(relPath); + this.write = write; } private void initFolderQuota(String relPath) { @@ -749,11 +801,11 @@ public class CollaborationTools implements Serializable { } public boolean canWrite() { - return true; + return write; } public boolean canDelete() { - return true; + return write; } public boolean canList() { @@ -765,7 +817,7 @@ public class CollaborationTools implements Serializable { } public boolean canDeleteRevisionsPermanently() { - return true; + return write; } public Quota getQuota() { diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java index 5dfcb4f7180bf112bee62a06e5a6183151911f82..bf3c7420d1d308ef57d725ce50aa605940e5e5cb 100644 --- a/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java +++ b/src/main/java/org/olat/collaboration/CollaborationToolsSettingsController.java @@ -60,6 +60,7 @@ public class CollaborationToolsSettingsController extends BasicController { private ChoiceOfToolsForm cots; private NewsFormController newsController; private CalendarForm calendarForm; + private FolderForm folderForm; boolean lastCalendarEnabledState; private Controller quotaCtr; @@ -107,13 +108,25 @@ public class CollaborationToolsSettingsController extends BasicController { vc_collabtools.put("calendarform", calendarForm.getInitialComponent()); } else { lastCalendarEnabledState = false; - vc_collabtools.contextPut("folderToolEnabled", Boolean.FALSE); + vc_collabtools.contextPut("calendarToolEnabled", Boolean.FALSE); } // update quota form: only show when enabled - if (collabTools.isToolEnabled(CollaborationTools.TOOL_FOLDER) && ureq.getUserSession().getRoles().isOLATAdmin()) { + if (collabTools.isToolEnabled(CollaborationTools.TOOL_FOLDER)) { + vc_collabtools.contextPut("folderToolEnabled", Boolean.TRUE); + //fxdiff VCRP-8: collaboration tools folder access control + if(ureq.getUserSession().getRoles().isOLATAdmin()) { + vc_collabtools.put("quota", quotaCtr.getInitialComponent()); + } vc_collabtools.contextPut("folderToolEnabled", Boolean.TRUE); - vc_collabtools.put("quota", quotaCtr.getInitialComponent()); + if(folderForm != null) { + removeAsListenerAndDispose(folderForm); + } + Long lFolderAccess = collabTools.lookupFolderAccess(); + int access = lFolderAccess == null ? CollaborationTools.FOLDER_ACCESS_ALL : lFolderAccess.intValue(); + folderForm = new FolderForm(ureq, getWindowControl(), access); + listenTo(folderForm); + vc_collabtools.put("folderform", folderForm.getInitialComponent()); } else { vc_collabtools.contextPut("folderToolEnabled", Boolean.FALSE); } @@ -191,6 +204,15 @@ public class CollaborationToolsSettingsController extends BasicController { // update quota form: only show when enabled if (collabTools.isToolEnabled(CollaborationTools.TOOL_FOLDER)) { vc_collabtools.contextPut("folderToolEnabled", Boolean.TRUE); + //fxdiff VCRP-8: collaboration tools folder access control + if(folderForm != null) { + removeAsListenerAndDispose(folderForm); + } + Long lFolderAccess = collabTools.lookupFolderAccess(); + int access = lFolderAccess == null ? CollaborationTools.FOLDER_ACCESS_ALL : lFolderAccess.intValue(); + folderForm = new FolderForm(ureq, getWindowControl(), access); + listenTo(folderForm); + vc_collabtools.put("folderform", folderForm.getInitialComponent()); if (ureq.getUserSession().getRoles().isOLATAdmin()) { vc_collabtools.put("quota", quotaCtr.getInitialComponent()); } @@ -210,7 +232,10 @@ public class CollaborationToolsSettingsController extends BasicController { CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf( new KalendarModifiedEvent(), OresHelper.lookupType(CalendarManager.class) ); - } + //fxdiff VCRP-8: collaboration tools folder access control + } else if (source == folderForm) { + collabTools.saveFolderAccess(new Long(folderForm.getFolderAccess())); + } } @@ -285,6 +310,53 @@ class ChoiceOfToolsForm extends FormBasicController { } } +//fxdiff VCRP-8: collaboration tools folder access control +class FolderForm extends FormBasicController { + + private SingleSelection folderAccessEl; + private int folderAccess; + + public FolderForm(UserRequest ureq, WindowControl wControl, int folderAccess) { + super(ureq, wControl); + this.folderAccess = folderAccess; + initForm(ureq); + } + + public int getFolderAccess() { + if (folderAccessEl.isOneSelected() && folderAccessEl.getSelectedKey().equals("all")){ + return CollaborationTools.FOLDER_ACCESS_ALL; + } else { + return CollaborationTools.FOLDER_ACCESS_OWNERS; + } + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("folder.access.title"); + + String[] keys = new String[] { "owner", "all" }; + String values[] = new String[] { + translate("folder.access.owners"), + translate("folder.access.all") + }; + folderAccessEl = uifactory.addRadiosVertical("folder.access", "folder.access", formLayout, keys, values); + String selectedKey = (folderAccess == CollaborationTools.FOLDER_ACCESS_ALL) ? "all" : "owner"; + folderAccessEl.select(selectedKey, true); + + uifactory.addFormSubmitButton("submit", formLayout); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent(ureq, Event.DONE_EVENT); + } +} + class CalendarForm extends FormBasicController { private SingleSelection access; diff --git a/src/main/java/org/olat/collaboration/_content/collaborationtools.html b/src/main/java/org/olat/collaboration/_content/collaborationtools.html index 1628cf7f78e94dc51b8e858aeee24f84c73b2efb..37a96f8def16ab8795ee28482a8925027041434e 100644 --- a/src/main/java/org/olat/collaboration/_content/collaborationtools.html +++ b/src/main/java/org/olat/collaboration/_content/collaborationtools.html @@ -5,6 +5,9 @@ $r.render("choiceOfTools") #if ($calendarToolEnabled) $r.render("calendarform") #end -#if ($folderToolEnabled && $isOlatAdmin) - $r.render("quota") +#if ($folderToolEnabled) + $r.render("folderform") + #if ($isOlatAdmin) + $r.render("quota") + #end #end \ No newline at end of file diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties index b1884624e2b68372ac62a25b65d7438d8269f55a..4d0c86678dd776f114d041ba0ffe5cc17ebb74ce 100644 --- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties @@ -12,5 +12,9 @@ collabtools.named.hasNews=Information an Mitglieder collabtools.named.hasWiki=Wiki collabtools.named.hasPortfolio=ePortfolio folder=Ordner +folder.access=Ordner Schreibberechtigung +folder.access.title=Ordner Schreibberechtigung konfigurieren +folder.access.all=Alle Mitglieder +folder.access.owners=Besitzer bzw. Betreuer news.content=Information an Mitglieder selection=Auswahl diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties index 93a6c0993e517b8b5548ba6c7f6dc4c4078cf7de..285ddb44bb13480af41fe4429c8acea937fac1f3 100644 --- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Wed Jan 19 19:21:33 CET 2011 +#Mon May 16 17:33:28 CEST 2011 calendar.access=Calendar write permission calendar.access.all=All members calendar.access.owners=Owners and tutors respectively @@ -12,5 +12,9 @@ collabtools.named.hasNews=Information for members collabtools.named.hasPortfolio=ePortfolio collabtools.named.hasWiki=Wiki folder=Folder +folder.access=Folder write permission +folder.access.all=All members +folder.access.owners=Owners resp. coaches +folder.access.title=Configure folder write permission news.content=Information for members selection=Selection diff --git a/src/main/java/org/olat/commons/calendar/CalendarManager.java b/src/main/java/org/olat/commons/calendar/CalendarManager.java index 16c56aa8f6426a0b6467431f9440c4ed257060f8..ed2136569dc7b5b703457c84572334f9afef3478 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/CalendarManager.java @@ -216,6 +216,14 @@ public interface CalendarManager extends UserDataDeletable { * @return true if success */ public boolean updateEventAlreadyInSync(final Kalendar cal, final KalendarEvent kalendarEvent); + + /** + * Update a calendar with the events from an other calendar + * @param cal + * @param importedCal + * @return true if success + */ + public boolean updateCalendar(final Kalendar cal, final Kalendar importedCal); /** * Get a calendar by type and id. diff --git a/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java b/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java index 34b97e1c561a2f3fdb2284cd66f6119b50bf292b..b5a4c42b138d14cdd2cc236756479154565ca72b 100644 --- a/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/ICalFileCalendarManager.java @@ -1,4 +1,4 @@ -/** + /** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> @@ -35,8 +35,10 @@ import java.io.StringReader; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.StringTokenizer; import net.fortuna.ical4j.data.CalendarBuilder; @@ -63,6 +65,7 @@ import net.fortuna.ical4j.model.property.LastModified; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.ProdId; import net.fortuna.ical4j.model.property.RRule; +import net.fortuna.ical4j.model.property.Summary; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Version; import net.fortuna.ical4j.model.property.XProperty; @@ -83,7 +86,9 @@ import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.manager.BasicManager; +import org.olat.core.util.CodeHelper; import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.cache.n.CacheWrapper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.SyncerCallback; @@ -464,7 +469,10 @@ public class ICalFileCalendarManager extends BasicManager implements CalendarMan */ private KalendarEvent getKalendarEvent(VEvent event) { // subject - String subject = event.getSummary().getValue(); + Summary eventsummary = event.getSummary(); + String subject = ""; + if (eventsummary != null) + subject = eventsummary.getValue(); // start Date start = event.getStartDate().getDate(); Duration dur = event.getDuration(); @@ -486,7 +494,14 @@ public class ICalFileCalendarManager extends BasicManager implements CalendarMan end = new Date(end.getTime() - (1000 * 60 * 60 * 24)); } - KalendarEvent calEvent = new KalendarEvent(event.getUid().getValue(), subject, start, end); + // fxdiff: + Uid eventuid = event.getUid(); + String uid; + if (eventuid != null) + uid = eventuid.getValue(); + else + uid = CodeHelper.getGlobalForeverUniqueID(); + KalendarEvent calEvent = new KalendarEvent(uid, subject, start, end); calEvent.setAllDayEvent(isAllDay); // classification @@ -530,7 +545,7 @@ public class ICalFileCalendarManager extends BasicManager implements CalendarMan if (linkProperty != null) { String encodedLink = linkProperty.getValue(); StringTokenizer st = new StringTokenizer(encodedLink, "§", false); - if (st.countTokens() == 4) { + if (st.countTokens() >= 4) { String provider = st.nextToken(); String id = st.nextToken(); String displayName = st.nextToken(); @@ -836,7 +851,38 @@ public class ICalFileCalendarManager extends BasicManager implements CalendarMan // inform all controller about calendar change for reload CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); return successfullyPersist; - } + } + + public boolean updateCalendar(final Kalendar cal, final Kalendar importedCal) { + OLATResourceable calOres = getOresHelperFor(cal); + Boolean updatedSuccessful = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync( calOres, new SyncerCallback<Boolean>() { + public Boolean execute() { + Map<String,KalendarEvent> uidToEvent = new HashMap<String,KalendarEvent>(); + for(KalendarEvent event:cal.getEvents()) { + if(StringHelper.containsNonWhitespace(event.getID())) { + uidToEvent.put(event.getID(), event); + } + } + + Kalendar loadedCal = getCalendarFromCache(cal.getType(), cal.getCalendarID()); + for(KalendarEvent importedEvent:importedCal.getEvents()) { + String uid = importedEvent.getID(); + if(uidToEvent.containsKey(uid)) { + loadedCal.removeEvent(importedEvent); // remove old event + loadedCal.addEvent(importedEvent); // add changed event + } else { + loadedCal.addEvent(importedEvent); + } + } + + boolean successfullyPersist = persistCalendar(cal); + // inform all controller about calendar change for reload + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new KalendarModifiedEvent(cal), OresHelper.lookupType(CalendarManager.class)); + return new Boolean(successfullyPersist); + } + }); + return updatedSuccessful.booleanValue(); + } /** * Load a calendar when a calendar exists or create a new one. diff --git a/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java b/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java new file mode 100644 index 0000000000000000000000000000000000000000..d64f59756bded0a18437ddf6ac96cab869874957 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ImportCalendarJob.java @@ -0,0 +1,49 @@ +/** + * 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. + * <p> + */ +package org.olat.commons.calendar; + +import org.olat.core.commons.scheduler.JobWithDB; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.quartz.JobExecutionContext; + +/** + * + * <h3>Description:</h3> + * <p> + * <p> + * Initial Date: 21 feb. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class ImportCalendarJob extends JobWithDB { + + private static final OLog log = Tracing.createLoggerFor(ImportCalendarJob.class); + + @Override + public void executeWithDB(JobExecutionContext context) { + try { + ImportCalendarManager.updateCalendarIn(); + } catch (Exception e) { + log.error("", e); + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java b/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java index fdce13e8a90154eb5d905898e94f066c1c1290c0..bedd454f48df811c784ceb627b617b31a7314a45 100644 --- a/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java +++ b/src/main/java/org/olat/commons/calendar/ImportCalendarManager.java @@ -31,17 +31,23 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.StringTokenizer; import java.util.regex.Pattern; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarComparator; import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.commons.persistence.DBQuery; import org.olat.core.gui.UserRequest; import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.manager.BasicManager; +import org.olat.core.util.StringHelper; +import org.olat.core.util.resource.OresHelper; import org.olat.properties.Property; import org.olat.properties.PropertyManager; @@ -59,6 +65,99 @@ public class ImportCalendarManager extends BasicManager { private static final OLog log = Tracing.createLoggerFor(ImportCalendarManager.class); public static String PROP_CATEGORY = "Imported-Calendar"; + public static String PROP_CATEGORY_IMP = "Imported-Calendar-To"; + + + public static boolean importCalendarIn(Kalendar cal, String importUrl) { + try { + String calendarContent = getContentFromUrl(importUrl); + CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + Kalendar importedCal = calManager.buildKalendarFrom(calendarContent, cal.getType(), cal.getCalendarID()); + boolean imported = calManager.updateCalendar(cal, importedCal); + if(imported) { + PropertyManager pm = PropertyManager.getInstance(); + List<Property> p = pm.findProperties(null, null, cal.getType(), 0l, PROP_CATEGORY_IMP, cal.getCalendarID()); + if(p.isEmpty()) { + long timestamp = System.currentTimeMillis(); + OLATResourceable ores = OresHelper.createOLATResourceableInstance(cal.getType(), 0l); + Property prop = pm.createPropertyInstance(null, null, ores, PROP_CATEGORY_IMP, cal.getCalendarID(), null, timestamp, null, importUrl); + pm.saveProperty(prop); + } else { + Property prop = p.get(0); + String textVal = prop.getTextValue(); + if(!textVal.contains(importUrl)) { + prop.setTextValue(textVal + "|" + importUrl); + } + } + } + return imported; + } catch (Exception e) { + log.error("", e); + return false; + } + } + + /** + * Method used by the cron job + * @return + */ + public static boolean updateCalendarIn() { + List<Property> properties = loadPropertiesByImportCategory(); + log.audit("Begin to update " + properties.size() + " calendars."); + + int count = 0; + for(Property property:properties) { + String type = property.getResourceTypeName(); + String id = property.getName(); + String importUrls = property.getTextValue(); + if(!StringHelper.containsNonWhitespace(importUrls) || !StringHelper.containsNonWhitespace(type) || !StringHelper.containsNonWhitespace(id)) { + continue; + } + + for(StringTokenizer tokenizer = new StringTokenizer(importUrls, "|"); tokenizer.hasMoreTokens(); ) { + String importUrl = tokenizer.nextToken(); + CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + Kalendar cal = calManager.getCalendar(type + "dru", id); + if(importCalendarIn(cal, importUrl)) { + log.audit("Updated successfully calendar: " + type + " / " + id); + } else { + log.audit("Failed to update calendar: " + type + " / " + id); + } + + if(count++ % 20 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + } + } + return false; + } + + private static List<Property> loadPropertiesByImportCategory() { + StringBuilder sb = new StringBuilder(); + sb.append("select p from org.olat.properties.Property as p where ") + .append(" p.category = :category") + .append(" and p.resourceTypeId = :restypeid"); + + DBQuery query = DBFactory.getInstance().createQuery(sb.toString()); + query.setString("category", PROP_CATEGORY_IMP); + query.setLong("restypeid", new Long(0)); + + List<Property> properties = query.list(); + return properties; + } + + public static boolean importCalendarIn(KalendarRenderWrapper calenderWrapper, InputStream in) { + try { + String calendarContent = getContentFromStream(in); + Kalendar cal = calenderWrapper.getKalendar(); + CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + Kalendar importedCal = calManager.buildKalendarFrom(calendarContent, cal.getType(), cal.getCalendarID()); + return calManager.updateCalendar(cal, importedCal); + } catch (Exception e) { + log.error("", e); + return false; + } + } /** * Save the imported calendar @@ -113,19 +212,19 @@ public class ImportCalendarManager extends BasicManager { * @param ureq * @return */ - public static List getImportedCalendarsForIdentity(UserRequest ureq) { + public static List<KalendarRenderWrapper> getImportedCalendarsForIdentity(UserRequest ureq) { // initialize the calendars list - List calendars = new ArrayList(); + List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>(); // read all the entries from the database PropertyManager pm = PropertyManager.getInstance(); - List properties = pm.listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); + List<Property> properties = pm.listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); // return the list of calendar objects - Iterator propertyIter = properties.iterator(); + Iterator<Property> propertyIter = properties.iterator(); CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); while (propertyIter.hasNext()) { - Property calendarProperty = (Property)propertyIter.next(); + Property calendarProperty = propertyIter.next(); String calendarName = calendarProperty.getName(); KalendarRenderWrapper calendarWrapper = calManager.getImportedCalendar(ureq.getIdentity(), calendarName); calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY); @@ -148,12 +247,11 @@ public class ImportCalendarManager extends BasicManager { */ public static void reloadUrlImportedCalendars(UserRequest ureq) { // read all the entries from the database - List properties = PropertyManager.getInstance().listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); + List<Property> properties = PropertyManager.getInstance().listProperties(ureq.getIdentity(), null, null, PROP_CATEGORY, null); // return the list of calendar objects - Iterator propertyIter = properties.iterator(); - CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager(); + Iterator<Property> propertyIter = properties.iterator(); while (propertyIter.hasNext()) { - Property calendarProperty = (Property)propertyIter.next(); + Property calendarProperty = propertyIter.next(); String calendarName = calendarProperty.getName(); String calendarUrl = calendarProperty.getStringValue(); long timestampLastupdate = calendarProperty.getLongValue(); @@ -222,7 +320,7 @@ public class ImportCalendarManager extends BasicManager { } private static String getImportedCalendarType() { - return CalendarManagerFactory.getInstance().getCalendarManager().TYPE_USER; + return CalendarManager.TYPE_USER; } @@ -236,15 +334,18 @@ public class ImportCalendarManager extends BasicManager { } public static String getContentFromUrl(String url) throws IOException { - InputStream in=(new URL(url)).openStream(); - BufferedReader dis = new BufferedReader(new InputStreamReader(in)); - StringBuffer fBuf = new StringBuffer() ; - String line; - while ( (line = dis.readLine()) != null) { - fBuf.append (line + "\n"); - } - in.close (); + InputStream in = new URL(url).openStream(); + return getContentFromStream(in); + } + + public static String getContentFromStream(InputStream in) throws IOException { + BufferedReader dis = new BufferedReader(new InputStreamReader(in)); + StringBuffer fBuf = new StringBuffer() ; + String line; + while ( (line = dis.readLine()) != null) { + fBuf.append (line + "\n"); + } + in.close (); return fBuf.toString(); } - } diff --git a/src/main/java/org/olat/commons/calendar/_content/calConfig.html b/src/main/java/org/olat/commons/calendar/_content/calConfig.html index 098acabfb1ea25747466fe8bbd28e92efb7b2e29..235899eb2144c8fbe6a6a02207254d414d26d7b7 100644 --- a/src/main/java/org/olat/commons/calendar/_content/calConfig.html +++ b/src/main/java/org/olat/commons/calendar/_content/calConfig.html @@ -45,6 +45,11 @@ $r.contextHelpWithWrapper("org.olat.commons.calendar","personal-calendar-callist href="$r.commandURIbg("add", "id", "$calendarWrapper.getKalendar().getCalendarID()")" $r.bgTarget() title="$r.translateInAttribute("cal.add.event")"></a> + <a class="o_cal_config_importevent" + href="$r.commandURIbg("import", "id", "$calendarWrapper.getKalendar().getCalendarID()")" + $r.bgTarget() + title="$r.translateInAttribute("cal.import.event")"></a> + #else <div class="o_cal_config_readonly" title="$r.translateInAttribute("cal.add.readonly")"></div> diff --git a/src/main/java/org/olat/commons/calendar/_content/calExternalLinks.html b/src/main/java/org/olat/commons/calendar/_content/calExternalLinks.html new file mode 100644 index 0000000000000000000000000000000000000000..80c3219b35ab0f87485e33c4907f512190f25513 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/_content/calExternalLinks.html @@ -0,0 +1,20 @@ +<table> + <thead> + <tr> + <th>$r.translate("tab.links.extern.url")</th> + <th>$r.translate("tab.links.extern.name")</th> + <th></th> + <th></th> + </tr> + </thead> + <tbody> + #foreach($link in $links) + <tr> + <td>$r.render("url_$link.id") #if($r.available("url_${link.id}_ERROR")) $r.render("url_${link.id}_ERROR") #end</td> + <td>$r.render("displayName_$link.id") #if($r.available("displayName_${link.id}_ERROR")) $r.render("displayName_${link.id}_ERROR") #end</td> + <td>$r.render("add_$link.id")</td> + <td>$r.render("del_$link.id")</td> + </tr> + #end + </tbody> +</table> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/calExternalMedias.html b/src/main/java/org/olat/commons/calendar/_content/calExternalMedias.html new file mode 100644 index 0000000000000000000000000000000000000000..087b8405becee54eb76b3b33be409a0ee959aecf --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/_content/calExternalMedias.html @@ -0,0 +1,22 @@ +<table> + <thead> + <tr> + <th>$r.translate("tab.links.extern.url")</th> + <th></th> + <th>$r.translate("tab.links.extern.name")</th> + <th></th> + <th></th> + </tr> + </thead> + <tbody> + #foreach($link in $links) + <tr> + <td>$r.render("url_$link.id") #if($r.available("url_${link.id}_ERROR")) $r.render("url_${link.id}_ERROR") #end</td> + <td>$r.render("media_$link.id")</td> + <td>$r.render("displayName_$link.id") #if($r.available("displayName_${link.id}_ERROR")) $r.render("displayName_${link.id}_ERROR") #end</td> + <td>$r.render("add_$link.id")</td> + <td>$r.render("del_$link.id")</td> + </tr> + #end + </tbody> +</table> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_content/importEvents.html b/src/main/java/org/olat/commons/calendar/_content/importEvents.html new file mode 100644 index 0000000000000000000000000000000000000000..56d5e6cf150c4e72a56e50a19e95c92addf19fe9 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/_content/importEvents.html @@ -0,0 +1,4 @@ +$r.render("chooseContainer") +$r.render("urlContainer") +$r.render("fileContainer") +$r.render("buttonGroupLayout") \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties index 5eed1406a897477deedd5c172a8d7c62a263305c..563179f03022dbd16fd1166295364c54df48536e 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_de.properties @@ -1,4 +1,5 @@ cal.add.event=Termin hinzuf\u00FCgen +cal.import.event=Termine importieren cal.add.readonly=(nur lesend) cal.color.choose=Farbe w\u00E4hlen cal.color.title=Farbe w\u00E4hlen @@ -144,7 +145,14 @@ help.hover.groupcal=Hilfe zur Verwendung des Gruppenkalenders tab.event=Termindetails tab.links=Verkn\u00FCpfung +tab.links.extern=Externe Dokumenten +tab.links.extern.new=Neue externe Dokument +tab.links.extern.url=URL +tab.links.extern.url.invalid=Die angegebene URL ist ung\u00FCltig. +tab.links.extern.name=Name uncheckall=Auswahl l\u00F6schen +table.add=+ +table.delete=- cal.icalfeed.title = iCal Feed-Link cal.icalfeed.info = Bitte benutzen Sie die folgende URL, um diesen Kalender von anderen Anwendungen aus aufzurufen. Sie können die URL kopieren und in jede andere Kalenderanwendung einfügen, welche das iCal-Format unterstützt. @@ -180,7 +188,10 @@ cal.import.remove.title = L cal.import.remove.confirmation_message = Wollen Sie den importierten Kalender entfernen? cal.import.remove.info = Kalender wurde entfernt cal.import.type.file = Datei importieren +cal.import.type.file.desc = Kalendar von einer Datei importieren cal.import.type.url = Importieren via URL +cal.import.type.url.desc = Kalendar via URL importieren +cal.import.type.url.error = Kalendar via URL importieren cal.import.url.prompt = Öffentliche Kalender-Adresse (URL im iCal-Format) cal.import.url.empty.error = Die URL des Kalenders darf nicht leer sein. cal.import.url.invalid = Der Download ist fehlgeschlagen. Bitte überprüfen Sie die URL. diff --git a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties index 77f77bd8ec564a62db23a301ab8d51b2358aaeae..2025310b081555f979bf5ac5cab0854d55e64621 100644 --- a/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/commons/calendar/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Sun Jan 23 12:51:00 CET 2011 +#Thu May 26 10:58:05 CEST 2011 cal.add.event=Add event cal.add.readonly=(Read only) cal.color.choose=Choose color @@ -74,6 +74,7 @@ cal.import.calname.exists.error=A calendar with that name already exists cal.import.calname.prompt=Please provide a name for this imported calendar cal.import.calname.submit=Import cal.import.calname.title=Import calendar +cal.import.event=Import calendar cal.import.form.failed=Transmission of this file failed cal.import.form.format.error=This file format cannot be uploaded as calendar cal.import.form.limit.error=Calendar files must not exceed {0} bytes @@ -88,7 +89,10 @@ cal.import.remove.title=Confirm removal cal.import.success=Import sucessful cal.import.title=Import calendar cal.import.type.file=Import file +cal.import.type.file.desc=Import a calendar form a file cal.import.type.url=Import from URL +cal.import.type.url.desc=Import a calendar from an URL +cal.import.type.url.error=Import calendar from an URL cal.import.url.content.invalid=Calendar file format incorrect. Please check the URL cal.import.url.empty.error=URL of calendar cannot be left blank. cal.import.url.file.write.error=Data could not be written to that calendar file. Please try again later. @@ -180,5 +184,12 @@ help.hover.personalcal=Help on how to use personal calendars help.hover.personalcal.date=Help to create events in personal calendars tab.event=Event details tab.links=Link +tab.links.extern=External documents +tab.links.extern.name=Name +tab.links.extern.new=New external document +tab.links.extern.url=URL +tab.links.extern.url.invalid=This URL is invalid +table.add=+ +table.delete=- ul.select=Select file uncheckall=Delete selection diff --git a/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml b/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml index 53e4a1a9b93aa685fa447000bd37389b91e82cdb..a38cbdce2ba1450e5d4cd44018c0c8227aa2ca3a 100644 --- a/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml +++ b/src/main/java/org/olat/commons/calendar/_spring/calendarContext.xml @@ -1,9 +1,33 @@ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + <bean id="calendarImportTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> + <property name="jobDetail" ref="calendarImportJob" /> + <!-- adjust cron style syntax for your needs + A "Cron-Expression" is a string comprised of 6 or 7 fields separated by white space. The 6 mandatory and 1 optional fields are as follows: + Field Name Allowed Values Allowed Special Characters + Seconds 0-59 , - * / + Minutes 0-59 , - * / + Hours 0-23 , - * / + Day-of-month 1-31 , - * ? / L W C + Month 1-12 or JAN-DEC , - * / + Day-of-Week 1-7 or SUN-SAT , - * ? / L C # + Year (Optional) empty, 1970-2099 , - * / + As of OLAT 6.3 it's best to let the cronjob run every two hours since users can now choose how often + they will get notified. The shortest interval is set to two hours. + --> + <property name="cronExpression" value="0 22 */4 * * ?" /> + + <!-- OLAT-5093 start delay ensures there's no conflict with server startup and db not being ready yet --> + <property name="startDelay" value="40000" /> + </bean> <bean id="calendarModule" class="org.olat.commons.calendar.CalendarModule" init-method="init"> <property name="persistedProperties"> @@ -14,5 +38,8 @@ </bean> </property> </bean> - + + <bean id="calendarImportJob" class="org.springframework.scheduling.quartz.JobDetailBean"> + <property name="jobClass" value="org.olat.commons.calendar.ImportCalendarJob" /> + </bean> </beans> \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/_static/css/calendar.css b/src/main/java/org/olat/commons/calendar/_static/css/calendar.css index a917a1aaf526d26f7067b36e53a73e13c49f3b31..b6aab21950c0fbe14788501f68de658163dfec07 100644 --- a/src/main/java/org/olat/commons/calendar/_static/css/calendar.css +++ b/src/main/java/org/olat/commons/calendar/_static/css/calendar.css @@ -365,6 +365,12 @@ a.o_cal_config_remove_cal:hover { width: 20px; height: 16px; background: url(calendar_add.png) no-repeat 0 0; float: left; display: inline; +} + .o_cal_config_importevent { + position: relative; + width: 20px; height: 16px; + background: url(calendar_import.png) no-repeat 0 0; + float: left; display: inline; } .o_cal_config_readonly { position: relative; @@ -376,6 +382,10 @@ a.o_cal_config_addevent:hover { width: 20px; height: 16px; background: url(calendar_add_over.png) no-repeat 0 0; } +a.o_cal_config_importevent:hover { + width: 20px; height: 16px; + background: url(calendar_import_over.png) no-repeat 0 0; +} /* colorchooser */ #o_cal_colorchooser { @@ -465,10 +475,11 @@ div.o_cal_links {} .o_cal_wv_print ul { list-style-type: none; margin-left: 0; } .o_cal_event { clear:left; margin: 0.2em 0; } .o_cal_wv_list * { float: left; width: 100%; } /* float all elements in order to fully include floating children */ - .o_cal_event span { padding: 0 0.4em; display: block; } + .o_cal_event span { padding: 0 0.4em; display:block; } .o_cal_date { font-weight: bold; } .o_cal_time { width: 25%; } .o_cal_subject { width: 40%; border-left-style: solid; border-left-width: thick; } + .o_cal_subject p { padding:0 0.4em; margin:0; } .o_cal_location { float: right; width: 34%; } /* Since subject has a 1px border, decrease the width of location by 1% */ .o_cal_config_scrollwrapper { width: auto; height: auto; overflow: visible; overflow-x: visible; } .o_cal_config_calendar { border-width: 0; border-left-style: solid; border-left-width: thick; float: none; display: block;} diff --git a/src/main/java/org/olat/commons/calendar/_static/css/calendar_import.png b/src/main/java/org/olat/commons/calendar/_static/css/calendar_import.png new file mode 100644 index 0000000000000000000000000000000000000000..45344a99f895b0f3f5b2b31b0cdbf616a6ca54c4 Binary files /dev/null and b/src/main/java/org/olat/commons/calendar/_static/css/calendar_import.png differ diff --git a/src/main/java/org/olat/commons/calendar/_static/css/calendar_import_over.png b/src/main/java/org/olat/commons/calendar/_static/css/calendar_import_over.png new file mode 100644 index 0000000000000000000000000000000000000000..7f162e756bd9e1b921408677be625717b558e663 Binary files /dev/null and b/src/main/java/org/olat/commons/calendar/_static/css/calendar_import_over.png differ diff --git a/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java b/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java new file mode 100644 index 0000000000000000000000000000000000000000..bb9be19bca478ee039562700b0fe90d1aaffac8d --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/ExternalLinksController.java @@ -0,0 +1,324 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.commons.calendar.ui; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.commons.calendar.model.KalendarEventLink; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; + +/** + * + * <h3>Description:</h3> + * A controller to handle a list of absolute links for the calendar. + * <p> + * <h4>Events fired by this Controller</h4> + * <ul> + * <li>DONE_EVENT</li> + * <li>CANCELLED_EVENT</li> + * </ul> + * <p> + * Initial Date: 16 déc. 2010 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class ExternalLinksController extends FormBasicController { + + public static final String EXTERNAL_LINKS_PROVIDER = "external-links"; + + private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); + + private FormLink newButton; + private final KalendarEvent kalendarEvent; + private List<LinkWrapper> externalLinks; + private FormLayoutContainer linksContainer; + + public ExternalLinksController(UserRequest ureq, WindowControl wControl, KalendarEvent kalendarEvent) { + super(ureq, wControl, LAYOUT_VERTICAL); + setBasePackage(CalendarManager.class); + + this.kalendarEvent = kalendarEvent; + + externalLinks = new ArrayList<LinkWrapper>(); + List<KalendarEventLink> links = kalendarEvent.getKalendarEventLinks(); + for(KalendarEventLink link:links) { + if(EXTERNAL_LINKS_PROVIDER.equals(link.getProvider())) { + externalLinks.add(new LinkWrapper(link)); + } + } + if(externalLinks.isEmpty()) { + LinkWrapper newLinkWrapper = createLinkWrapper(); + externalLinks.add(newLinkWrapper); + } + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("tab.links.extern"); + + String page = VELOCITY_ROOT + "/calExternalLinks.html"; + linksContainer = FormLayoutContainer.createCustomFormLayout("links", getTranslator(), page); + formLayout.add(linksContainer); + linksContainer.setRootForm(mainForm); + + for(LinkWrapper link:externalLinks) { + addNewFormLink(link, linksContainer); + } + linksContainer.contextPut("links", externalLinks); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("ok-cancel", getTranslator()); + formLayout.add(buttonLayout); + buttonLayout.setRootForm(mainForm); + + uifactory.addFormSubmitButton("ok", "save", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + private void addNewFormLink(LinkWrapper link, FormLayoutContainer layoutContainer) { + // add link target + String id = link.getId(); + String uri = link.getLink().getURI(); + if(!StringHelper.containsNonWhitespace(uri)) { + uri = "http://"; + } + TextElement url = uifactory.addTextElement("url_" + id, null, -1, uri, layoutContainer); + url.clearError(); + url.setDisplaySize(60); + url.setMandatory(true); + link.setUrl(url); + + // add link description + TextElement name = uifactory.addTextElement("displayName_" + id, null, -1, link.getLink().getDisplayName(), layoutContainer); + name.clearError(); + name.setDisplaySize(40); + name.setMandatory(true); + link.setName(name); + + // add link add action button + FormLink addButton = uifactory.addFormLink("add_" + id, "table.add", "table.add", layoutContainer, Link.BUTTON); + addButton.setUserObject(link); + link.setAddButton(addButton); + + // add link deletion action button + FormLink delButton = uifactory.addFormLink("del_" + id, "table.delete", "table.delete", layoutContainer, Link.BUTTON); + delButton.setUserObject(link); + link.setDelButton(delButton); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + + boolean allOk = true; + for(LinkWrapper link:externalLinks) { + link.getUrl().clearError(); + link.getName().clearError(); + if(!link.isEmpty()) { + String url = link.getUrl().getValue(); + if(!StringHelper.containsNonWhitespace(url)) { + link.getUrl().setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else { + try { + new URL(url); + } catch(MalformedURLException e) { + link.getUrl().setErrorKey("tab.links.extern.url.invalid", null); + allOk &= false; + } + } + + String name = link.getName().getValue(); + if(!StringHelper.containsNonWhitespace(name)) { + link.getName().setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + } + } + + return allOk && super.validateFormLogic(ureq); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == newButton) { + String id = UUID.randomUUID().toString().replaceAll("-", ""); + KalendarEventLink link = new KalendarEventLink(EXTERNAL_LINKS_PROVIDER, id, "", "", ""); + LinkWrapper linkWrapper = new LinkWrapper(link); + externalLinks.add(linkWrapper); + addNewFormLink(linkWrapper, linksContainer); + } else if (source.getUserObject() instanceof LinkWrapper){ + LinkWrapper link = (LinkWrapper)source.getUserObject(); + if(link.getDelButton().equals(source)) { + externalLinks.remove(link); + } else if (link.getAddButton().equals(source)) { + int index = externalLinks.indexOf(link); + LinkWrapper newLinkWrapper = createLinkWrapper(); + addNewFormLink(newLinkWrapper, linksContainer); + if(index >= 0 && index + 1 < externalLinks.size()) { + externalLinks.add(index + 1, newLinkWrapper); + } else { + externalLinks.add(newLinkWrapper); + } + } + } + } + + @Override + protected void formOK(UserRequest ureq) { + List<KalendarEventLink> links = kalendarEvent.getKalendarEventLinks(); + + List<LinkWrapper> filledWrappers = new ArrayList<LinkWrapper>(); + for(LinkWrapper linkWrapper:externalLinks) { + if(!linkWrapper.isEmpty()) { + filledWrappers.add(linkWrapper); + } + } + + //add and update links + Set<String> usedUuids = new HashSet<String>(); + for(LinkWrapper linkWrapper:filledWrappers) { + boolean found = false; + usedUuids.add(linkWrapper.getId()); + for(KalendarEventLink link:links) { + if(link.getId().equals(linkWrapper.getId())) { + link.setURI(linkWrapper.getUrl().getValue()); + link.setDisplayName(linkWrapper.getName().getValue()); + link.setIconCssClass("b_link_extern"); + found = true; + } + } + if(!found) { + KalendarEventLink newLink = linkWrapper.getLink(); + newLink.setURI(linkWrapper.getUrl().getValue()); + newLink.setDisplayName(linkWrapper.getName().getValue()); + newLink.setIconCssClass("b_link_extern"); + links.add(newLink); + } + } + + //remove deleted links + for(Iterator<KalendarEventLink> it=links.iterator(); it.hasNext(); ) { + KalendarEventLink link = it.next(); + if(EXTERNAL_LINKS_PROVIDER.equals(link.getId()) && !usedUuids.contains(link.getId())) { + it.remove(); + } + } + + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + + private LinkWrapper createLinkWrapper() { + String id = UUID.randomUUID().toString().replaceAll("-", ""); + KalendarEventLink newLink = new KalendarEventLink(EXTERNAL_LINKS_PROVIDER, id, "", "", ""); + LinkWrapper newLinkWrapper = new LinkWrapper(newLink); + return newLinkWrapper; + } + + public class LinkWrapper { + + private TextElement url; + private TextElement name; + private FormLink delButton; + private FormLink addButton; + private final KalendarEventLink link; + + public LinkWrapper(KalendarEventLink link) { + this.link = link; + } + + public String getId() { + return link.getId(); + } + + public boolean isEmpty() { + return url == null || !StringHelper.containsNonWhitespace(url.getValue()); + } + + public TextElement getUrl() { + return url; + } + + public void setUrl(TextElement url) { + this.url = url; + } + + public TextElement getName() { + return name; + } + + public void setName(TextElement name) { + this.name = name; + } + + public FormLink getDelButton() { + return delButton; + } + + public void setDelButton(FormLink delButton) { + this.delButton = delButton; + } + + public FormLink getAddButton() { + return addButton; + } + + public void setAddButton(FormLink addButton) { + this.addButton = addButton; + } + + public KalendarEventLink getLink() { + return link; + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java new file mode 100644 index 0000000000000000000000000000000000000000..4078564d65c10d90644eff73da4631f8aeb39e14 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/ImportCalendarController.java @@ -0,0 +1,189 @@ +/** +* 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <p> +*/ + +package org.olat.commons.calendar.ui; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.ImportCalendarManager; +import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; + +/** + * + * <h3>Description:</h3> + * <p> + * Initial Date: 4 feb. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class ImportCalendarController extends FormBasicController { + + private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); + + private FormLink importTypeUrlButton; + private FormLink importTypeFileButton; + private FormSubmit importButton; + private TextElement importUrl; + private FileElement importFile; + private final KalendarRenderWrapper calendarWrapper; + + private FormLayoutContainer urlLayout; + private FormLayoutContainer fileLayout; + private FormLayoutContainer chooseLayout; + + public ImportCalendarController(UserRequest ureq, WindowControl wControl, KalendarRenderWrapper calendarWrapper) { + super(ureq, wControl, "importEvents"); + this.calendarWrapper = calendarWrapper; + + initForm(ureq); + } + + @Override + protected void constructorInit(String pageName) { + velocity_root = VELOCITY_ROOT; + super.constructorInit(pageName); + setBasePackage(CalendarManager.class); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + //choose panel + chooseLayout = FormLayoutContainer.createVerticalFormLayout("chooseContainer", getTranslator()); + chooseLayout.setRootForm(mainForm); + uifactory.addStaticTextElement("cal.import.type.file.desc", "", chooseLayout); + importTypeFileButton = uifactory.addFormLink("cal.import.type.file", chooseLayout, Link.BUTTON); + uifactory.addSpacerElement("choose-spacer", chooseLayout, false); + uifactory.addStaticTextElement("cal.import.type.url.desc", "", chooseLayout); + importTypeUrlButton = uifactory.addFormLink("cal.import.type.url", chooseLayout, Link.BUTTON); + chooseLayout.setVisible(true); + formLayout.add("chooseContainer", chooseLayout); + + //url panel + urlLayout = FormLayoutContainer.createDefaultFormLayout("urlContainer", getTranslator()); + urlLayout.setRootForm(mainForm); + importUrl = uifactory.addTextElement("cal.import.url.prompt", "cal.import.url.prompt", 200, "", urlLayout); + urlLayout.setVisible(false); + formLayout.add("urlContainer", urlLayout); + + //file panel + fileLayout = FormLayoutContainer.createDefaultFormLayout("fileContainer", getTranslator()); + fileLayout.setRootForm(mainForm); + importFile = uifactory.addFileElement("cal.import.form.prompt", "cal.import.form.prompt", fileLayout); + fileLayout.setVisible(false); + formLayout.add("fileContainer", fileLayout); + + //standard cancel panel + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + buttonGroupLayout.setRootForm(mainForm); + formLayout.add("buttonGroupLayout", buttonGroupLayout); + importButton = uifactory.addFormSubmitButton("ok", buttonGroupLayout); + importButton.setVisible(false); + uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl()); + } + + /** + * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) + */ + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + importUrl.clearError(); + if(urlLayout.isVisible()) { + String url = importUrl.getValue(); + if(StringHelper.containsNonWhitespace(url)) { + try { + new URL(url); + } catch (MalformedURLException e) { + importUrl.setErrorKey("cal.import.url.invalid", null); + allOk &= false; + } + } else { + importUrl.setErrorKey("cal.import.url.empty.error", null); + allOk &= false; + } + } + return allOk && super.validateFormLogic(ureq); + } + + @Override + protected void formOK(UserRequest ureq) { + if(urlLayout.isVisible()) { + String url = importUrl.getValue(); + if(ImportCalendarManager.importCalendarIn(calendarWrapper.getKalendar(), url)) { + showInfo("cal.import.success"); + fireEvent(ureq, Event.DONE_EVENT); + } else { + showError("cal.import.url.content.invalid"); + } + } else if(fileLayout.isVisible()) { + InputStream in = importFile.getUploadInputStream(); + if(ImportCalendarManager.importCalendarIn(calendarWrapper, in)) { + showInfo("cal.import.success"); + fireEvent(ureq, Event.DONE_EVENT); + } else { + showError("cal.import.form.format.error"); + } + FileUtils.closeSafely(in); + } + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == importTypeFileButton) { + chooseLayout.setVisible(false); + fileLayout.setVisible(true); + importButton.setVisible(true); + } else if (source == importTypeUrlButton) { + chooseLayout.setVisible(false); + urlLayout.setVisible(true); + importButton.setVisible(true); + } + super.formInnerEvent(ureq, source, event); + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/KalendarConfigurationController.java b/src/main/java/org/olat/commons/calendar/ui/KalendarConfigurationController.java index f3757963729272de61412e86fc0ab9d4591ff5d8..5161276402434770bb4b663bc05e748e550db512 100644 --- a/src/main/java/org/olat/commons/calendar/ui/KalendarConfigurationController.java +++ b/src/main/java/org/olat/commons/calendar/ui/KalendarConfigurationController.java @@ -31,6 +31,7 @@ import org.olat.commons.calendar.ICalTokenGenerator; import org.olat.commons.calendar.model.KalendarConfig; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; +import org.olat.commons.calendar.ui.events.KalendarGUIImportEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -51,6 +52,7 @@ public class KalendarConfigurationController extends BasicController { private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); private static final Object CMD_ADD = "add"; + private static final Object CMD_IMPORT = "import"; private static final Object CMD_TOGGLE_DISPLAY = "tglvis"; private static final Object CMD_CHOOSE_COLOR = "cc"; private static final Object CMD_ICAL_FEED = "if"; @@ -108,6 +110,10 @@ public class KalendarConfigurationController extends BasicController { // add new event to calendar String calendarID = ureq.getParameter(PARAM_ID); fireEvent(ureq, new KalendarGUIAddEvent(calendarID, new Date())); + } else if (command.equals(CMD_IMPORT)) { + // add new event to calendar + String calendarID = ureq.getParameter(PARAM_ID); + fireEvent(ureq, new KalendarGUIImportEvent(calendarID)); } else if (command.equals(CMD_TOGGLE_DISPLAY)) { String calendarID = ureq.getParameter(PARAM_ID); KalendarRenderWrapper calendarWrapper = findKalendarRenderWrapper(calendarID); diff --git a/src/main/java/org/olat/commons/calendar/ui/KalendarEntryDetailsController.java b/src/main/java/org/olat/commons/calendar/ui/KalendarEntryDetailsController.java index 5591ddaaa088014d8d57a7d4c14fef1aaba029e8..2a2489ce68729282b07e67a5221e70fbb6d10c56 100644 --- a/src/main/java/org/olat/commons/calendar/ui/KalendarEntryDetailsController.java +++ b/src/main/java/org/olat/commons/calendar/ui/KalendarEntryDetailsController.java @@ -29,7 +29,11 @@ import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.CalendarManagerFactory; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.commons.calendar.model.KalendarEventLink; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.controllers.linkchooser.CustomMediaChooserController; +import org.olat.core.commons.controllers.linkchooser.URLChoosenEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -44,6 +48,9 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.helpers.Settings; +import org.olat.core.util.StringHelper; public class KalendarEntryDetailsController extends BasicController { @@ -56,8 +63,11 @@ public class KalendarEntryDetailsController extends BasicController { private TabbedPane pane; private KalendarEntryForm eventForm; private LinkProvider activeLinkProvider; + private CustomMediaChooserController customMediaChooserCtr; private DialogBoxController deleteYesNoController; private CopyEventToCalendarController copyEventToCalendarController; + private ExternalLinksController externalLinksController; + private MediaLinksController mediaLinksController; private Link deleteButton; public KalendarEntryDetailsController(UserRequest ureq, KalendarEvent kalendarEvent, KalendarRenderWrapper calendarWrapper, @@ -95,6 +105,23 @@ public class KalendarEntryDetailsController extends BasicController { if (!isReadOnly) { //course node links pane.addTab(translate("tab.links"), linkVC); + + //custom media chooser + if (CoreSpringFactory.containsBean(CustomMediaChooserController.class.getName())) { + CustomMediaChooserController customMediaChooserFactory = (CustomMediaChooserController) CoreSpringFactory.getBean(CustomMediaChooserController.class.getName()); + customMediaChooserCtr = customMediaChooserFactory.getInstance(ureq, wControl, null, null, null); + if (customMediaChooserCtr != null) { + listenTo(customMediaChooserCtr); + mediaLinksController = new MediaLinksController(ureq, wControl, kalendarEvent, customMediaChooserCtr); + pane.addTab(customMediaChooserCtr.getTabbedPaneTitle(), mediaLinksController.getInitialComponent()); + listenTo(mediaLinksController); + } + } + + //list of links + externalLinksController = new ExternalLinksController(ureq, wControl, kalendarEvent); + pane.addTab(translate("tab.links.extern"), externalLinksController.getInitialComponent()); + listenTo(externalLinksController); } // wrap everything in a panel @@ -149,7 +176,9 @@ public class KalendarEntryDetailsController extends BasicController { else if (event.equals(Event.CANCELLED_EVENT)) mainPanel.setContent(mainVC); } else if (source == activeLinkProvider) { - fireEvent(ureq, Event.DONE_EVENT); + if(kalendarEvent.getCalendar() != null) { + fireEvent(ureq, Event.DONE_EVENT); + } }else if (source == eventForm) { if (event == Event.DONE_EVENT) { // ok, save edited entry @@ -198,6 +227,45 @@ public class KalendarEntryDetailsController extends BasicController { // user canceled, finish workflow fireEvent(ureq, Event.DONE_EVENT); } + } else if (source == customMediaChooserCtr) { + boolean doneSuccessfully = true; + if(event instanceof URLChoosenEvent) { + URLChoosenEvent urlEvent = (URLChoosenEvent)event; + String url = urlEvent.getURL(); + List<KalendarEventLink> links = kalendarEvent.getKalendarEventLinks(); + + String provider = customMediaChooserCtr.getClass().getSimpleName(); + String id = url; + String displayName = StringHelper.containsNonWhitespace(urlEvent.getDisplayName()) ? urlEvent.getDisplayName() : url; + String uri = url.contains("://") ? url : (Settings.getServerContextPathURI() + url); + String iconCssClass = urlEvent.getIconCssClass(); + if(!StringHelper.containsNonWhitespace(iconCssClass)) { + iconCssClass = CSSHelper.createFiletypeIconCssClassFor(url); + } + links.add(new KalendarEventLink(provider, id, displayName, uri, iconCssClass)); + + Kalendar cal = kalendarEvent.getCalendar(); + doneSuccessfully = CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, kalendarEvent); + } + + if (doneSuccessfully) { + fireEvent(ureq, event); + } else { + showError("cal.error.save"); + fireEvent(ureq, Event.FAILED_EVENT); + } + } else if (source == externalLinksController || source == mediaLinksController) { + //save externals links + Kalendar cal = kalendarEvent.getCalendar(); + if (kalendarEvent.getCalendar() != null) { + boolean doneSuccessfully = CalendarManagerFactory.getInstance().getCalendarManager().updateEventFrom(cal, kalendarEvent); + if (doneSuccessfully) { + fireEvent(ureq, Event.DONE_EVENT); + } else { + showError("cal.error.save"); + fireEvent(ureq, Event.FAILED_EVENT); + } + } } } diff --git a/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java b/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java new file mode 100644 index 0000000000000000000000000000000000000000..ccca775e72d73d7f8d1652600b44eb2c641e94ee --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/MediaLinksController.java @@ -0,0 +1,381 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.commons.calendar.ui; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.olat.commons.calendar.CalendarManager; +import org.olat.commons.calendar.model.KalendarEvent; +import org.olat.commons.calendar.model.KalendarEventLink; +import org.olat.core.commons.controllers.linkchooser.CustomMediaChooserController; +import org.olat.core.commons.controllers.linkchooser.URLChoosenEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; + +/** + * + * <h3>Description:</h3> + * A controller to handle a list of absolute links for the calendar. + * <p> + * <h4>Events fired by this Controller</h4> + * <ul> + * <li>DONE_EVENT</li> + * <li>CANCELLED_EVENT</li> + * </ul> + * <p> + * Initial Date: 16 déc. 2010 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class MediaLinksController extends FormBasicController { + + public final String provider; + + private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(CalendarManager.class); + + private FormLink newButton; + private final KalendarEvent kalendarEvent; + private LinkWrapper currentLink; + private List<LinkWrapper> externalLinks; + private FormLayoutContainer linksContainer; + private CloseableModalController mediaDialogBox; + private CustomMediaChooserController mediaChooserController; + private CustomMediaChooserController customMediaChooserController; + + public MediaLinksController(UserRequest ureq, WindowControl wControl, KalendarEvent kalendarEvent, + CustomMediaChooserController customMediaChooserController) { + super(ureq, wControl, LAYOUT_VERTICAL); + setBasePackage(CalendarManager.class); + + this.kalendarEvent = kalendarEvent; + this.customMediaChooserController = customMediaChooserController; + this.provider = customMediaChooserController.getClass().getSimpleName(); + + externalLinks = new ArrayList<LinkWrapper>(); + List<KalendarEventLink> links = kalendarEvent.getKalendarEventLinks(); + for(KalendarEventLink link:links) { + if(provider.equals(link.getProvider())) { + externalLinks.add(new LinkWrapper(link)); + } + } + if(externalLinks.isEmpty()) { + LinkWrapper newLinkWrapper = createLinkWrapper(); + externalLinks.add(newLinkWrapper); + } + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("tab.links.extern"); + + String page = VELOCITY_ROOT + "/calExternalMedias.html"; + linksContainer = FormLayoutContainer.createCustomFormLayout("links", getTranslator(), page); + formLayout.add(linksContainer); + linksContainer.setRootForm(mainForm); + + for(LinkWrapper link:externalLinks) { + addNewFormLink(link, linksContainer); + } + linksContainer.contextPut("links", externalLinks); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("ok-cancel", getTranslator()); + formLayout.add(buttonLayout); + buttonLayout.setRootForm(mainForm); + + uifactory.addFormSubmitButton("ok", "save", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + private void addNewFormLink(LinkWrapper link, FormLayoutContainer layoutContainer) { + // add link target + String id = link.getId(); + TextElement url = uifactory.addTextElement("url_" + id, null, -1, link.getLink().getURI(), layoutContainer); + url.clearError(); + url.setDisplaySize(60); + url.setMandatory(true); + url.setEnabled(false); + link.setUrl(url); + + // custom media action button + FormLink mediaButton = uifactory.addFormLink("media_" + id, " ", " ", layoutContainer, Link.NONTRANSLATED); + mediaButton.setCustomEnabledLinkCSS("b_small o_ll_browse"); + mediaButton.setUserObject(link); + link.setMediaButton(mediaButton); + + // add link description + TextElement name = uifactory.addTextElement("displayName_" + id, null, -1, link.getLink().getDisplayName(), layoutContainer); + name.clearError(); + name.setDisplaySize(40); + name.setMandatory(true); + link.setName(name); + + // add link add action button + FormLink addButton = uifactory.addFormLink("add_" + id, "table.add", "table.add", layoutContainer, Link.BUTTON); + addButton.setUserObject(link); + link.setAddButton(addButton); + + // add link deletion action button + FormLink delButton = uifactory.addFormLink("del_" + id, "table.delete", "table.delete", layoutContainer, Link.BUTTON); + delButton.setUserObject(link); + link.setDelButton(delButton); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + + boolean allOk = true; + for(LinkWrapper link:externalLinks) { + link.getUrl().clearError(); + link.getName().clearError(); + if(!link.isEmpty()) { + String name = link.getName().getValue(); + if(!StringHelper.containsNonWhitespace(name)) { + link.getName().setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + } + } + + return allOk && super.validateFormLogic(ureq); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == newButton) { + String id = UUID.randomUUID().toString().replaceAll("-", ""); + KalendarEventLink link = new KalendarEventLink(provider, id, "", "", ""); + LinkWrapper linkWrapper = new LinkWrapper(link); + externalLinks.add(linkWrapper); + addNewFormLink(linkWrapper, linksContainer); + } else if (source.getUserObject() instanceof LinkWrapper){ + currentLink = (LinkWrapper)source.getUserObject(); + if(currentLink.getDelButton().equals(source)) { + externalLinks.remove(currentLink); + } else if (currentLink.getAddButton().equals(source)) { + int index = externalLinks.indexOf(currentLink); + LinkWrapper newLinkWrapper = createLinkWrapper(); + addNewFormLink(newLinkWrapper, linksContainer); + if(index >= 0 && index + 1 < externalLinks.size()) { + externalLinks.add(index + 1, newLinkWrapper); + } else { + externalLinks.add(newLinkWrapper); + } + } else if (currentLink.getMediaButton().equals(source)) { + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + + mediaChooserController = customMediaChooserController.getInstance(ureq, getWindowControl(), null, null, ""); + listenTo(mediaChooserController); + + mediaDialogBox = new CloseableModalController(getWindowControl(), translate("choose"), mediaChooserController.getInitialComponent()); + mediaDialogBox.activate(); + listenTo(mediaDialogBox); + } + } + } + + @Override + public void event(UserRequest ureq, Component source, Event event) { + super.event(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == mediaDialogBox) { + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + mediaDialogBox = null; + mediaChooserController = null; + } else if(mediaChooserController == source) { + if(event instanceof URLChoosenEvent) { + URLChoosenEvent choosenEvent = (URLChoosenEvent)event; + String url = choosenEvent.getURL(); + currentLink.getUrl().setValue(url); + currentLink.setCssClass(choosenEvent.getIconCssClass()); + if(StringHelper.containsNonWhitespace(choosenEvent.getDisplayName())) { + currentLink.getName().setValue(choosenEvent.getDisplayName()); + } + } + mediaDialogBox.deactivate(); + removeAsListenerAndDispose(mediaDialogBox); + removeAsListenerAndDispose(mediaChooserController); + mediaDialogBox = null; + mediaChooserController = null; + } + super.event(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + List<KalendarEventLink> links = kalendarEvent.getKalendarEventLinks(); + + List<LinkWrapper> filledWrappers = new ArrayList<LinkWrapper>(); + for(LinkWrapper linkWrapper:externalLinks) { + if(!linkWrapper.isEmpty()) { + filledWrappers.add(linkWrapper); + } + } + + //add and update links + Set<String> usedUuids = new HashSet<String>(); + for(LinkWrapper linkWrapper:filledWrappers) { + boolean found = false; + usedUuids.add(linkWrapper.getId()); + for(KalendarEventLink link:links) { + if(link.getId().equals(linkWrapper.getId())) { + link.setURI(linkWrapper.getUrl().getValue()); + link.setDisplayName(linkWrapper.getName().getValue()); + link.setIconCssClass(linkWrapper.getCssClass()); + found = true; + } + } + if(!found) { + KalendarEventLink newLink = linkWrapper.getLink(); + newLink.setURI(linkWrapper.getUrl().getValue()); + newLink.setDisplayName(linkWrapper.getName().getValue()); + newLink.setIconCssClass(linkWrapper.getCssClass()); + links.add(newLink); + } + } + + //remove deleted links + for(Iterator<KalendarEventLink> it=links.iterator(); it.hasNext(); ) { + KalendarEventLink link = it.next(); + if(provider.equals(link.getId()) && !usedUuids.contains(link.getId())) { + it.remove(); + } + } + + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private LinkWrapper createLinkWrapper() { + String id = UUID.randomUUID().toString().replaceAll("-", ""); + KalendarEventLink newLink = new KalendarEventLink(provider, id, "", "", ""); + LinkWrapper newLinkWrapper = new LinkWrapper(newLink); + return newLinkWrapper; + } + + public class LinkWrapper { + + private String cssClass; + private TextElement url; + private TextElement name; + private FormLink delButton; + private FormLink addButton; + private FormLink mediaButton; + private final KalendarEventLink link; + + public LinkWrapper(KalendarEventLink link) { + this.link = link; + } + + public String getId() { + return link.getId(); + } + + public boolean isEmpty() { + return url == null || !StringHelper.containsNonWhitespace(url.getValue()); + } + + public TextElement getUrl() { + return url; + } + + public void setUrl(TextElement url) { + this.url = url; + } + + public TextElement getName() { + return name; + } + + public void setName(TextElement name) { + this.name = name; + } + + public String getCssClass() { + return cssClass; + } + + public void setCssClass(String cssClass) { + this.cssClass = cssClass; + } + + public FormLink getDelButton() { + return delButton; + } + + public void setDelButton(FormLink delButton) { + this.delButton = delButton; + } + + public FormLink getAddButton() { + return addButton; + } + + public void setAddButton(FormLink addButton) { + this.addButton = addButton; + } + + public FormLink getMediaButton() { + return mediaButton; + } + + public void setMediaButton(FormLink mediaButton) { + this.mediaButton = mediaButton; + } + + public KalendarEventLink getLink() { + return link; + } + } +} diff --git a/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java b/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java index 0db87f37fabaaf97e3f0729b8348daff81467ade..c6692a80e5c6d690b3a9bd17bb946ba149f15ecc 100644 --- a/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java +++ b/src/main/java/org/olat/commons/calendar/ui/WeeklyCalendarController.java @@ -40,6 +40,7 @@ import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; import org.olat.commons.calendar.ui.components.WeeklyCalendarComponent; import org.olat.commons.calendar.ui.events.KalendarGUIAddEvent; import org.olat.commons.calendar.ui.events.KalendarGUIEditEvent; +import org.olat.commons.calendar.ui.events.KalendarGUIImportEvent; import org.olat.commons.calendar.ui.events.KalendarModifiedEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -89,6 +90,7 @@ public class WeeklyCalendarController extends BasicController implements Calenda private ImportedCalendarConfigurationController importedCalendarConfig; private KalendarEntryDetailsController editController; private SearchAllCalendarsController searchController; + private ImportCalendarController importCalendarController; private CalendarSubscription calendarSubscription; private Controller subscriptionController; private String caller; @@ -175,12 +177,13 @@ public class WeeklyCalendarController extends BasicController implements Calenda this.importedCalendarWrappers = importedCalendarWrappers; this.calendarSubscription = calendarSubscription; this.caller = caller; + + // fxdiff OLAT-6399 + boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); // main panel mainPanel = new Panel("mainPanel"); - boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); - // main velocity controller vcMain = createVelocityContainer("indexWeekly"); thisWeekLink = LinkFactory.createLink("cal.thisweek", vcMain, this); @@ -408,12 +411,16 @@ public class WeeklyCalendarController extends BasicController implements Calenda if(affectedCal!=null) { ThreadLocalUserActivityLogger.log(getCalLoggingAction(), getClass(), LoggingResourceable.wrap(ureq.getIdentity()), LoggingResourceable.wrap(affectedCal)); } + } else if (source == importCalendarController) { + cmc.deactivate(); } else if(source == cmc && event == CloseableModalController.CLOSE_MODAL_EVENT){ //DO NOT DEACTIVATE AS ALREADY CLOSED BY CloseableModalController INTERNALLY weeklyCalendar.setDirty(true); } else if (source == calendarConfig || source == importedCalendarConfig) { if (event instanceof KalendarGUIAddEvent) { pushAddEventController((KalendarGUIAddEvent)event, ureq); + } else if (event instanceof KalendarGUIImportEvent) { + pushImportEventController((KalendarGUIImportEvent)event, ureq); } else if (event == Event.CHANGED_EVENT) { importedCalendarWrappers = ImportCalendarManager.getImportedCalendarsForIdentity(ureq); importedCalendarConfig.setCalendars(importedCalendarWrappers); @@ -561,6 +568,19 @@ public class WeeklyCalendarController extends BasicController implements Calenda setCalLoggingAction(CalendarLoggingAction.CALENDAR_ENTRY_CREATED); } + private void pushImportEventController(KalendarGUIImportEvent importEvent, UserRequest ureq) { + KalendarRenderWrapper calendarWrapper = weeklyCalendar.getKalendarRenderWrapper(importEvent.getCalendarID()); + + removeAsListenerAndDispose(importCalendarController); + + importCalendarController = new ImportCalendarController(ureq, getWindowControl(), calendarWrapper); + listenTo(importCalendarController); + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), translate("close"), importCalendarController.getInitialComponent()); + cmc.activate(); + listenTo(cmc); + } + protected void doDispose() { CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, OresHelper.lookupType(CalendarManager.class)); } diff --git a/src/main/java/org/olat/commons/calendar/ui/components/WeeklyCalendarComponentRenderer.java b/src/main/java/org/olat/commons/calendar/ui/components/WeeklyCalendarComponentRenderer.java index 7ea6cc53aabde056a25b24cfa78781aaf4fb1b78..b3d48eb13913fc7c7503b166538b62376252d424 100644 --- a/src/main/java/org/olat/commons/calendar/ui/components/WeeklyCalendarComponentRenderer.java +++ b/src/main/java/org/olat/commons/calendar/ui/components/WeeklyCalendarComponentRenderer.java @@ -46,6 +46,7 @@ import org.olat.core.gui.render.RenderingState; import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; +import org.olat.core.gui.util.CSSHelper; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; @@ -221,13 +222,14 @@ public class WeeklyCalendarComponentRenderer implements ComponentRenderer { } sb.append("</span></div>\n"); // event name (subject) - sb.append("<div class=\"o_cal_subject " + eventWrapper.getCssClass() + "\"><span>\n"); + //fxdiff BAKS-13: firefox doesn't break lines with only <br />, we need <p> + sb.append("<div class=\"o_cal_subject " + eventWrapper.getCssClass() + "\"><p>"); if (hidden) { sb.append("-"); } else { - sb.append(escapedSubject); + sb.append(escapedSubject.replace("<br />", "</p><p>")); } - sb.append("</span></div>\n"); + sb.append("</p></div>\n"); // location if (StringHelper.containsNonWhitespace(event.getLocation())) { sb.append("<div class=\"o_cal_location\"><span>\n"); @@ -648,12 +650,28 @@ public class WeeklyCalendarComponentRenderer implements ComponentRenderer { for (Iterator iter = kalendarEventLinks.iterator(); iter.hasNext();) { KalendarEventLink link = (KalendarEventLink) iter.next(); sb.append("<br /><b>"); - sb.append("<a href=\"javascript:top.o_openUriInMainWindow('").append(link.getURI()).append("')\" title=\"").append(StringEscapeUtils.escapeHtml(link.getDisplayName())).append("\" "); + //fxdiff + String uri = link.getURI(); String iconCssClass = link.getIconCssClass(); - if (StringHelper.containsNonWhitespace(iconCssClass)) { - sb.append("class=\"b_with_small_icon_left ").append(iconCssClass).append("\""); + if(!StringHelper.containsNonWhitespace(iconCssClass)) { + String displayName = link.getDisplayName(); + iconCssClass = CSSHelper.createFiletypeIconCssClassFor(displayName); } - sb.append(" onclick=\"return o2cl();\">").append(link.getDisplayName()).append("</a>"); + + if(uri.contains("://")) { + sb.append("<a href=\"").append(uri).append("\" title=\"").append(StringEscapeUtils.escapeHtml(link.getDisplayName())).append("\" "); + + if (StringHelper.containsNonWhitespace(iconCssClass)) { + sb.append("class=\"b_with_small_icon_left ").append(iconCssClass).append("\""); + } + sb.append(" target=\"_blank\">").append(link.getDisplayName()).append("</a>"); + } else { + sb.append("<a href=\"javascript:top.o_openUriInMainWindow('").append(uri).append("')\" title=\"").append(StringEscapeUtils.escapeHtml(link.getDisplayName())).append("\" "); + if (StringHelper.containsNonWhitespace(iconCssClass)) { + sb.append("class=\"b_with_small_icon_left ").append(iconCssClass).append("\""); + } + sb.append(" onclick=\"return o2cl();\">").append(link.getDisplayName()).append("</a>"); + } sb.append("</b>"); } sb.append("</div>"); diff --git a/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java b/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..90bc3078b1cfb9cad8c3bb36bbcaa7c8eac42314 --- /dev/null +++ b/src/main/java/org/olat/commons/calendar/ui/events/KalendarGUIImportEvent.java @@ -0,0 +1,46 @@ +/** + * 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. + * <p> + */ + +package org.olat.commons.calendar.ui.events; + +import org.olat.core.gui.control.Event; + +/** + * + * <h3>Description:</h3> + * <p> + * Initial Date: 4 feb. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class KalendarGUIImportEvent extends Event { + + public static final String CMD_IMPORT = "acalevent"; + private String calendarID; + + public KalendarGUIImportEvent(String calendarID) { + super(CMD_IMPORT); + this.calendarID = calendarID; + } + + public String getCalendarID() { + return calendarID; + } +} diff --git a/src/main/java/org/olat/commons/file/mail/SendDocumentsByEMailController.java b/src/main/java/org/olat/commons/file/mail/SendDocumentsByEMailController.java new file mode 100644 index 0000000000000000000000000000000000000000..162bdf1dd03ed22689f2d6797a512a304582ac50 --- /dev/null +++ b/src/main/java/org/olat/commons/file/mail/SendDocumentsByEMailController.java @@ -0,0 +1,694 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.commons.file.mail; + +import java.io.File; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.velocity.VelocityContext; +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.commons.modules.bc.FileSelection; +import org.olat.core.commons.modules.bc.FolderConfig; +import org.olat.core.commons.modules.bc.commands.CmdSendMail; +import org.olat.core.commons.modules.bc.commands.FolderCommand; +import org.olat.core.commons.modules.bc.commands.FolderCommandHelper; +import org.olat.core.commons.modules.bc.commands.FolderCommandStatus; +import org.olat.core.commons.modules.bc.components.FolderComponent; +import org.olat.core.commons.modules.bc.meta.MetaInfo; +import org.olat.core.commons.modules.bc.meta.MetaInfoFormController; +import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.TextBoxListElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.textboxlist.ResultMapProvider; +import org.olat.core.gui.components.textboxlist.TextBoxListComponent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider; +import org.olat.core.gui.control.generic.ajax.autocompletion.ListReceiver; +import org.olat.core.gui.control.generic.folder.FolderHelper; +import org.olat.core.gui.translator.Translator; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.id.Identity; +import org.olat.core.id.Persistable; +import org.olat.core.id.Preferences; +import org.olat.core.id.User; +import org.olat.core.id.UserConstants; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.core.util.mail.MailHelper; +import org.olat.core.util.mail.MailTemplate; +import org.olat.core.util.mail.MailerResult; +import org.olat.core.util.mail.MailerWithTemplate; +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; + +/** + * + * <h3>Description:</h3> + * <p> + * <p> + * Initial Date: 7 feb. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class SendDocumentsByEMailController extends FormBasicController implements CmdSendMail { + + private TextElement bodyElement; + private TextElement subjectElement; + private TextBoxListElement userListBox; + private FormLayoutContainer attachmentsLayout; + private final DecimalFormat formatMb = new DecimalFormat("0.00"); + + private int status = FolderCommandStatus.STATUS_SUCCESS; + private List<VFSLeaf> files; + private FileSelection selection; + private List<File> attachments; + + private final BaseSecurity securityManager; + private final boolean allowAttachments; + private static final int MAX_RESULTS_USERS = 12; + + public SendDocumentsByEMailController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, null, Util.createPackageTranslator(MetaInfoFormController.class, ureq.getLocale(), Util.createPackageTranslator(SendDocumentsByEMailController.class, ureq.getLocale()))); + + securityManager = BaseSecurityManager.getInstance(); + allowAttachments = !FolderConfig.getSendDocumentLinkOnly(); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("send.mail.title"); + setFormDescription("send.mail.description"); + setFormStyle("b_send_documents"); + + int emailCols = 25; + boolean allowExtern = FolderConfig.getSendDocumentToExtern(); + + userListBox = uifactory.addTextBoxListElement("send.mail.to.auto", "send.mail.to", "send.mail.to", null, formLayout, getTranslator()); + userListBox.setNoFormSubmit(true); + userListBox.addActionListener(this, FormEvent.ONCHANGE); + ((TextBoxListComponent)userListBox.getComponent()).setMapperProvider(new UserListProvider()); + ((TextBoxListComponent)userListBox.getComponent()).setAllowNewValues(allowExtern); + ((TextBoxListComponent)userListBox.getComponent()).setAllowDuplicates(false); + ((TextBoxListComponent)userListBox.getComponent()).setMaxResults(MAX_RESULTS_USERS + 2); + + subjectElement = uifactory.addTextElement("tsubject", "send.mail.subject", 255, "", formLayout); + + bodyElement = uifactory.addTextAreaElement("tbody", "send.mail.body", -1, 20, emailCols, false, "", formLayout); + + if(allowAttachments) { + String page = Util.getPackageVelocityRoot(SendDocumentsByEMailController.class) + "/attachments.html"; + attachmentsLayout = FormLayoutContainer.createCustomFormLayout("attachments", getTranslator(), page); + attachmentsLayout.setRootForm(mainForm); + attachmentsLayout.setLabel("send.mail.attachments", null); + formLayout.add(attachmentsLayout); + } + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + formLayout.add(buttonGroupLayout); + uifactory.addFormSubmitButton("ok", buttonGroupLayout); + uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl()); + } + + @Override + protected void doDispose() { + // + } + + public int getStatus() { + return status; + } + + public boolean runsModal() { + return false; + } + + public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator translator) { + VFSContainer currentContainer = folderComponent.getCurrentContainer(); + VFSContainer rootContainer = folderComponent.getRootContainer(); + + if(!VFSManager.exists(currentContainer)) { + status = FolderCommandStatus.STATUS_FAILED; + showError(translator.translate("FileDoesNotExist")); + return null; + } + status = FolderCommandHelper.sanityCheck(wControl, folderComponent); + if(status == FolderCommandStatus.STATUS_FAILED) { + return null; + } + selection = new FileSelection(ureq, folderComponent.getCurrentContainerPath()); + status = FolderCommandHelper.sanityCheck3(wControl, folderComponent, selection); + if(status == FolderCommandStatus.STATUS_FAILED) { + return null; + } + + boolean selectionWithContainer = false; + List<String> filenames = selection.getFiles(); + List<VFSLeaf> leafs = new ArrayList<VFSLeaf>(); + for(String file:filenames) { + VFSItem item = currentContainer.resolve(file); + if(item instanceof VFSContainer) { + selectionWithContainer = true; + } else if (item instanceof VFSLeaf) { + leafs.add((VFSLeaf)item); + } + } + if(selectionWithContainer) { + if(leafs.isEmpty()) { + wControl.setError(getTranslator().translate("send.mail.noFileSelected")); + return null; + } else { + setFormWarning(getTranslator().translate("send.mail.selectionContainsFolder")); + } + } + setFiles(rootContainer, leafs); + return this; + } + + protected void setFiles(VFSContainer rootContainer, List<VFSLeaf> leafs) { + this.files = leafs; + + StringBuilder subjectSb = new StringBuilder(); + if(StringHelper.containsNonWhitespace(subjectElement.getValue())) { + subjectSb.append(subjectElement.getValue()).append('\n').append('\n'); + } + StringBuilder bodySb = new StringBuilder(); + if(StringHelper.containsNonWhitespace(bodyElement.getValue())) { + bodySb.append(bodyElement.getValue()).append('\n').append('\n'); + } + + attachments = new ArrayList<File>(); + long fileSize = 0l; + for(VFSLeaf file:files) { + MetaInfo infos = null; + if(file instanceof MetaTagged) { + infos = ((MetaTagged)file).getMetaInfo(); + } + //subject + appendToSubject(file, infos, subjectSb); + + //body + appendMetadatas(file, infos, bodySb); + appendBusinessPath(rootContainer, file, bodySb); + bodySb.append('\n').append('\n'); + fileSize += file.getSize(); + if(allowAttachments && file instanceof LocalFileImpl) { + File f = ((LocalFileImpl)file).getBasefile(); + attachments.add(f); + } + } + + int mailQuota = MailHelper.getMaxSizeForAttachement(); + long fileSizeInMB = fileSize / (1024l * 1024l); + if(allowAttachments) { + if(fileSizeInMB > mailQuota) { + attachments.clear(); + setFormWarning("send.mail.fileToBigForAttachments", new String[]{String.valueOf(mailQuota), String.valueOf(fileSizeInMB)}); + } else { + List<FileInfo> infos = new ArrayList<FileInfo>(files.size()); + for(VFSLeaf file:files) { + final String name = file.getName(); + final double size = file.getSize() / (1024.0 * 1024.0); + final String sizeStr = formatMb.format(size); + final String cssClass = CSSHelper.createFiletypeIconCssClassFor(file.getName()); + infos.add(new FileInfo(name, sizeStr, cssClass)); + } + attachmentsLayout.contextPut("attachments", infos); + } + } + + subjectElement.setValue(subjectSb.toString()); + bodyElement.setValue(bodySb.toString()); + } + + protected void appendToSubject(VFSLeaf file, MetaInfo infos, StringBuilder sb) { + if(sb.length() > 0) sb.append(", "); + if(infos != null && StringHelper.containsNonWhitespace(infos.getTitle())) { + sb.append(infos.getTitle()); + } else { + sb.append(file.getName()); + } + } + + protected void appendMetadatas(VFSLeaf file, MetaInfo infos, StringBuilder sb) { + if(infos == null) { + appendMetadata("mf.filename", file.getName(), sb); + } else { + appendMetadata("mf.filename", infos.getName(), sb); + String title = infos.getTitle(); + if(StringHelper.containsNonWhitespace(title)) { + appendMetadata("mf.title", title, sb); + } + String comment = infos.getComment(); + if(StringHelper.containsNonWhitespace(comment)) { + appendMetadata("mf.comment", comment, sb); + } + String creator = infos.getCreator(); + if(StringHelper.containsNonWhitespace(creator)) { + appendMetadata("mf.creator", creator, sb); + } + String publisher = infos.getPublisher(); + if(StringHelper.containsNonWhitespace(publisher)) { + appendMetadata("mf.publisher", publisher, sb); + } + String source = infos.getSource(); + if(StringHelper.containsNonWhitespace(source)) { + appendMetadata("mf.source", source, sb); + } + String city = infos.getCity(); + if(StringHelper.containsNonWhitespace(city)) { + appendMetadata("mf.city", city, sb); + } + appendPublicationDate(infos, sb); + String pages = infos.getPages(); + if(StringHelper.containsNonWhitespace(pages)) { + appendMetadata("mf.pages", pages, sb); + } + String language = infos.getLanguage(); + if(StringHelper.containsNonWhitespace(language)) { + appendMetadata("mf.language", language, sb); + } + String url = infos.getUrl(); + if(StringHelper.containsNonWhitespace(url)) { + appendMetadata("mf.url", url, sb); + } + String author = infos.getHTMLFormattedAuthor(); + if(StringHelper.containsNonWhitespace(author)) { + appendMetadata("mf.author", author, sb); + } + String size = StringHelper.formatMemory(file.getSize()); + appendMetadata("mf.size", size, sb); + long lastModifiedDate = infos.getLastModified(); + if(lastModifiedDate > 0) { + appendMetadata("mf.lastModified", StringHelper.formatLocaleDate(lastModifiedDate, getLocale()), sb); + } + String type = FolderHelper.extractFileType(file.getName(), getLocale()); + if(StringHelper.containsNonWhitespace(type)) { + appendMetadata("mf.type", type, sb); + } + int downloads = infos.getDownloadCount(); + if(infos.getDownloadCount() >= 0) { + appendMetadata("mf.downloads", String.valueOf(downloads), sb); + } + } + } + + protected void appendMetadata(String i18nKey, String value, StringBuilder sb) { + sb.append(translate(i18nKey)).append(": ").append(value).append('\n'); + } + + protected void appendPublicationDate(MetaInfo infos, StringBuilder sb) { + String[] publicationDate = infos.getPublicationDate(); + if(publicationDate == null || publicationDate.length != 2) return; + String month = publicationDate[1]; + String year = publicationDate[0]; + if(StringHelper.containsNonWhitespace(month) || StringHelper.containsNonWhitespace(year)) { + sb.append(translate("mf.publishDate")).append(":"); + if(StringHelper.containsNonWhitespace(month)) { + sb.append(" ").append(translate("mf.month").replaceAll(" ", "")).append(" ").append(month); + } + if(StringHelper.containsNonWhitespace(year)) { + sb.append(" ").append(translate("mf.year").replaceAll(" ", "")).append(" ").append(year); + } + sb.append('\n'); + } + } + + protected void appendBusinessPath(VFSContainer rootContainer, VFSLeaf file, StringBuilder sb) { + BusinessControlFactory bCF = BusinessControlFactory.getInstance(); + String businnessPath = getWindowControl().getBusinessControl().getAsString(); + + String relPath = getRelativePath(rootContainer, file); + businnessPath += "[path=" + relPath + "]"; + + List<ContextEntry> ces = bCF.createCEListFromString(businnessPath); + String uri = bCF.getAsURIString(ces, true); + this.appendMetadata("mf.url", uri, sb); + } + + protected String getRelativePath(VFSContainer rootContainer, VFSLeaf file) { + String sb = "/" + file.getName(); + VFSContainer parent = file.getParentContainer(); + while(parent != null && !rootContainer.isSame(parent)) { + sb = "/" + parent.getName() + sb; + parent = parent.getParentContainer(); + } + return sb; + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + String subject = subjectElement.getValue(); + subjectElement.clearError(); + if(!StringHelper.containsNonWhitespace(subject)) { + subjectElement.setErrorKey("form.legende.mandatory", null); + } + + String body = bodyElement.getValue(); + bodyElement.clearError(); + if(!StringHelper.containsNonWhitespace(body)) { + bodyElement.setErrorKey("form.legende.mandatory", null); + } + + List<String> values = this.userListBox.getValueList(); + if(FolderConfig.getSendDocumentToExtern()) { + for(String value:values) { + allOk &= (MailHelper.isValidEmailAddress(value) || securityManager.isIdentityVisible(value)); + } + } else { + for(String value:values) { + allOk &= securityManager.isIdentityVisible(value); + } + } + return allOk & super.validateFormLogic(ureq); + } + + @Override + protected void formOK(UserRequest ureq) { + List<Identity> tos = getTos(); + String subject = subjectElement.getValue(); + String body = bodyElement.getValue(); + sendEmail(tos, subject, body, ureq); + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } + + protected List<Identity> getTos() { + List<String> values = userListBox.getValueList(); + List<Identity> identities = new ArrayList<Identity>(); + for(String value:values) { + Identity id = securityManager.findIdentityByName(value); + if(id != null) { + identities.add(id); + } else if(FolderConfig.getSendDocumentToExtern()) { + identities.add(new EMailIdentity(value)); + } + } + return identities; + } + + protected void sendEmail(List<Identity> tos, String subject, String body, UserRequest ureq) { + File[] attachmentArray = null; + if(attachments != null && !attachments.isEmpty() && allowAttachments) { + attachmentArray = new File[attachments.size()]; + attachmentArray = attachments.toArray(attachmentArray); + } + + MailTemplate mailTemplate = new MailTemplate(subject, body, attachmentArray) { + @Override + public void putVariablesInMailContext(VelocityContext context, Identity recipient) { + //nothing to do; + } + }; + + MailerResult mailerResult = MailerWithTemplate.getInstance().sendMailAsSeparateMails(tos, null, null, mailTemplate, ureq.getIdentity()); + MailHelper.printErrorsAndWarnings(mailerResult, getWindowControl(), ureq.getLocale()); + } + + public class UserListProvider implements ListProvider, ResultMapProvider { + + protected String formatIdentity(Identity ident) { + User u = ident.getUser(); + String login = ident.getName(); + String first = u.getProperty(UserConstants.FIRSTNAME, null); + String last = u.getProperty(UserConstants.LASTNAME, null); + String mail = u.getProperty(UserConstants.EMAIL, null); + return login + ": " + last + " " + first + " " + mail; + } + + @Override + public void getAutoCompleteContent(String searchValue, Map<String, String> resMap) { + Map<String, String> userProperties = new HashMap<String, String>(); + userProperties.put(UserConstants.FIRSTNAME, searchValue); + userProperties.put(UserConstants.LASTNAME, searchValue); + userProperties.put(UserConstants.EMAIL, searchValue); + if (StringHelper.containsNonWhitespace(searchValue)) { + List<Identity> res = securityManager.getVisibleIdentitiesByPowerSearch(searchValue, userProperties, false,null, null, null, null, null); + + int maxEntries = 14; + boolean hasMore = false; + for (Identity ident:res) { + maxEntries--; + String login = ident.getName(); + resMap.put(formatIdentity(ident), login); + if(maxEntries <= 0) { + hasMore = true; + break; + } + } + if(hasMore){ + resMap.put(TextBoxListComponent.MORE_RESULTS_INDICATOR,TextBoxListComponent.MORE_RESULTS_INDICATOR); + } + } + } + + public void getResult(String searchValue, ListReceiver receiver) { + Map<String, String> userProperties = new HashMap<String, String>(); + userProperties.put(UserConstants.FIRSTNAME, searchValue); + userProperties.put(UserConstants.LASTNAME, searchValue); + userProperties.put(UserConstants.EMAIL, searchValue); + + String login = (searchValue.equals("") ? null : searchValue); + List<Identity> res = securityManager.getVisibleIdentitiesByPowerSearch(login, userProperties, false,null, null, null, null, null); + + int maxEntries = MAX_RESULTS_USERS; + boolean hasMore = false; + for (Iterator<Identity> it_res = res.iterator(); (hasMore=it_res.hasNext()) && maxEntries > 0;) { + maxEntries--; + Identity ident = it_res.next(); + User u = ident.getUser(); + String mail = u.getProperty(UserConstants.EMAIL, null); + receiver.addEntry(mail, mail); + } + if(hasMore){ + receiver.addEntry(".....","....."); + } + } + } + + public class FileInfo { + private final String filename; + private final String sizeInMB; + private final String cssClass; + + public FileInfo(String filename, String sizeInMB, String cssClass) { + this.filename = filename; + this.sizeInMB = sizeInMB; + this.cssClass = cssClass; + } + + public String getFilename() { + return filename; + } + + public String getSizeInMB() { + return sizeInMB; + } + + public String getCssClass() { + return cssClass; + } + } + + public class EMailIdentity implements Identity { + private final String email; + private final User user; + + public EMailIdentity(String email) { + this.email = email; + user = new EMailUser(email); + } + + @Override + public Long getKey() { + return null; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return this == persistable; + } + + @Override + public Date getCreationDate() { + return null; + } + + @Override + public String getName() { + return email; + } + + @Override + public User getUser() { + return user; + } + + @Override + public Date getLastLogin() { + return null; + } + + @Override + public void setLastLogin(Date loginDate) {/**/} + + @Override + public Integer getStatus() { + return null; + } + + @Override + public void setStatus(Integer newStatus) {/**/} + + @Override + public void setName(String name) {/**/} + } + + public class EMailUser implements User { + private final EMailPreferences prefs = new EMailPreferences(); + private Map<String, String> data = new HashMap<String, String>(); + + public EMailUser(String email) { + data.put(UserConstants.FIRSTNAME, ""); + data.put(UserConstants.LASTNAME, ""); + data.put(UserConstants.EMAIL, email); + } + + public Long getKey() { + return null; + } + + @SuppressWarnings("unused") + public boolean equalsByPersistableKey(Persistable persistable) { + return this == persistable; + } + + public Date getLastModified() { + return null; + } + + public Date getCreationDate() { + return null; + } + + @SuppressWarnings("unused") + public void setProperty(String propertyName, String propertyValue) {/**/} + + @SuppressWarnings("unused") + public void setPreferences(Preferences prefs) {/**/} + + @SuppressWarnings("unused") + public String getProperty(String propertyName, Locale locale) { + return data.get(propertyName); + } + + public void setIdentityEnvironmentAttributes(Map<String, String> identEnvAttribs) {/**/} + + public String getPropertyOrIdentityEnvAttribute(String propertyName, Locale locale) { + return data.get(propertyName); + } + + public Preferences getPreferences() { + return prefs; + } + } + + public class EMailPreferences implements Preferences { + @Override + public String getLanguage() { + return getLocale().getLanguage(); + } + + @Override + public void setLanguage(String l) { + // + } + + @Override + public String getFontsize() { + return null; + } + + @Override + public void setFontsize(String l) { + // + } + + @Override + public String getNotificationInterval() { + return null; + } + + @Override + public void setNotificationInterval(String notificationInterval) {/* */ } + + @Override + public String getReceiveRealMail() { + return "true"; + } + + @Override + public void setReceiveRealMail(String receiveRealMail) { + // + } + + @Override + public boolean getInformSessionTimeout() { + return false; + } + + @Override + public void setInformSessionTimeout(boolean b) {/* */} + + @Override + public boolean getPresenceMessagesPublic() { + return false; + } + + @Override + public void setPresenceMessagesPublic(boolean b) {/* */} + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/commons/file/mail/_content/attachments.html b/src/main/java/org/olat/commons/file/mail/_content/attachments.html new file mode 100644 index 0000000000000000000000000000000000000000..4089e9eb1641d7c542f3c8c6c295b40e0f827d38 --- /dev/null +++ b/src/main/java/org/olat/commons/file/mail/_content/attachments.html @@ -0,0 +1,9 @@ +<table> + #foreach ($attachment in $attachments) + <tr> + <td><span class="b_with_small_icon_left $attachment.cssClass">$attachment.filename</span></td> + <td>($attachment.sizeInMB MB)</td> + </tr> + #end +</table> + diff --git a/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..075fdbb99de3dd73df39c482dcf6f5c1f513377f --- /dev/null +++ b/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_de.properties @@ -0,0 +1,12 @@ +send.mail.title=Dateien per E-Mail versenden +send.mail.description=Sie können E-Mail mit Dokumenten versenden +send.mail.attachments=Anhang +send.mail.subject=Betreff +send.mail.body=Nachricht +send.mail.to=An +send.mail.to.auto=An +send.mail.to.auto.add=+ +send.mail.noFileSelected=Sie haben keine Dateien ausgewählt. +send.mail.error=Achtung!!! +send.mail.selectionContainsFolder=Nur einzelne Dateien können versendet werden. +send.mail.fileToBigForAttachments={0}MB ist die maximale Grösse für Anhänge. E-Mail wird nur Links zu Dokumenten enthalten. diff --git a/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..3e9a612f2fc75c070dffcee31fc4e91b84755254 --- /dev/null +++ b/src/main/java/org/olat/commons/file/mail/_i18n/LocalStrings_en.properties @@ -0,0 +1,13 @@ +#Thu May 26 10:42:18 CEST 2011 +send.mail.attachments=Attachment +send.mail.body=Message +send.mail.description=You can send e-mails with attachments +send.mail.error=Attention\!\!\! +send.mail.fileToBigForAttachments=The maximum size for mail attachments is {0}MB. The e-mail will contain links to the documents instead. +send.mail.noFileSelected=No document selected. +send.mail.selectionContainsFolder=You can send only single documents. +send.mail.subject=Subject +send.mail.title=Send files by e-mail +send.mail.to=To +send.mail.to.auto=To +send.mail.to.auto.add=+ diff --git a/src/main/java/org/olat/commons/file/mail/_spring/sendDocByMailContext.xml b/src/main/java/org/olat/commons/file/mail/_spring/sendDocByMailContext.xml new file mode 100644 index 0000000000000000000000000000000000000000..f0a89aca2ff39bb025ad4d64dac5d972c6fd2f87 --- /dev/null +++ b/src/main/java/org/olat/commons/file/mail/_spring/sendDocByMailContext.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + <bean id="sendDocumentByEMailControllerCreator" class="org.olat.core.gui.control.creator.AutoCreator" > + <property name="className" value="org.olat.commons.file.mail.SendDocumentsByEMailController"/> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/BaseChiefController.java b/src/main/java/org/olat/core/commons/chiefcontrollers/BaseChiefController.java index d23daf41ee26702547c4cca205324b824cf678ea..23d7c9e17689f21a9104061f0ef35d3c73b47313 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/BaseChiefController.java +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/BaseChiefController.java @@ -23,6 +23,8 @@ package org.olat.core.commons.chiefcontrollers; import java.io.File; +import java.util.HashSet; +import java.util.Set; import org.olat.core.dispatcher.mapper.GlobalMapperRegistry; import org.olat.core.dispatcher.mapper.Mapper; @@ -43,6 +45,7 @@ import org.olat.core.gui.control.WindowControlInfoImpl; import org.olat.core.gui.control.guistack.GuiStack; import org.olat.core.gui.control.guistack.GuiStackSimpleImpl; import org.olat.core.gui.control.info.WindowControlInfo; +import org.olat.core.gui.control.winmgr.JSCommand; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.context.BusinessControl; @@ -51,8 +54,10 @@ import org.olat.core.logging.AssertException; import org.olat.core.logging.JavaScriptTracingController; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; +import org.olat.core.util.ZipUtil; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; import org.olat.core.util.prefs.Preferences; @@ -90,26 +95,49 @@ public class BaseChiefController extends DefaultChiefController implements Conte private Controller developmentC; private Controller jsLoggerC; - + + private Set<String> bodyCssClasses = new HashSet<String>(); + private final WindowBackOffice wbo; private static Mapper jsTranslationMapper; private static String jsTranslationMapperPath; - - private static final boolean jsMathEnabled; - + + private static boolean jsMathEnabled; + static { - // initialize global javascript translation mapper - shared in VM by all users + // initialize global javascript translation mapper - shared in VM by all + // users jsTranslationMapper = new JSTranslatorMapper(); jsTranslationMapperPath = GlobalMapperRegistry.getInstance().register(JSTranslatorMapper.class, jsTranslationMapper); - // check if mandatory jsmath files are unzipped, write error otherwhise - File jsMathImages = new File(WebappHelper.getContextRoot() + "/static/js/jsMath/fonts"); - if (!jsMathImages.exists() || !jsMathImages.isDirectory() || !(jsMathImages.list().length > 0)) { - log.error("jsMath images needed by body.html are not deployed properly. This can result in JS errors. Run \"mvn olat:font\" to deploy the necessary jsMath images and restart tomcat"); + // check if mandatory jsmath files are unzipped, write error otherwise + // fxdiff: we don't want to extract on servers where jsMath is symlinked + // for all OLATs! + String fDir = WebappHelper.getContextRoot() + "/static/js/jsMath/"; + File jsMath = new File(fDir); + try { + if (jsMath.exists() && FileUtils.isSymlink(jsMath)) { + log.info("found a symlink to local jsMath fonts. wont extract fonts.zip, jsMath is ready!"); + jsMathEnabled = true; + } else { + File jsMathImages = new File(fDir + "/fonts"); + if (!jsMathImages.exists() || !jsMathImages.isDirectory() || !(jsMathImages.list().length > 0)) { + File fZip = new File(fDir + "/fonts.zip"); + if (fZip.exists()) { + log.info("could not find jsMath fonts, try to unzip fonts.zip. please wait..."); + jsMathEnabled = ZipUtil.unzip(fZip, new File(fDir)); + } + } else { + jsMathEnabled = true; + } + } + } catch (Exception e) { + log.error("error finding jsMath: " + e); jsMathEnabled = false; - } else { - jsMathEnabled = true; + } + if (!jsMathEnabled) { + log.error("jsMath fonts are not available (neither by symlink nor by fonts.zip). Please unzip the file ``fonts.zip'' before starting olat."); } } @@ -142,22 +170,29 @@ public class BaseChiefController extends DefaultChiefController implements Conte mainvc.contextPut("o_winid", String.valueOf(mainPanel.getDispatchID())); // add jsMath library mainvc.contextPut("jsMathEnabled", Boolean.valueOf(jsMathEnabled)); + // add optional css classes + mainvc.contextPut("bodyCssClasses", bodyCssClasses); + mainPanel.setContent(mainvc); WindowManager winman = Windows.getWindows(ureq).getWindowManager(); - wbo = winman.createWindowBackOffice("basechiefwindow", this); + wbo = winman.createWindowBackOffice("basechiefwindow", this); Window w = wbo.getWindow(); - - // part that builds the css and javascript lib includes (<script src="..."> and <rel link - // e.g. - // <script type="text/javascript" src="/demo/g/2/js/jscalendar/calendar.js"></script> - mainvc.put("jsCssRawHtmlHeader", w.getJsCssRawHtmlHeader()); - - // control part for ajax-communication. returns an empty panel if ajax is not enabled, so that ajax can be turned on on the fly for development mode + // part that builds the css and javascript lib includes (<script + // src="..."> and <rel link + // e.g. + // <script type="text/javascript" + // src="/demo/g/2/js/jscalendar/calendar.js"></script> + + mainvc.put("jsCssRawHtmlHeader", w.getJsCssRawHtmlHeader()); + + // control part for ajax-communication. returns an empty panel if ajax + // is not enabled, so that ajax can be turned on on the fly for + // development mode jsServerC = wbo.createAJAXController(ureq); - mainvc.put("jsServer", jsServerC.getInitialComponent()); - + mainvc.put("jsServer", jsServerC.getInitialComponent()); + // init with no bookmark (=empty bc) mainvc.contextPut("o_bc", ""); @@ -167,10 +202,10 @@ public class BaseChiefController extends DefaultChiefController implements Conte // the current GUI theme and the global settings that contains the // font-size. both are pushed as objects so that window.dirty always reads // out the correct value - mainvc.contextPut("theme", w.getGuiTheme()); - mainvc.contextPut("globalSettings", winman.getGlobalSettings()); - mainvc.contextPut("isScreenReader", winman.isForScreenReader()); - + mainvc.contextPut("theme", w.getGuiTheme()); + mainvc.contextPut("globalSettings", winman.getGlobalSettings()); + mainvc.contextPut("isScreenReader", winman.isForScreenReader()); + // content panel contentPanel = new Panel("olatContentPanel"); mainvc.put("olatContentPanel", contentPanel); @@ -256,11 +291,16 @@ public class BaseChiefController extends DefaultChiefController implements Conte // Inline translation interceptor. when the translation tool is enabled it // will start the translation tool in translation mode, if the overlay // feature is enabled it will start in customizing mode - if (ureq.getUserSession().isAuthenticated() && ureq.getUserSession().getRoles().isOLATAdmin() && (I18nModule.isTransToolEnabled() || I18nModule.isOverlayEnabled())) { + // fxdiff: allow user-managers to use the inline translation also. TODO: + // do this with a proper right-mgmt! + if (ureq.getUserSession().isAuthenticated() + && (ureq.getUserSession().getRoles().isOLATAdmin() || ureq.getUserSession().getRoles().isUserManager()) + && (I18nModule.isTransToolEnabled() || I18nModule.isOverlayEnabled())) { inlineTranslationC = wbo.createInlineTranslationDispatcherController(ureq, getWindowControl()); Preferences guiPrefs = ureq.getUserSession().getGuiPreferences(); - Boolean isInlineTranslationEnabled = (Boolean) guiPrefs.get(I18nModule.class, I18nModule.GUI_PREFS_INLINE_TRANSLATION_ENABLED, Boolean.FALSE); - I18nManager.getInstance().setMarkLocalizedStringsEnabled(ureq.getUserSession(), isInlineTranslationEnabled); + Boolean isInlineTranslationEnabled = (Boolean) guiPrefs.get(I18nModule.class, I18nModule.GUI_PREFS_INLINE_TRANSLATION_ENABLED, + Boolean.FALSE); + I18nManager.getInstance().setMarkLocalizedStringsEnabled(ureq.getUserSession(), isInlineTranslationEnabled); mainvc.put("inlineTranslation", inlineTranslationC.getInitialComponent()); } @@ -296,7 +336,7 @@ public class BaseChiefController extends DefaultChiefController implements Conte * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Controller source, Event event) { - // nothing to listen to at the moment + // nothing to listen to at the moment } /** @@ -385,14 +425,15 @@ public class BaseChiefController extends DefaultChiefController implements Conte * org.olat.core.gui.control.Controller) */ public void setContentController(boolean autoDisposeOnWindowClose, Controller contentController) { - if (this.contentController != null) throw new AssertException("can only set contentController once!"); + if (this.contentController != null) + throw new AssertException("can only set contentController once!"); this.contentController = contentController; this.autoDisposeOnWindowClose = autoDisposeOnWindowClose; currentGuiStack = new GuiStackSimpleImpl(contentController.getInitialComponent()); - contentPanel.setContent(currentGuiStack.getPanel()); -// REVIEW:12-2007:CodeCleanup - //contentPanel.setContent(contentController.getInitialComponent()); + contentPanel.setContent(currentGuiStack.getPanel()); + // REVIEW:12-2007:CodeCleanup + // contentPanel.setContent(contentController.getInitialComponent()); } /** @@ -403,4 +444,35 @@ public class BaseChiefController extends DefaultChiefController implements Conte return jsMathEnabled; } + /** + * adds a css-Classname to the OLAT body-tag + * + * @param cssClass + * the name of a css-Class + */ + public void addBodyCssClass(String cssClass) { + // sets class for full page refreshes + bodyCssClasses.add(cssClass); + + // only relevant in AJAX mode + JSCommand jsc = new JSCommand("try { $('b_body').addClassName('" + cssClass + "'); } catch(e){if(o_info.debug) console.log(e) }"); + getWindowControl().getWindowBackOffice().sendCommandTo(jsc); + + } + + /** + * removes the given css-Classname from the OLAT body-tag + * + * @param cssClass + * the name of a css-Class + */ + public void removeBodyCssClass(String cssClass) { + // sets class for full page refreshes + bodyCssClasses.remove(cssClass); + + //only relevant in AJAX mode + JSCommand jsc = new JSCommand("try { $('b_body').removeClassName('" + cssClass + "'); } catch(e){if(o_info.debug) console.log(e) }"); + getWindowControl().getWindowBackOffice().sendCommandTo(jsc); + } + } diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_content/body.html b/src/main/java/org/olat/core/commons/chiefcontrollers/_content/body.html index cfbde0581cc400b662080bc9bf294dbd1bab1112..94dead1428a7670d3cfa4b0bec7c1ba5b008a6d3 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_content/body.html +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_content/body.html @@ -27,9 +27,12 @@ o_info.initialPageLoadFinished = false; o_info.o_winid = $o_winid; o_info.uriprefix="$r.relWinLink()"; o_info.bc="$o_bc"; +o_info.businessPath=""; o_info.dirty_form = "$r.escapeDoubleQuotes($r.translate("form.dirty"))"; o_info.locale = "$r.getLanguageCode()"; o_info.lastClickTime = new Date().getTime(); +##fxdiff +o_info.drop = true; ## olat layout specific o_info.o_baseURI = "$r.staticLink("")"; o_info.b_classPathStaticBaseURI = "${classPathStaticBaseURI}"; @@ -52,8 +55,10 @@ o_info.initialPageLoadFinished = true; </script> ## Basic JS libraries for DOM manipulation and visual effects <script type="text/javascript" src="$r.staticLink("js/prototype/prototype.js")"></script> -<script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/scriptaculous.js")"></script> +##fxdiff add dragdrop <script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/effects.js")"></script> +<script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/dragdrop.js")"></script> +<script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/scriptaculous.js")"></script> <script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/controls.js")"></script> <script type="text/javascript" src="$r.staticLink("js/scriptaculous/src/sound.js")"></script> ## EXT JS library @@ -142,7 +147,7 @@ $r.renderHeaderIncludes() ## Page title taken from org.olat.core package <title>$r.translate("page.appname") - $r.translate("page.title")</title></head> ## TODO:fj pb: Review order -<body onload="b_start();" id="b_body" class="b_lang_$r.getLanguageCode()"> +<body onload="b_start();" id="b_body" class="#foreach($cssClass in $bodyCssClasses)$cssClass #end b_lang_$r.getLanguageCode()"> ## if in debug mode, it is a invisible component which is only visible by wrapping debug information around all other ## components. <!-- START guiDebug --> diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties index b9b92b04f5b98396799ca2c0ce3658fb045805e4..4b87c313c465e608002cfc306efb6eb83ee6e4fe 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties @@ -31,6 +31,7 @@ FileResource.WIKI=Wiki FileResource.XLS=Excel FolderModule=Ordner Forum=Forum +Inbox=Inbox InfoMessage=Mitteilungen ReturnboxController=R\u00FCckgabeordner SolutionController=Musterl\u00F6sungen diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties index 1ba0cabe33d5032365c9d7f9335e589d7f245ae8..38304dccd1a584194311924af7cf81f5b5c2e1cd 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Wed Jan 05 13:56:31 CET 2011 +#Thu May 26 09:40:07 CEST 2011 AssessmentManager=Assessment tool BusinessGroup=Group CalendarManager=Calendar @@ -31,7 +31,9 @@ FileResource.WIKI=Wiki FileResource.XLS=Excel FolderModule=Folder Forum=Forum +Inbox=Inbox InfoMessage=Messages +LibrarySite=Library ReturnboxController=Return box SolutionController=Sample solutions User=User diff --git a/src/main/java/org/olat/core/commons/contextHelp/ContextHelpModule.java b/src/main/java/org/olat/core/commons/contextHelp/ContextHelpModule.java index 68bbc2f79f5b39b2723cbde28a15aa7ff88bc637..80dc18111a1df57a3c2afb747b6524fdf58cc11f 100644 --- a/src/main/java/org/olat/core/commons/contextHelp/ContextHelpModule.java +++ b/src/main/java/org/olat/core/commons/contextHelp/ContextHelpModule.java @@ -44,7 +44,8 @@ import org.olat.core.util.WebappHelper; public class ContextHelpModule extends AbstractOLATModule implements Destroyable { public static final String CHELP_DIR = "/_chelp/"; - public static final String CHELP_STATIC_DIR = CHELP_DIR + "/_static/"; + //fxdiff FXOLAT-185:fix loading of files in jar + public static final String CHELP_STATIC_DIR = CHELP_DIR + "_static/"; private static final String CONFIG_CONTEXTHELP_ENABLED = "contextHelpEnabled"; private static final String CONFIG_RATING_ENABLED = "ratingEnabled"; diff --git a/src/main/java/org/olat/core/commons/contextHelp/ContextHelpPageCrumbController.java b/src/main/java/org/olat/core/commons/contextHelp/ContextHelpPageCrumbController.java index 48d900a78969aae8c5000e7337d28f77a5eb5dae..bd399a05393b9ca002cacf658078bcf599eb3a9f 100644 --- a/src/main/java/org/olat/core/commons/contextHelp/ContextHelpPageCrumbController.java +++ b/src/main/java/org/olat/core/commons/contextHelp/ContextHelpPageCrumbController.java @@ -112,6 +112,10 @@ class ContextHelpPageCrumbController extends CrumbBasicController { int suffixPos = relPath.lastIndexOf("."); if (suffixPos > 0) { String mediaName = relPath.substring(0, suffixPos); + //fxdiff FXOLAT-185:fix loading of files in jar + if(mediaName.startsWith("/")) { + mediaName = mediaName.substring(1, mediaName.length()); + } String postfix = relPath.substring(suffixPos); // 1) try it with current language String fileName = mediaName + "_" + getLocale().toString() + postfix; diff --git a/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserController.java b/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserController.java index 106c0ed08aee0a748049c59295b8fd1dab89f209..0419051e187977d0afdffca672db168553d31f30 100644 --- a/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserController.java +++ b/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserController.java @@ -36,6 +36,7 @@ import org.olat.core.gui.control.generic.ajax.tree.TreeNodeClickedEvent; import org.olat.core.gui.control.generic.folder.FolderTreeModel; 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.filters.VFSItemFilter; /** @@ -160,7 +161,8 @@ public class FileChooserController extends BasicController { fireEvent(ureq, Event.CANCELLED_EVENT); } else if (source == selectLink) { if (selectedItem != null) { - if (onlyLeafsSelectable && selectedItem instanceof VFSContainer) { + //fxdiff FXOLAT-125: virtual file system for CP + if (onlyLeafsSelectable && !(selectedItem instanceof VFSLeaf)) { showWarning("filechooser.tree.error.only.leafs", selectedItem.getName()); } else { fireEvent(ureq, new FileChoosenEvent(selectedItem)); diff --git a/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserUIFactory.java b/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserUIFactory.java index cdf2dadfc5d66a5eabaab099f64579de4931817a..35ad900abdd8c50c1cca67c50894aedb2b23a95c 100644 --- a/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserUIFactory.java +++ b/src/main/java/org/olat/core/commons/controllers/filechooser/FileChooserUIFactory.java @@ -28,6 +28,7 @@ import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; import org.olat.core.util.vfs.MergeSource; import org.olat.core.util.vfs.NamedContainerImpl; +import org.olat.core.util.vfs.NamedLeaf; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.filters.VFSContainerFilter; @@ -165,6 +166,11 @@ public class FileChooserUIFactory { // direction is not necessarily the same as the opposite check while ( tmpItem != null && !rootContainer.isSame(tmpItem) && !tmpItem.isSame(rootContainer)) { String itemFileName = tmpItem.getName(); + //fxdiff FXOLAT-125: virtual file system for CP + if(tmpItem instanceof NamedLeaf) { + itemFileName = ((NamedLeaf)tmpItem).getDelegate().getName(); + } + // Special case: check if this is a named container, see OLAT-3848 for (NamedContainerImpl namedRootChild : namedRootChilds) { if (namedRootChild.isSame(tmpItem)) { diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/CustomMediaChooserController.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/CustomMediaChooserController.java index b4f2d37bd25037cfbf50168e9c73891bd7a14ec5..459b14480ddc5f86c2f9a502b8b4e718ddb79f4b 100644 --- a/src/main/java/org/olat/core/commons/controllers/linkchooser/CustomMediaChooserController.java +++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/CustomMediaChooserController.java @@ -30,10 +30,10 @@ package org.olat.core.commons.controllers.linkchooser; * * @author Florian Gnägi, frentix GmbH, http://www.frentix.com */ +import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.DefaultController; import org.olat.core.gui.control.WindowControl; -import org.olat.core.logging.activity.IUserActivityLogger; import org.olat.core.util.vfs.VFSContainer; public abstract class CustomMediaChooserController extends DefaultController { @@ -62,6 +62,9 @@ public abstract class CustomMediaChooserController extends DefaultController { */ abstract public CustomMediaChooserController getInstance(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, String[] suffixes, String fileName); + + //fxdiff + abstract public Mapper getMapperInstance(VFSContainer rootDir, String[] suffixes, String fileName); /** * @return Title for media chooser tabbed pane diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java new file mode 100644 index 0000000000000000000000000000000000000000..0a204b528bcce0f6303f0a3148660d7a8a1b526a --- /dev/null +++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java @@ -0,0 +1,75 @@ +/** + * 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br> + * University of Zurich, Switzerland. + * <p> + */ + +package org.olat.core.commons.controllers.linkchooser; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.VFSContainer; + +/** + * + * <h3>Description:</h3> + * This is a link chooser which can be embedded in a standard olat popup window + * <p> + * <h4>Events fired by this Controller</h4> + * <ul> + * <li>URLChooseEvent</li> + * </ul> + * <p> + * Initial Date: 15 déc. 2010 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class MediaChooserController extends LinkChooserController { + + + /** + * + * @param ureq + * @param wControl + * @param rootDir Root directory for file-chooser. + * @param uploadRelPath The relative path within the rootDir where uploaded + * files should be put into. If NULL, the root Dir is used + * @param suffixes Supported file suffixes for file-chooser. + * @param fileName Base file-path for file-chooser. + * @param userActivityLogger + * @param internalLinkTreeModel Model with internal links e.g. course-node + * tree model. The internal-link chooser tab won't be shown when the + * internalLinkTreeModel is null. + */ + public MediaChooserController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, String uploadRelPath, String[] suffixes, String fileName, + CustomLinkTreeModel customLinkTreeModel) { + super(ureq, wControl, rootDir, uploadRelPath, suffixes, fileName, customLinkTreeModel); + } + + /** + * @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 + @SuppressWarnings("unused") + public void event(UserRequest ureq, Controller source, Event event) { + fireEvent(ureq, event); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/URLChoosenEvent.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/URLChoosenEvent.java index cab14e656692a3bb04db93f2b04ec458629c7008..c8bb2b0274214ad8362d4aa884eabd1d32f88e9f 100644 --- a/src/main/java/org/olat/core/commons/controllers/linkchooser/URLChoosenEvent.java +++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/URLChoosenEvent.java @@ -33,15 +33,25 @@ import org.olat.core.gui.control.Event; * @author Florian Gnägi, frentix GmbH, http://www.frentix.com */ public class URLChoosenEvent extends Event { - private String url; + private final String url; + private final String displayName; + private final String htmlTarget; + private final String iconCssClass; /** * Constructor for this even. * @param url The URL that has been selected */ public URLChoosenEvent(String url) { + this(url, null, null, null); + } + + public URLChoosenEvent(String url, String displayName, String htmlTarget, String iconCssClass) { super("urlchoosenevent"); this.url = url; + this.displayName = displayName; + this.htmlTarget = htmlTarget; + this.iconCssClass = iconCssClass; } /** @@ -50,4 +60,25 @@ public class URLChoosenEvent extends Event { public String getURL() { return url; } + + /** + * @return the display name of the link (can be null) + */ + public String getDisplayName() { + return displayName; + } + + /** + * @return The HTML target + */ + public String getHtmlTarget() { + return htmlTarget; + } + + /** + * @return the css class icon for the file + */ + public String getIconCssClass() { + return iconCssClass; + } } diff --git a/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java b/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java index 8d8746180cc9fe4954786cebf0de5d5d5ca3eb4a..1c2bd53902868210454bd7d398898c4c8fc116ad 100644 --- a/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java +++ b/src/main/java/org/olat/core/commons/editor/htmleditor/HTMLEditorController.java @@ -47,7 +47,9 @@ import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.LocalFileImpl; +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.version.Versionable; @@ -380,7 +382,7 @@ public class HTMLEditorController extends FormBasicController { /** - * Helper method to get a meaningfull debugging filename from the vfs + * Helper method to get a meaningful debugging filename from the vfs * container and the file path * * @param root @@ -389,10 +391,17 @@ public class HTMLEditorController extends FormBasicController { */ private String getFileDebuggingPath(VFSContainer root, String relPath) { String path = relPath; - VFSContainer dir = root; - while (dir != null) { - path = "/" + dir.getName() + path; - dir = dir.getParentContainer(); + //fxdiff: FXOLAT-167 + VFSItem item = root.resolve(relPath); + if (item instanceof LocalFileImpl) { + LocalFileImpl file = (LocalFileImpl) item; + path = file.getBasefile().getAbsolutePath(); + } else { + VFSContainer dir = root; + while (dir != null) { + path = "/" + dir.getName() + path; + dir = dir.getParentContainer(); + } } return path; } diff --git a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java index eedc0b8b73f0c5da97901e7a326092cde09822af..88a45c6de4b4b9238323d71baa94179c4b3cf0b7 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java @@ -32,6 +32,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.commons.chiefcontrollers.ChiefControllerMessageEvent; import org.olat.core.commons.chiefcontrollers.LanguageChangedEvent; import org.olat.core.commons.fullWebApp.util.GlobalStickyMessage; +import org.olat.core.configuration.PersistedProperties; import org.olat.core.gui.GUIInterna; import org.olat.core.gui.GUIMessage; import org.olat.core.gui.UserRequest; @@ -43,6 +44,7 @@ import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.panel.OncePanel; import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.text.TextFactory; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; @@ -116,7 +118,6 @@ public class BaseFullWebappController extends BasicController implements Generic // the sites list private List<SiteInstance> sites; private Map<SiteInstance, BornSiteInstance> siteToBornSite = new HashMap<SiteInstance, BornSiteInstance>(); - private static final int MAX_TABS = 5; private int navLinkCounter = 1; //fxdiff BAKS-7 Resume function private Map<SiteInstance,HistoryPoint> siteToBusinessPath = new HashMap<SiteInstance,HistoryPoint>(); @@ -126,6 +127,7 @@ public class BaseFullWebappController extends BasicController implements Generic protected Controller contentCtrl; private Panel initialPanel; private DTabs myDTabsImpl; + private static Integer MAX_TAB; public BaseFullWebappController(UserRequest ureq, WindowControl ouisc_wControl, BaseFullWebappControllerParts baseFullWebappControllerParts) { @@ -425,7 +427,10 @@ public class BaseFullWebappController extends BasicController implements Generic activateSite(sites.get(0), ureq, null, null); } } - if (sites == null && contentCtrl == null) { throw new AssertException("either one site has to be present or a content controller"); } + if (sites == null && contentCtrl == null) { + // fxdiff: FXOLAT-190 RS if no sites displayed... show empty page instead + main.setContent(TextFactory.createTextComponentFromString("empty", "", null, false, null)); + } // set maintenance message String stickyMessage = GlobalStickyMessage.getGlobalStickyMessage(); @@ -797,7 +802,7 @@ public class BaseFullWebappController extends BasicController implements Generic //fxdiff BAKS-7 Resume function public DTab createDTab(OLATResourceable ores, OLATResourceable repoOres, String title) { // fxdiff: read from props - if (dtabs.size() >= MAX_TABS) { + if (dtabs.size() >= getMaxTabs()) { getWindowControl().setError(translate("warn.tabsfull")); return null; } @@ -805,6 +810,20 @@ public class BaseFullWebappController extends BasicController implements Generic return dt; } + /** + * fxdiff: load max dTab-Amount from Properties, set default to 5 + * @return + */ + private int getMaxTabs() { + if (MAX_TAB == null) { + PersistedProperties prop = new PersistedProperties(this); + prop.init(); + prop.setIntPropertyDefault("max.dtabs", 5); + MAX_TAB = prop.getIntPropertyValue("max.dtabs"); + } + return MAX_TAB; + } + /** * @see org.olat.core.gui.control.generic.dtabs.DTabs#addDTab(org.olat.core.gui.control.generic.dtabs.DTab) */ diff --git a/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsBackController.java b/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsBackController.java index d867b4364eef80f3e937fb96405b73a6a22a1330..82a02e0d40292785318b6b55348bfd6d45225fde 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsBackController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsBackController.java @@ -20,20 +20,24 @@ */ package org.olat.core.commons.fullWebApp; +import org.olat.core.commons.chiefcontrollers.BaseChiefController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.MainLayoutBasicController; import org.olat.core.gui.control.generic.layout.MainLayout3ColumnsController; +import org.olat.core.gui.control.winmgr.JSCommand; /** - * <h3>Description:</h3> - * This layouter controller provides a fullscreen view with a back-link + * <h3>Description:</h3> This layouter controller provides a fullscreen view + * with a back-link * <p> * <h3>Events thrown by this controller:</h3> * <ul> @@ -44,52 +48,61 @@ import org.olat.core.gui.control.generic.layout.MainLayout3ColumnsController; * @author Florian Gnaegi, frentix GmbH, http://www.frentix.com */ public class LayoutMain3ColsBackController extends MainLayoutBasicController implements MainLayout3ColumnsController { - + private LayoutMain3ColsController layoutCtr; private VelocityContainer backVC; private Link backLink; + private boolean fullScreen = false; + + private BaseChiefController thebaseChief; /** * Constructor for creating a 3 col based menu on the main area + * * @param ureq * @param wControl - * @param col1 usually the left column - * @param col2 usually the right column - * @param col3 usually the content column - * @param layoutConfigKey identificator for this layout to persist the users column width settings + * @param col1 + * usually the left column + * @param col2 + * usually the right column + * @param col3 + * usually the content column + * @param layoutConfigKey + * identificator for this layout to persist the users column + * width settings */ - public LayoutMain3ColsBackController(UserRequest ureq, WindowControl wControl, - Component col1, Component col2, Component col3, String layoutConfigKey) { + public LayoutMain3ColsBackController(UserRequest ureq, WindowControl wControl, Component col1, Component col2, Component col3, + String layoutConfigKey) { super(ureq, wControl); - - // create a wrapper velocity container that contains the back link and normal main layout - backVC = createVelocityContainer("main_back"); - + + // create a wrapper velocity container that contains the back link and + // normal main layout + backVC = createVelocityContainer("main_back"); + // create layout and add it to main view layoutCtr = new LayoutMain3ColsController(ureq, wControl, col1, col2, col3, layoutConfigKey); - listenTo(layoutCtr); + listenTo(layoutCtr); backVC.put("3collayout", layoutCtr.getInitialComponent()); - + // create close link backLink = LinkFactory.createLinkBack(backVC, this); // finish: use wrapper as view putInitialPanel(backVC); } - /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) + * org.olat.core.gui.components.Component, + * org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Component source, Event event) { - if (source == backLink){ + if (source == backLink) { // remove the preview workflow from the stack and notify listeners // about the back click - getWindowControl().pop(); + deactivate();// fxdiff FXOLAT-116: SCORM improvements fireEvent(ureq, Event.BACK_EVENT); } } - @Override public Component getInitialComponent() { @@ -100,53 +113,77 @@ public class LayoutMain3ColsBackController extends MainLayoutBasicController imp * Activate this back workflow */ public void activate() { - getWindowControl().pushToMainArea(backVC); + if(fullScreen) + getWindowControl().pushAsModalDialog(backVC); + else + getWindowControl().pushToMainArea(backVC); } - + + // fxdiff FXOLAT-116: SCORM improvements + public void setAsFullscreen(UserRequest ureq) { + ChiefController cc = (ChiefController) Windows.getWindows(ureq).getAttribute("AUTHCHIEFCONTROLLER"); + if (cc instanceof BaseChiefController) { + thebaseChief = (BaseChiefController) cc; + thebaseChief.addBodyCssClass("b_full_screen"); + } + fullScreen = true; + } + /** - * Deactivates back controller. Please do use this method here instead of getWindowControl().pop() ! + * Deactivates back controller. Please do use this method here instead of + * getWindowControl().pop() ! */ public void deactivate() { getWindowControl().pop(); + // fxdiff FXOLAT-116: SCORM improvements + if (fullScreen) { + thebaseChief.removeBodyCssClass("b_full_screen"); + } } - @Override protected void doDispose() { // child controller autodisposed + thebaseChief=null; } - // // Methods from the 3 col layout: // public void hideCol1(boolean hide) { this.layoutCtr.hideCol1(hide); } + public void hideCol2(boolean hide) { this.layoutCtr.hideCol2(hide); } + public void hideCol3(boolean hide) { // ignore this: col3 is mandatory - } + } + public void setCol1(Component col1Component) { this.layoutCtr.setCol1(col1Component); } + public void setCol2(Component col2Component) { this.layoutCtr.setCol2(col2Component); } + public void setCol3(Component col3Component) { this.layoutCtr.setCol3(col3Component); } + public void addCssClassToMain(String cssClass) { this.layoutCtr.addCssClassToMain(cssClass); } + public void addDisposableChildController(Controller toBedisposedControllerOnDispose) { this.layoutCtr.addDisposableChildController(toBedisposedControllerOnDispose); } + public void removeCssClassFromMain(String cssClass) { this.layoutCtr.removeCssClassFromMain(cssClass); } - } \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsPreviewController.java b/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsPreviewController.java index a174e58cbcfe7e8fb1a969ed1272064e6990434a..ef29b12c3049966843391c0b6c6cae610a0694c4 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsPreviewController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/LayoutMain3ColsPreviewController.java @@ -20,17 +20,20 @@ */ package org.olat.core.commons.fullWebApp; +import org.olat.core.commons.chiefcontrollers.BaseChiefController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.htmlheader.jscss.CustomCSS; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.MainLayoutBasicController; import org.olat.core.gui.control.generic.layout.MainLayout3ColumnsController; +import org.olat.core.gui.control.winmgr.JSCommand; /** * <h3>Description:</h3> @@ -49,7 +52,10 @@ public class LayoutMain3ColsPreviewController extends MainLayoutBasicController private LayoutMain3ColsController layoutCtr; private VelocityContainer previewVC; private Link backLink; - + private boolean fullScreen = false; + + private BaseChiefController thebaseChief; + /** * Constructor for creating a 3 col based menu on the main area * @param ureq @@ -88,7 +94,7 @@ public class LayoutMain3ColsPreviewController extends MainLayoutBasicController if (source == backLink){ // remove the preview workflow from the stack and notify listeners // about the back click - getWindowControl().pop(); + deactivate();//fxdiff FXOLAT-116: SCORM improvements fireEvent(ureq, Event.BACK_EVENT); } } @@ -103,7 +109,20 @@ public class LayoutMain3ColsPreviewController extends MainLayoutBasicController * Activate this preview workflow */ public void activate() { - getWindowControl().pushToMainArea(previewVC); + if(fullScreen) + getWindowControl().pushAsModalDialog(previewVC); + else + getWindowControl().pushToMainArea(previewVC); + } + + //fxdiff FXOLAT-116: SCORM improvements + public void setAsFullscreen(UserRequest ureq) { + ChiefController cc = (ChiefController) Windows.getWindows(ureq).getAttribute("AUTHCHIEFCONTROLLER"); + if (cc instanceof BaseChiefController) { + thebaseChief = (BaseChiefController) cc; + thebaseChief.addBodyCssClass("b_full_screen"); + } + fullScreen = true; } /** @@ -111,12 +130,17 @@ public class LayoutMain3ColsPreviewController extends MainLayoutBasicController */ public void deactivate() { getWindowControl().pop(); + // fxdiff FXOLAT-116: SCORM improvements + if (fullScreen) { + thebaseChief.removeBodyCssClass("b_full_screen"); + } } @Override protected void doDispose() { // child controller autodisposed + thebaseChief = null; } diff --git a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html index 1d87b26935a3b70fc2557779d885191fc7dd51c2..00c6a0bfe9a0f96bfeca0d0c7d5b7d8aa7715d49 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html +++ b/src/main/java/org/olat/core/commons/fullWebApp/_content/fullwebapplayout.html @@ -31,6 +31,9 @@ ## ---- END Access keys <div id="b_logo"></div> +<!-- fxdiff: used for course layout generator --> +<div id="b_right_logo"></div> +<!-- end diff --> ## something to be displayed in the header, a logo, some ads, whatever #if($r.available("headerComponent")) $r.render("headerComponent") diff --git a/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutCreator.java b/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutCreator.java index c01d2b08ceab4c418f0ab4f909a44d522627bbe8..0ef27f24b2827a99ab23d1a9cd0c5974df7e3b8e 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutCreator.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutCreator.java @@ -88,5 +88,20 @@ public class BaseFullWebappPopupLayoutCreator implements lureq, lwControl, oplm.getFullWebappParts()); return pbwc; } + + //fxdiff + public PopupBrowserWindowController createNewUnauthenticatedPopupWindowController(UserRequest lureq, WindowControl lwControl, + ControllerCreator contentControllerCreator) { + BaseFullWebappPopupLayout oplm; + if (!(contentControllerCreator instanceof BaseFullWebappPopupLayout)) { + oplm = BaseFullWebappPopupLayoutFactory.createMinimalPopupLayout(contentControllerCreator); + } else { + oplm = (BaseFullWebappPopupLayout) contentControllerCreator; + } + + PopupBrowserWindowController pbwc = new BaseFullWebappPopupBrowserWindow(lureq, lwControl, oplm.getFullWebappParts()); + return pbwc; + } + } diff --git a/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutFactory.java b/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutFactory.java index 70fdadfe3472cad0e3ffe1d7baaf1a23a36b8a13..d64e84184f130f5ae31e38ce77c3cf5f72318fae 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutFactory.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/popup/BaseFullWebappPopupLayoutFactory.java @@ -61,6 +61,12 @@ public class BaseFullWebappPopupLayoutFactory { } + //fxdiff + public static BaseFullWebappPopupLayout createMinimalPopupLayout(ControllerCreator controllerCreator) { + BaseFullWebappPopupLayout layoutCC = new BaseFullWebappMinimalLayoutControllerCreator(controllerCreator); + return layoutCC; + } + /** * Open a new Window which redirects somewhere else * @param ureq diff --git a/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java b/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java new file mode 100644 index 0000000000000000000000000000000000000000..f101cf3ca5def867befde82b791a190a3ad5c813 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java @@ -0,0 +1,413 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ + +package org.olat.core.commons.modules.bc; + +import static java.util.Arrays.asList; + +import org.olat.core.commons.controllers.linkchooser.FileLinkChooserController; +import org.olat.core.commons.controllers.linkchooser.LinkChooserController; +import org.olat.core.commons.controllers.linkchooser.URLChoosenEvent; +import org.olat.core.commons.modules.bc.commands.FolderCommand; +import org.olat.core.commons.modules.bc.commands.FolderCommandStatus; +import org.olat.core.commons.modules.bc.components.FolderComponent; +import org.olat.core.commons.modules.bc.meta.MetaInfo; +import org.olat.core.commons.modules.bc.meta.MetaInfoFactory; +import org.olat.core.commons.modules.bc.meta.MetaInfoFormController; +import org.olat.core.commons.modules.bc.meta.MetaInfoHelper; +import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; +import org.olat.core.commons.modules.bc.version.RevisionListController; +import org.olat.core.commons.modules.bc.version.VersionCommentController; +import org.olat.core.commons.modules.bc.vfs.OlatRootFileImpl; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.ButtonClickedEvent; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.gui.translator.Translator; +import org.olat.core.logging.activity.CoreLoggingResourceable; +import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.util.Util; +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.version.Versionable; +import org.olat.core.util.vfs.version.Versions; + +/** + * + * Description:<br> + * TODO: srosse Class Description for FileCopyController + * + * <P> + * Initial Date: 18 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class FileCopyController extends LinkChooserController { + private final FolderComponent folderComponent; + + private DialogBoxController lockedFileDialog; + private DialogBoxController overwriteDialog; + private RevisionListController revisionListCtr; + private CloseableModalController revisionListDialogBox; + private VersionCommentController commentVersionCtr; + private CloseableModalController commentVersionDialogBox; + private VersionCommentController unlockCtr; + private CloseableModalController unlockDialogBox; + private MetaInfoFormController metaDataCtr; + + private VFSLeaf newFile; + private VFSLeaf sourceLeaf; + private VFSLeaf existingVFSItem; + private String renamedFilename; + + public FileCopyController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, + FolderComponent folderComponent) { + super(ureq, wControl, rootDir, null, null, "", null); + this.folderComponent = folderComponent; + } + + @Override + //this is a hack to overwrite the package used by the BasicController + protected VelocityContainer createVelocityContainer(String page) { + Translator fallbackTranslator = Util.createPackageTranslator(FileCopyController.class, getLocale()); + setTranslator(Util.createPackageTranslator(LinkChooserController.class, getLocale(), fallbackTranslator)); + velocity_root = Util.getPackageVelocityRoot(LinkChooserController.class); + return super.createVelocityContainer(page); + } + + @Override + public void event(UserRequest ureq, Controller source, Event event) { + if(source instanceof FileLinkChooserController) { + if (event == Event.DONE_EVENT || event == Event.CANCELLED_EVENT){ + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } else if (event instanceof URLChoosenEvent) { + URLChoosenEvent choosenEvent = (URLChoosenEvent)event; + String url = choosenEvent.getURL(); + if(url.indexOf("://") < 0) { + VFSContainer cContainer = folderComponent.getExternContainerForCopy(); + VFSItem item = cContainer.resolve(url); + if(item instanceof VFSLeaf) { + sourceLeaf = (VFSLeaf)item; + String filename = sourceLeaf.getName(); + VFSContainer tContainer = folderComponent.getCurrentContainer(); + newFile = tContainer.createChildLeaf(filename); + if(newFile == null) { + existingVFSItem = (VFSLeaf)tContainer.resolve(filename); + fileAlreadyExists(ureq); + } else { + finishUpload(ureq); + } + } else { + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } + } else { + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } + } + } else if (source == overwriteDialog) { + if (event instanceof ButtonClickedEvent) { + ButtonClickedEvent buttonClickedEvent = (ButtonClickedEvent) event; + if (buttonClickedEvent.getPosition() == 0) { //ok + if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) { + //new version + String relPath = null; + if(existingVFSItem instanceof OlatRootFileImpl) { + relPath = ((OlatRootFileImpl)existingVFSItem).getRelPath(); + } + int maxNumOfRevisions = FolderConfig.versionsAllowed(relPath); + if(maxNumOfRevisions == 0) { + //someone play with the configuration + // Overwrite... + String fileName = existingVFSItem.getName(); + existingVFSItem.delete(); + newFile = folderComponent.getCurrentContainer().createChildLeaf(fileName); + // ... and notify listeners. + finishUpload(ureq); + } else { + + removeAsListenerAndDispose(commentVersionCtr); + commentVersionCtr = new VersionCommentController(ureq,getWindowControl(), askForLock(existingVFSItem, ureq), true); + listenTo(commentVersionCtr); + + removeAsListenerAndDispose(commentVersionDialogBox); + commentVersionDialogBox = new CloseableModalController(getWindowControl(), translate("save"), commentVersionCtr.getInitialComponent()); + listenTo(commentVersionDialogBox); + + commentVersionDialogBox.activate(); + } + } else { + //if the file is locked, ask for unlocking it + if(existingVFSItem instanceof MetaTagged && ((MetaTagged)existingVFSItem).getMetaInfo().isLocked()) { + + removeAsListenerAndDispose(unlockCtr); + unlockCtr = new VersionCommentController(ureq,getWindowControl(), true, false); + listenTo(unlockCtr); + + removeAsListenerAndDispose(unlockDialogBox); + unlockDialogBox = new CloseableModalController(getWindowControl(), translate("ok"), unlockCtr.getInitialComponent()); + listenTo(unlockDialogBox); + + unlockDialogBox.activate(); + + } else { + // Overwrite... + String fileName = existingVFSItem.getName(); + existingVFSItem.delete(); + newFile = folderComponent.getCurrentContainer().createChildLeaf(fileName); + // ... and notify listeners. + finishUpload(ureq); + } + } + } else if (buttonClickedEvent.getPosition() == 1) { //not ok + //make newFile with the proposition of filename + newFile = folderComponent.getCurrentContainer().createChildLeaf(renamedFilename); + // ... and notify listeners. + finishUpload(ureq); + } else if (buttonClickedEvent.getPosition() == 2) { // cancel + //cancel -> do nothing + + } else { + throw new RuntimeException("Unknown button number " + buttonClickedEvent.getPosition()); + } + } + } else if (source == lockedFileDialog) { + if (event instanceof ButtonClickedEvent) { + ButtonClickedEvent buttonClickedEvent = (ButtonClickedEvent) event; + switch(buttonClickedEvent.getPosition()) { + case 0: { + // ... and notify listeners. + newFile = existingVFSItem; + finishUpload(ureq); + break; + } + case 1: {//cancel + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + break; + } + default: + throw new RuntimeException("Unknown button number " + buttonClickedEvent.getPosition()); + } + } + } else if (source == commentVersionCtr) { + String comment = commentVersionCtr.getComment(); + if(existingVFSItem instanceof MetaTagged) { + MetaInfo info = ((MetaTagged)existingVFSItem).getMetaInfo(); + if(info.isLocked() && !commentVersionCtr.keepLocked()) { + info.setLocked(false); + info.write(); + } + } + + commentVersionDialogBox.deactivate(); + if(revisionListDialogBox != null) { + revisionListDialogBox.deactivate(); + } + + //ok, new version of the file + Versionable existingVersionableItem = (Versionable)existingVFSItem; + boolean ok = existingVersionableItem.getVersions().addVersion(ureq.getIdentity(), comment, sourceLeaf.getInputStream()); + if(ok) { + newFile = existingVFSItem; + } + finishSuccessfullUpload(existingVFSItem.getName(), ureq); + } else if (source == unlockCtr) { + // Overwrite... + String fileName = existingVFSItem.getName(); + if(!unlockCtr.keepLocked()) { + MetaInfo info = ((MetaTagged)existingVFSItem).getMetaInfo(); + info.setLocked(false); + info.setLockedBy(null); + info.write(); + } + + unlockDialogBox.deactivate(); + + newFile = existingVFSItem; + // ... and notify listeners. + finishSuccessfullUpload(existingVFSItem.getName(), ureq); + + } else if (source == revisionListCtr) { + if(FolderCommandStatus.STATUS_CANCELED == revisionListCtr.getStatus()) { + + revisionListDialogBox.deactivate(); + + //don't want to delete revisions + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } else { + if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) { + + revisionListDialogBox.deactivate(); + + Versionable versionable = (Versionable)existingVFSItem; + Versions versions = versionable.getVersions(); + int maxNumOfRevisions = FolderConfig.versionsAllowed(null); + if(maxNumOfRevisions < 0 || maxNumOfRevisions > versions.getRevisions().size()) { + + removeAsListenerAndDispose(commentVersionCtr); + commentVersionCtr = new VersionCommentController(ureq,getWindowControl(), askForLock(existingVFSItem, ureq), true); + listenTo(commentVersionCtr); + + removeAsListenerAndDispose(commentVersionDialogBox); + commentVersionDialogBox = new CloseableModalController(getWindowControl(), translate("save"), commentVersionCtr.getInitialComponent()); + listenTo(commentVersionDialogBox); + + commentVersionDialogBox.activate(); + + } else { + + removeAsListenerAndDispose(revisionListCtr); + revisionListCtr = new RevisionListController(ureq,getWindowControl(),versionable); + listenTo(revisionListCtr); + + removeAsListenerAndDispose(revisionListDialogBox); + revisionListDialogBox = new CloseableModalController(getWindowControl(), translate("delete"), revisionListCtr.getInitialComponent()); + listenTo(revisionListDialogBox); + + revisionListDialogBox.activate(); + } + } + } + } + } + + private void finishUpload(UserRequest ureq) { + VFSManager.copyContent(sourceLeaf, newFile); + finishSuccessfullUpload(newFile.getName(), ureq); + } + + private void finishSuccessfullUpload(String fileName, UserRequest ureq) { + VFSContainer currentContainer = folderComponent.getCurrentContainer(); + VFSItem item = currentContainer.resolve(fileName); + if (item instanceof OlatRootFileImpl) { + OlatRootFileImpl relPathItem = (OlatRootFileImpl) item; + // create meta data + MetaInfo meta = MetaInfoFactory.createMetaInfoFor(relPathItem); + if (metaDataCtr != null) { + meta = metaDataCtr.getMetaInfo(meta); + } + meta.setAuthor(ureq.getIdentity().getName()); + meta.clearThumbnails();//if overwrite an older file + meta.write(); + } + ThreadLocalUserActivityLogger.log(FolderLoggingAction.FILE_COPIED, getClass(), CoreLoggingResourceable.wrapUploadFile(fileName)); + + // Notify listeners about upload + fireEvent(ureq, new FolderEvent(FolderEvent.NEW_FILE_EVENT, newFile.getName())); + fireEvent(ureq, FolderCommand.FOLDERCOMMAND_FINISHED); + } + + private boolean askForLock(VFSItem item, UserRequest ureq) { + if(item instanceof MetaTagged) { + MetaInfo info = ((MetaTagged)item).getMetaInfo(); + if(info.isLocked() && !MetaInfoHelper.isLocked(item, ureq)) { + return true; + } + } + return false; + } + + private void fileAlreadyExists(UserRequest ureq) { + renamedFilename = proposedRenamedFilename(existingVFSItem); + + if (existingVFSItem instanceof MetaTagged && MetaInfoHelper.isLocked(existingVFSItem, ureq)) { + //the file is locked and cannot be overwritten + removeAsListenerAndDispose(lockedFileDialog); + lockedFileDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), translate("ul.lockedFile.title"), + translate("ul.lockedFile.text", new String[] {existingVFSItem.getName(), renamedFilename} ), + asList(translate("ul.overwrite.threeoptions.rename", renamedFilename), translate("ul.overwrite.threeoptions.cancel"))); + listenTo(lockedFileDialog); + lockedFileDialog.activate(); + } else if (existingVFSItem instanceof Versionable && ((Versionable)existingVFSItem).getVersions().isVersioned()) { + Versionable versionable = (Versionable)existingVFSItem; + Versions versions = versionable.getVersions(); + String relPath = null; + if(existingVFSItem instanceof OlatRootFileImpl) { + relPath = ((OlatRootFileImpl)existingVFSItem).getRelPath(); + } + int maxNumOfRevisions = FolderConfig.versionsAllowed(relPath); + if(maxNumOfRevisions == 0) { + //it's possible if someone change the configuration + // let calling method decide what to do. + removeAsListenerAndDispose(overwriteDialog); + overwriteDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), translate("ul.overwrite.threeoptions.title"), + translate("ul.overwrite.threeoptions.text", new String[] {existingVFSItem.getName(), renamedFilename} ), + asList(translate("ul.overwrite.threeoptions.overwrite"), translate("ul.overwrite.threeoptions.rename", renamedFilename), + translate("ul.overwrite.threeoptions.cancel"))); + listenTo(overwriteDialog); + + overwriteDialog.activate(); + + } else if(versions.getRevisions().isEmpty() || maxNumOfRevisions < 0 || maxNumOfRevisions > versions.getRevisions().size()) { + // let calling method decide what to do. + removeAsListenerAndDispose(overwriteDialog); + overwriteDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), translate("ul.overwrite.threeoptions.title"), + translate("ul.versionoroverwrite", new String[] {existingVFSItem.getName(), renamedFilename} ), + asList(translate("ul.overwrite.threeoptions.newVersion"), translate("ul.overwrite.threeoptions.rename", renamedFilename), + translate("ul.overwrite.threeoptions.cancel"))); + listenTo(overwriteDialog); + + overwriteDialog.activate(); + + } else { + + String title = translate("ul.tooManyRevisions.title", new String[]{Integer.toString(maxNumOfRevisions), Integer.toString(versions.getRevisions().size())}); + String description = translate("ul.tooManyRevisions.description", new String[]{Integer.toString(maxNumOfRevisions), Integer.toString(versions.getRevisions().size())}); + + removeAsListenerAndDispose(revisionListCtr); + revisionListCtr = new RevisionListController(ureq, getWindowControl(), versionable, title, description); + listenTo(revisionListCtr); + + removeAsListenerAndDispose(revisionListDialogBox); + revisionListDialogBox = new CloseableModalController(getWindowControl(), translate("delete"), revisionListCtr.getInitialComponent()); + listenTo(revisionListDialogBox); + + revisionListDialogBox.activate(); + } + } else { + // let calling method decide what to do. + // for this, we put a list with "existing name" and "new name" + overwriteDialog = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), translate("ul.overwrite.threeoptions.title"), + translate("ul.overwrite.threeoptions.text", new String[] {existingVFSItem.getName(), renamedFilename} ), + asList(translate("ul.overwrite.threeoptions.overwrite"), translate("ul.overwrite.threeoptions.rename", renamedFilename), + translate("ul.overwrite.threeoptions.cancel"))); + listenTo(overwriteDialog); + overwriteDialog.activate(); + } + } + + private String proposedRenamedFilename(VFSLeaf file) { + String currentName = file.getName(); + for(int i=1; i<999; i++) { + String proposition = VFSManager.appendNumberAtTheEndOfFilename(currentName, i); + VFSItem item = folderComponent.getCurrentContainer().resolve(proposition); + if(item == null) { + return proposition; + } + } + return null; + + } +} diff --git a/src/main/java/org/olat/core/commons/modules/bc/FolderConfig.java b/src/main/java/org/olat/core/commons/modules/bc/FolderConfig.java index e6a5b6fffab6c3640e4b1c34958d42ab52077ff1..2ae10e4e40c22bb60542229f1b039207ec0e4c0f 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/FolderConfig.java +++ b/src/main/java/org/olat/core/commons/modules/bc/FolderConfig.java @@ -53,6 +53,8 @@ public class FolderConfig { private static final String TMP_DIR = "/tmp"; private static final String VERSION_DIR = "/.version"; private static FolderVersioningConfigurator versioningConfigurator; + private static boolean sendDocumentToExtern; + private static boolean sendDocumentLinkOnly; /* @@ -206,6 +208,23 @@ public class FolderConfig { public static void setFolderRoot(String newFolderRoot) { folderRoot = newFolderRoot.replace('\\', '/'); } + + /** + * Allow to send document to extern e-mail addresses + * @param sendDocumentToExtern_ + */ + public static void setSendDocumentToExtern(boolean sendDocumentToExtern_) { + sendDocumentToExtern = sendDocumentToExtern_; + } + + /** + * Restrict sending e-mail to links to the documents (which enforce login for + * the recipient) + * @param sendDocumentLinkOnly_ + */ + public static void setSendDocumentLinkOnly(boolean sendDocumentLinkOnly_) { + sendDocumentLinkOnly = sendDocumentLinkOnly_; + } /** * @return the canonical path to the meta root directory. @@ -242,6 +261,23 @@ public class FolderConfig { quotaKB = l; } + /** + * Allow to send document to extern e-mail addresses + * @return true to allow extern e-mail address + */ + public static boolean getSendDocumentToExtern() { + return sendDocumentToExtern; + } + + /** + * Restrict sending e-mail to links to the documents (which enforce login for + * the recipient) + * @return true to restrict to links only + */ + public static boolean getSendDocumentLinkOnly() { + return sendDocumentLinkOnly; + } + public static FolderVersioningConfigurator getVersioningConfigurator() { return versioningConfigurator; } diff --git a/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java b/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java index 7c81a9d9c7e8c860738d698b89583511c020bf77..05fd355b9fd2b1ff1b0db5c78b0ccae7b27eff73 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java +++ b/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java @@ -41,6 +41,8 @@ public class FolderModule extends AbstractOLATModule { private static final String CONFIG_ROOT = "Root"; private static final String CONFIG_LIMITULMB = "LimitULMB"; private static final String CONFIG_QUOTAMB = "QuotaMB"; + private static final String CONFIG_SENDDOCLINKONLY = "SendDocLinkOnly"; + private static final String CONFIG_SENDDOCTOEXTERN = "SendDocToExtern"; private FolderVersioningConfigurator versioning; /** @@ -76,6 +78,14 @@ public class FolderModule extends AbstractOLATModule { FolderConfig.setDefaultQuotaKB(quotaMB * 1024); log.info("Default user quota set to " + FolderConfig.getDefaultQuotaKB() + " KB."); + //set default + boolean sendDocLinkyOnly = getBooleanConfigParameter(CONFIG_SENDDOCLINKONLY, true); + FolderConfig.setSendDocumentLinkOnly(sendDocLinkyOnly); + + //set default + boolean sendDocToExtern = getBooleanConfigParameter(CONFIG_SENDDOCTOEXTERN, false); + FolderConfig.setSendDocumentToExtern(sendDocToExtern); + // create tmp directory File fTmp = new File(FolderConfig.getCanonicalTmpDir()); fTmp.mkdirs(); diff --git a/src/main/java/org/olat/core/commons/modules/bc/FolderRunController.java b/src/main/java/org/olat/core/commons/modules/bc/FolderRunController.java index 2a2ce0905bc0422589b18f4a76d205bcddb67359..9743b5d3d923f9c2d7bc30c2fc4f65bc1d48b7f8 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/FolderRunController.java +++ b/src/main/java/org/olat/core/commons/modules/bc/FolderRunController.java @@ -121,7 +121,7 @@ public class FolderRunController extends BasicController implements Activateable * @param wControl */ public FolderRunController(UserRequest ureq, WindowControl wControl) { - this(new BriefcaseWebDAVProvider().getContainer(ureq.getIdentity()), true, true, ureq, wControl); + this(new BriefcaseWebDAVProvider().getContainer(ureq.getIdentity()), true, true, true, ureq, wControl); } /** @@ -132,7 +132,7 @@ public class FolderRunController extends BasicController implements Activateable * @param wControl */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, UserRequest ureq, WindowControl wControl) { - this(rootContainer, displayWebDAVLink, false, ureq, wControl, null, null); + this(rootContainer, displayWebDAVLink, false, false, ureq, wControl, null, null); } /** @@ -142,8 +142,8 @@ public class FolderRunController extends BasicController implements Activateable * @param ureq * @param wControl */ - public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, UserRequest ureq, WindowControl wControl) { - this(rootContainer, displayWebDAVLink, displaySearch, ureq, wControl, null, null); + public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, WindowControl wControl) { + this(rootContainer, displayWebDAVLink, displaySearch, canMail, ureq, wControl, null, null); } /** @@ -170,12 +170,44 @@ public class FolderRunController extends BasicController implements Activateable * not use this feature. */ public FolderRunController(VFSContainer rootContainer, - boolean displayWebDAVLink, boolean displaySearch, UserRequest ureq, + boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, WindowControl wControl, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel) { + this(rootContainer, displayWebDAVLink, displaySearch, canMail, ureq, wControl, filter, customLinkTreeModel, null); + } + + /** + * Constructor for a folder controller with an optional file filter and an + * optional custom link model for editor. Use this one if you don't wan't to + * display all files in the file browser or if you want to use a custom link + * tree model in the editor. + * + * @param rootContainer + * The folder base. User can not navigate out of this container. + * @param displayWebDAVLink + * true: show the webDAV link; false: hide the webDAV link + * @param displaySearch + * true: display the search field; false: omit the search field. + * Note: for guest users the search is always omitted. + * @param ureq + * The user request object + * @param wControl + * The window control object + * @param filter + * A file filter or NULL to not use a filter + * @param customLinkTreeModel + * A custom link tree model used in the HTML editor or NULL to + * not use this feature. + * @param externContainerForCopy + * A container to copy files from + */ + public FolderRunController(VFSContainer rootContainer, + boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, + WindowControl wControl, VFSItemFilter filter, + CustomLinkTreeModel customLinkTreeModel, VFSContainer externContainerForCopy) { super(ureq, wControl); - + folderContainer = this.createVelocityContainer("run"); editQuotaButton = LinkFactory.createButtonSmall("editQuota", folderContainer, this); @@ -202,7 +234,8 @@ public class FolderRunController extends BasicController implements Activateable } - folderComponent = new FolderComponent(ureq, "foldercomp", rootContainer, filter, customLinkTreeModel); + folderComponent = new FolderComponent(ureq, "foldercomp", rootContainer, filter, customLinkTreeModel, externContainerForCopy); + folderComponent.setCanMail(ureq.getUserSession().getRoles().isGuestOnly() ? false : canMail); // guests can never send mail folderComponent.addListener(this); folderContainer.put("foldercomp", folderComponent); if (WebDAVManager.getInstance().isEnabled() && displayWebDAVLink) diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties index 503b8b03afc8a918a79739253a1926349cbbfe4e..d4911043e3816af342e7f23204816d05d16e7e21 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties @@ -42,13 +42,14 @@ cfile.name.empty=Bitte geben Sie einen Namen ein. cfile.name.example=index.html, info.txt, mystyles.css cfile.name.notvalid=Dieser Dateiname enthält unzulässige Zeichen, bitte entfernen Sie alle Sonderzeichen wie /,:, etc. cfile.name.notvalid.cannot.edit.metadata=Dieser Dateiname enthält unzulässige Zeichen, Sie können die Metadaten daher nicht editieren. +copyfile=Datei kopieren checkall=Alle ausw\u00E4hlen chelp.bc-webdav.title=WebDAV chelp.buddy=<i>$org.olat.group.ui\:BuddyGroup</i> chelp.couFol=<b>coursefolders</b>\: chelp.groFol=<b>groupfolders</b>\: chelp.home=<b>home</b>\: -chelp.homeP=<i>$org.olat.home\:menu.root</i> +chelp.homeP=<i>$org.olat.home\:main.menu.title</i> chelp.how=Wie kann ich WebDAV nutzen?\: chelp.how1=Microsoft Windows XP, Windows Vista und Mac OS X unterst\u00FCtzen WebDAV standardm\u00E4ssig. chelp.how10=Ablageordner aller Kurse, die Sie besitzen. Dies betrifft in der Regel nur Benutzer mit Autorenrechten. @@ -125,6 +126,7 @@ notifications.entry=Datei "{0}" von {1} ver\u00E4ndert notifications.header=In einem von Ihnen abonnierten Ordner befinden sich neue Dateien\: resize_image=Bildgr\u00F6sse f\u00FCr das Web optimieren (1280 x 1280) searchfile=Suchen +send=Mail versenden success=Operation erfolgreich text.element.error.notlongerthan=Dateien und Ordner d\u00FCrfen nicht mehr als 20 Zeichen enthalten. ul=Datei hochladen diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties index d6e144a007884fa4733bcd1eeb5922e613ce126d..4c6431b425725dae09d2306aaaabb9e0fcfc8fd7 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 20 20:19:06 CET 2011 +#Thu May 26 09:42:19 CEST 2011 Directory=Folder FileDeleteFailed=Files/folders <b>{0}</b> could not be deleted. FileDeleted=Files/folders <b>{0}</b> successfully deleted. @@ -48,7 +48,7 @@ chelp.buddy=<i>$org.olat.group.ui\:BuddyGroup</i> chelp.couFol=<b>coursefolders</b>\: chelp.groFol=<b>groupfolders</b>\: chelp.home=<b>home</b>\: -chelp.homeP=<i>$org.olat.home\:menu.root</i> +chelp.homeP=<i>$org.olat.home\:main.menu.title.title</i> chelp.how=How can I use WebDAV? chelp.how1=Microsoft Windows XP, Windows Vista, and Mac OS X usually support WebDAV. chelp.how10=Storage folders of all the courses you own. This usually concerns users with author rights. @@ -82,6 +82,7 @@ chelp.why2=By means of WebDAV you can copy single files or entire directories fr command.closepreview=Close preview command.preview=Show preview copy=Copy +copyfile=Copy files del=Delete del.confirm=Do you really want to delete the following files? del.header=Confirm deletion @@ -127,6 +128,7 @@ notifications.entry=File "{0}" modified by {1} notifications.header=There are new files in a folder subscribed by you\: resize_image=Optimize an image size for the Web (1280 x 1280) searchfile=Search +send=Send e-mail success=Operation successful text.element.error.notlongerthan=File and folder names must not contain more than 20 characters. ul=Upload file diff --git a/src/main/java/org/olat/core/commons/modules/bc/_spring/folderModuleCorecontext.xml b/src/main/java/org/olat/core/commons/modules/bc/_spring/folderModuleCorecontext.xml index 963f7efe743d0b7977a3da0d24f72e7cf0af7ff0..4676cefbe5e9909016ba055511d4fb19cd3b430c 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/_spring/folderModuleCorecontext.xml +++ b/src/main/java/org/olat/core/commons/modules/bc/_spring/folderModuleCorecontext.xml @@ -32,6 +32,10 @@ QuotaMB=${folder.quotamb} <!-- The personal folder's root relative to the user data root. --> Root=${folder.root} + <!-- Allow to send document to extern people --> + SendDocToExtern=${folder.sendDocumentToExtern} + <!-- Restrict sending document to links to enforce authentication --> + SendDocLinkOnly=${folder.sendDocumentLinkOnly} </value> </property> </bean> diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCopyFile.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCopyFile.java new file mode 100644 index 0000000000000000000000000000000000000000..5da3a04702b5c7f9784609827231b7a2f4f74535 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCopyFile.java @@ -0,0 +1,95 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ + +package org.olat.core.commons.modules.bc.commands; + +import org.olat.core.commons.modules.bc.FileCopyController; +import org.olat.core.commons.modules.bc.components.FolderComponent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.translator.Translator; +import org.olat.core.logging.AssertException; +import org.olat.core.util.WebappHelper; +import org.olat.core.util.vfs.VFSConstants; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSManager; + +/** + * + * Description:<br> + * Copy a file from an external folder + * + * <P> + * Initial Date: 17 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class CmdCopyFile extends BasicController implements FolderCommand { + + private int status = FolderCommandStatus.STATUS_SUCCESS; + + protected CmdCopyFile(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + } + + @Override + public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator translator) { + setTranslator(translator); + if (folderComponent.getCurrentContainer().canWrite() != VFSConstants.YES) { + throw new AssertException("Illegal attempt to create file in: " + folderComponent.getCurrentContainerPath()); + } + + //check for quota + long quotaLeft = VFSManager.getQuotaLeftKB(folderComponent.getCurrentContainer()); + if (quotaLeft < -2) { + String supportAddr = WebappHelper.getMailConfig("mailSupport"); + String msg = translate("QuotaExceededSupport", new String[] { supportAddr }); + getWindowControl().setError(msg); + return null; + } + + VFSContainer cContainer = folderComponent.getExternContainerForCopy(); + return new FileCopyController(ureq, wControl, cContainer, folderComponent); + } + + @Override + protected void doDispose() { + // + } + + @Override + public void event(UserRequest ureq, Component source, Event event) { + //empty + } + + @Override + public int getStatus() { + return status; + } + + @Override + public boolean runsModal() { + return false; + } +} diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java index c164c48b35ec765625c3ff4fddb8932dbf1dec83..fafcb220bec8840a4b67e19006019d24c2dec23f 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java @@ -102,7 +102,7 @@ public class CmdCreateFile extends BasicController implements FolderCommand { //check for quota long quotaLeft = VFSManager.getQuotaLeftKB(folderComponent.getCurrentContainer()); - if (quotaLeft < -2) { + if (quotaLeft <= 0 && quotaLeft != -1 ) { String supportAddr = WebappHelper.getMailConfig("mailSupport"); String msg = translate("QuotaExceededSupport", new String[] { supportAddr }); this.getWindowControl().setError(msg); diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdSendMail.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdSendMail.java new file mode 100644 index 0000000000000000000000000000000000000000..cec627c26ad914cd7c8e339a113b48bd6cc6d806 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdSendMail.java @@ -0,0 +1,37 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.commons.modules.bc.commands; + + +/** + * + * Description:<br> + * Open a panel with the list of deleted files of the selected container. The panel + * can delete definitively a deleted and versioned file and restore them. + * + * <P> + * Initial Date: 21 sept. 2009 <br> + * + * @author srosse + */ +public interface CmdSendMail extends FolderCommand { + +} diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java index 9565d4ede912e47d988dadd37b46a604cc99d9f7..2dc513708565c042cc380b8c94195fe1c0448744 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java @@ -137,7 +137,7 @@ public class CmdUpload extends BasicController implements FolderCommand { else if (quotaKB - actualUsage < 0) remainingQuotaKB = 0; else remainingQuotaKB = (int) quotaKB - (int) actualUsage; removeAsListenerAndDispose(fileUploadCtr); - + fileUploadCtr = new FileUploadController(getWindowControl(), currentContainer, ureq, uploadLimitKB, remainingQuotaKB, null, true, showMetadata, true, showCancel); listenTo(fileUploadCtr); mainVC.put("fileUploadCtr", fileUploadCtr.getInitialComponent()); diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java b/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java index 3e7515b0321322a93202a4e51e813bea675fb5bf..9188e7bb2bb1e545265044e5dc45765f25b46d03 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java +++ b/src/main/java/org/olat/core/commons/modules/bc/commands/FolderCommandFactory.java @@ -33,6 +33,7 @@ public class FolderCommandFactory { public static final String COMMAND_UPLOAD = "ul"; public static final String COMMAND_CREATEFOLDER = "cf"; public static final String COMMAND_CREATEFILE = "cfile"; + public static final String COMMAND_COPYFILE = "copyfile"; public static final String COMMAND_SERV = "serv"; public static final String COMMAND_EDIT = "edt"; public static final String COMMAND_EDIT_CONTENT = "editContent"; @@ -47,6 +48,7 @@ public class FolderCommandFactory { public static final String COMMAND_MOVE = "move"; public static final String COMMAND_COPY = "copy"; public static final String COMMAND_DEL = "del"; + public static final String COMMAND_MAIL = "mail"; public static final String COMMAND_ZIP = "zip"; public static final String COMMAND_UNZIP = "unzip"; @@ -65,6 +67,7 @@ public class FolderCommandFactory { FolderCommand cmd = null; if (command.equals(COMMAND_CREATEFOLDER)) cmd = new CmdCreateFolder(ureq,wControl); else if (command.equals(COMMAND_CREATEFILE)) cmd = new CmdCreateFile(ureq,wControl); + else if (command.equals(COMMAND_COPYFILE)) cmd = new CmdCopyFile(ureq,wControl); else if (command.equals(COMMAND_UPLOAD)) cmd = new CmdUpload(ureq, wControl, true); else if (command.equals(COMMAND_SERV)) cmd = new CmdServeResource(); else if (command.equals(COMMAND_SERV_THUMBNAIL)) cmd = new CmdServeThumbnailResource(); @@ -72,6 +75,10 @@ public class FolderCommandFactory { else if (command.equals(COMMAND_EDIT_CONTENT)) cmd = new CmdEditContent(ureq, wControl); else if (command.equals(COMMAND_EDIT_QUOTA)) cmd = new CmdEditQuota(wControl); else if (command.equals(COMMAND_DEL)) cmd = new CmdDelete(ureq, wControl); + else if (command.equals(COMMAND_MAIL)) { + AutoCreator controllerCreator = (AutoCreator)CoreSpringFactory.getBean("sendDocumentByEMailControllerCreator"); + cmd = (CmdSendMail)controllerCreator.createController(ureq, wControl); + } else if (command.equals(COMMAND_MOVE)) cmd = new CmdMoveCopy(wControl, true); else if (command.equals(COMMAND_COPY)) cmd = new CmdMoveCopy(wControl, false); else if (command.equals(COMMAND_ZIP)) cmd = new CmdZip(ureq,wControl); diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java index 33cc4110ea7c6b135636d69c27a1541b30112139..1f2fc9274f79c1069ee80b9839d0dbd4e976b67a 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java +++ b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponent.java @@ -75,11 +75,12 @@ public class FolderComponent extends Component { // attached files anywhere at the time of deleting it // likely to be resolved after user logs out, caches get cleared - and if not the server // restart overnight definitely removes those .nfs files. - protected static final String[] ATTACHMENT_EXCLUDE_PREFIXES = new String[]{".nfs", ".CVS", ".DS_Store"}; + // fxdiff: FXOLAT-333 hide all shadow-files per default + protected static final String[] ATTACHMENT_EXCLUDE_PREFIXES = new String[]{"."}; protected boolean sortAsc = true; // asc or desc? protected String sortCol = ""; // column to sort - + protected boolean canMail = false; private IdentityEnvironment identityEnv; private VFSContainer rootContainer; @@ -94,6 +95,7 @@ public class FolderComponent extends Component { private final DateFormat dateTimeFormat; private VFSItemExcludePrefixFilter exclFilter; private CustomLinkTreeModel customLinkTreeModel; + private final VFSContainer externContainerForCopy; /** * Wraps the folder module as a component. @@ -113,10 +115,17 @@ public class FolderComponent extends Component { public FolderComponent(UserRequest ureq, String name, VFSContainer rootContainer, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel) { + this(ureq, name, rootContainer, filter, customLinkTreeModel, null); + } + + public FolderComponent(UserRequest ureq, String name, + VFSContainer rootContainer, VFSItemFilter filter, + CustomLinkTreeModel customLinkTreeModel, VFSContainer externContainerForCopy) { super(name); this.identityEnv = ureq.getUserSession().getIdentityEnvironment(); this.filter = filter; this.customLinkTreeModel = customLinkTreeModel; + this.externContainerForCopy = externContainerForCopy; exclFilter = new VFSItemExcludePrefixFilter(ATTACHMENT_EXCLUDE_PREFIXES); Locale locale = ureq.getLocale(); collator = Collator.getInstance(locale); @@ -182,6 +191,14 @@ public class FolderComponent extends Component { sortAsc = !sortAsc; } } + + public boolean isCanMail() { + return canMail; + } + + public void setCanMail(boolean canMail) { + this.canMail = canMail; + } /** * Sorts the bc folder components table @@ -436,6 +453,10 @@ public class FolderComponent extends Component { return this.customLinkTreeModel; } + public VFSContainer getExternContainerForCopy() { + return externContainerForCopy; + } + /** * * @see org.olat.core.gui.components.Component#validate(org.olat.core.gui.UserRequest, org.olat.core.gui.render.ValidationResult) diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java index 2514af0213d2e31a8a16911bb8f1a1e89e4ba0ad..8f101df8ba7f3204dcfce1ae55b0703ecd3d255c 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java +++ b/src/main/java/org/olat/core/commons/modules/bc/components/FolderComponentRenderer.java @@ -90,6 +90,7 @@ public class FolderComponentRenderer implements ComponentRenderer { boolean canWrite = currentContainer.canWrite() == VFSConstants.YES; boolean canDelete = false; boolean canVersion = FolderConfig.versionsEnabled(fc.getCurrentContainer()); + boolean canMail = fc.isCanMail(); for (Iterator<VFSItem> iter = fc.getCurrentContainerChildren().iterator(); iter.hasNext();) { VFSItem child = iter.next(); if (child.canDelete() == VFSConstants.YES) { @@ -131,6 +132,22 @@ public class FolderComponentRenderer implements ComponentRenderer { } if(canWrite) { + if(fc.getExternContainerForCopy() != null && (fc.getExternContainerForCopy().getLocalSecurityCallback() == null || + fc.getExternContainerForCopy().getLocalSecurityCallback().canCopy())) { + //option copy file + target.append("<li><a class=\"b_briefcase_newfile\" href=\""); + ubu.buildURI(target, new String[] { VelocityContainer.COMMAND_ID }, new String[] { "copyfile" }, iframePostEnabled ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL); + target.append("\""); + if (iframePostEnabled) { // add ajax iframe target + StringOutput so = new StringOutput(); + ubu.appendTarget(so); + target.append(so.toString()); + } + target.append(">"); + target.append(translator.translate("copyfile")); + target.append("</a></li>"); + } + // option upload target.append("<li><a class=\"b_briefcase_upload\" href=\""); ubu.buildURI(target, new String[] { VelocityContainer.COMMAND_ID }, new String[] { "ul" }, iframePostEnabled ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL); @@ -181,7 +198,7 @@ public class FolderComponentRenderer implements ComponentRenderer { target.append(listRenderer.render(fc, ubu, translator, iframePostEnabled)); if (fc.getCurrentContainerChildren().size() > 0) { - if (canWrite || canDelete) { + if (canWrite || canDelete || canMail) { target.append("<div class=\"b_togglecheck\">"); @@ -194,6 +211,16 @@ public class FolderComponentRenderer implements ComponentRenderer { target.append("</a></div>"); target.append("<div class=\"b_briefcase_commandbuttons b_button_group\">"); + + //fxdiff BAKS-2: send documents by mail + if(canMail) { + target.append("<input type=\"submit\" class=\"b_button\" name=\""); + target.append(FolderRunController.ACTION_PRE).append(FolderCommandFactory.COMMAND_MAIL); + target.append("\" value=\""); + target.append(StringEscapeUtils.escapeHtml(translator.translate("send"))); + target.append("\"/>"); + } + if (canDelete) { // delete target.append("<input type=\"submit\" class=\"b_button\" name=\""); diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfo.java b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfo.java index aa48b55f661eb567264a732429818fdb7b307d7b..7ecb201f2b0c1c36f1bdcd546e8a2db11eed1c27 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfo.java +++ b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfo.java @@ -52,6 +52,12 @@ public interface MetaInfo { * @return True upon success. */ public boolean delete(); + + /** + * fxdiff: + * @return the unique id of the file or create one if it previously not exists + */ + public String getUUID(); /** * @return name of the initial author (OLAT user name) diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFileImpl.java b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFileImpl.java index 862ea8eb21a20b4d06372284bbb4eb6ee9d0d505..a220ea8e587c04355b00ee8b8355dc01b6864243 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFileImpl.java +++ b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFileImpl.java @@ -24,6 +24,7 @@ package org.olat.core.commons.modules.bc.meta; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -34,7 +35,9 @@ import java.io.StringReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.xml.parsers.SAXParser; @@ -91,6 +94,7 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { } // meta data + private String uuid; private Long authorIdentKey = null; private Long lockedByIdentKey = null; private String comment = ""; @@ -153,6 +157,9 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { if (!parseSAX(metaFile)) { String metaDirPath = canonicalMetaPath.substring(0, canonicalMetaPath.lastIndexOf('/')); new File(metaDirPath).mkdirs(); + if(uuid == null) { + generateUUID(); + } write(); } return true; @@ -212,7 +219,63 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { } if (move) FileUtils.moveFileToDir(fSource, fTarget); - else FileUtils.copyFileToDir(fSource, fTarget, "meta info"); + else { + //copy + Map<String,String> pathToUuid = new HashMap<String,String>(); + File mTarget = new File(fTarget, fSource.getName()); + collectUUIDRec(mTarget, pathToUuid); + + if(FileUtils.copyFileToDir(fSource, fTarget, "copy metadata")) { + File endTarget = new File(fTarget, fSource.getName()); + generateUUIDRec(endTarget, pathToUuid); + } + } + } + + private void collectUUIDRec(File mTarget, Map<String,String> pathToUuid) { + try { + if(mTarget.exists()) { + if(mTarget.isDirectory()) { + //TODO + } else { + MetaInfoFileImpl copyMeta = new MetaInfoFileImpl(); + copyMeta.metaFile = mTarget; + if (copyMeta.parseSAX(mTarget)) { + pathToUuid.put(mTarget.getCanonicalPath(), copyMeta.getUUID()); + } + } + } + } catch (IOException e) { + log.error("cannot collect current UUID before copy", e); + } + } + + private void generateUUIDRec(File endTarget, Map<String,String> pathToUuid) { + if(!endTarget.exists()) { + return; + } + + try { + if(endTarget.isDirectory()) { + for(File subEndTarget:endTarget.listFiles(new XmlFilter())) { + generateUUIDRec(subEndTarget, pathToUuid); + } + } else { + MetaInfoFileImpl copyMeta = new MetaInfoFileImpl(); + copyMeta.metaFile = endTarget; + if (copyMeta.parseSAX(endTarget)) { + String tempUuid = pathToUuid.get(endTarget.getCanonicalPath()); + if(StringHelper.containsNonWhitespace(tempUuid)) { + copyMeta.uuid =tempUuid; + } else { + copyMeta.generateUUID(); + } + copyMeta.write(); + } + } + } catch (IOException e) { + log.error("Cannot generate a new uuid on copy", e); + } } /** @@ -293,7 +356,11 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { bos = new BufferedOutputStream(new FileOutputStream(metaFile)); OutputStreamWriter sw = new OutputStreamWriter(bos, Charset.forName("UTF-8")); sw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); - sw.write("<meta>"); + sw.write("<meta"); + if(StringHelper.containsNonWhitespace(uuid)) { + sw.write(" uuid=\"" + uuid + "\""); + } + sw.write(">"); sw.write("<author><![CDATA[" + (authorIdentKey == null ? "" : authorIdentKey.toString()) + "]]></author>"); sw.write("<lock locked=\"" + locked + "\"" + (lockedDate == null ? "" : " date=\"" + lockedDate.getTime() + "\"") + "><![CDATA[" + (lockedByIdentKey == null ? "" : lockedByIdentKey) + "]]></lock>"); sw.write("<comment><![CDATA[" + filterForCData(comment) + "]]></comment>"); @@ -378,6 +445,10 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { synchronized(saxParser) { in = new FileInputStream(fMeta); saxParser.parse(in, this); + if(uuid == null) { + generateUUID(); + write(); + } } } catch (SAXParseException ex) { if(!parseSAXFiltered(fMeta)) { @@ -518,6 +589,15 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { } } + @Override + public String getUUID() { + return uuid; + } + + public void generateUUID() { + uuid = UUID.randomUUID().toString().replace("-", ""); + } + /** * @see org.olat.core.commons.modules.bc.meta.MetaInfo#getAuthorIdentity() */ @@ -860,7 +940,9 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { @Override public final void startElement(String uri, String localName, String qName, Attributes attributes) { - if ("lock".equals(qName)) { + if("meta".equals(qName)) { + uuid = attributes.getValue("uuid"); + } else if ("lock".equals(qName)) { locked ="true".equals(attributes.getValue("locked")); String date = attributes.getValue("date"); if (date != null && date.length() > 0) { @@ -956,6 +1038,13 @@ public class MetaInfoFileImpl extends DefaultHandler implements MetaInfo { return cssClass; } + public class XmlFilter implements FileFilter { + @Override + public boolean accept(File file) { + return file.getName().endsWith(".xml"); + } + } + public class Thumbnail { private int maxWidth; private int maxHeight; diff --git a/src/main/java/org/olat/core/commons/modules/glossary/Author.java b/src/main/java/org/olat/core/commons/modules/glossary/Author.java new file mode 100644 index 0000000000000000000000000000000000000000..21bc326db5330f5bc9dd537a1a153d9a01a02295 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/Author.java @@ -0,0 +1,87 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.core.commons.modules.glossary; + +import org.olat.core.id.Identity; +import org.olat.core.id.UserConstants; +import org.olat.core.util.StringHelper; + +/** + * + * Description:<br> + * TODO: srosse Class Description for Author + * + * <P> + * Initial Date: 15 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class Author { + + private String firstname; + private String surname; + private String link; + + public Author() { + // + } + + public Author(Identity identity) { + firstname = identity.getUser().getProperty(UserConstants.FIRSTNAME, null); + surname = identity.getUser().getProperty(UserConstants.LASTNAME, null); + link = "[Identity:" + identity.getKey() + "][Username:" + identity.getName() + "]"; + } + + public Long extractKey() { + if(StringHelper.containsNonWhitespace(link)) { + int indexId = link.indexOf("[Identity:"); + int indexUsername = link.indexOf("][Username:"); + if(indexId >= 0 && indexUsername > indexId) { + String keyString = link.substring(indexId + 10, indexUsername); + return Long.parseLong(keyString); + } + } + return null; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } +} diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItem.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItem.java index 294b874259a23f2a778c8a8be256b6d4b24cc836..a09da94718d1c5739bcf5bf4ee9a8bb9e9c44633 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItem.java +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItem.java @@ -23,6 +23,7 @@ import java.net.URI; import java.text.Collator; import java.text.Normalizer; import java.util.ArrayList; +import java.util.List; import java.util.Locale; /** @@ -42,6 +43,7 @@ public class GlossaryItem implements Comparable<Object> { private ArrayList<String> glossFlexions; private ArrayList<String> glossSynonyms; private ArrayList<URI> glossLinks; + private List<Revision> revHistory; public GlossaryItem(String glossTerm, String glossDef) { super(); @@ -113,6 +115,23 @@ public class GlossaryItem implements Comparable<Object> { return getGlossTerm(); } + /** + * @return Return the list of revisions + */ + public List<Revision> getRevHistory() { + if(revHistory == null) { + revHistory = new ArrayList<Revision>(); + } + return revHistory; + } + + /** + * @param revHistory The list of revisions + */ + public void setRevHistory(List<Revision> revHistory) { + this.revHistory = revHistory; + } + /** * @return Returns the glossFlexions. */ diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemEditorController.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemEditorController.java index f924a6c9b69f1290607f4c1820f162bb24454b42..d623e575f3d07840374c03b6722a744039f4f42f 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemEditorController.java +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemEditorController.java @@ -57,7 +57,8 @@ public class GlossaryItemEditorController extends BasicController implements Act * @param glossaryItemList * @param glossaryItem to be null, if a new Item should be generated and added to List */ - protected GlossaryItemEditorController(UserRequest ureq, WindowControl control, VFSContainer glossaryFolder, List<GlossaryItem> glossaryItemList, GlossaryItem glossaryItem) { + protected GlossaryItemEditorController(UserRequest ureq, WindowControl control, VFSContainer glossaryFolder, List<GlossaryItem> glossaryItemList, GlossaryItem glossaryItem, + boolean add) { super(ureq, control); editorVC = createVelocityContainer("editor"); @@ -71,7 +72,7 @@ public class GlossaryItemEditorController extends BasicController implements Act glossEditTabP = new TabbedPane("tp", ureq.getLocale()); - itmCtrl = new GlossaryTermAndSynonymController(ureq, control, glossaryItem, glossaryFolder); + itmCtrl = new GlossaryTermAndSynonymController(ureq, control, glossaryItem, glossaryFolder, add); listenTo(itmCtrl); glossEditTabP.addTab(translate("term.and.synonyms.title"), itmCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemManager.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemManager.java index 624c0a8756077bf702ce0b9dcf264e3f34a12e16..a937f598f4144e722ce52c211d934b5e15198d67 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemManager.java +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryItemManager.java @@ -68,10 +68,12 @@ public class GlossaryItemManager extends BasicManager { private static final String OLD_GLOSSARY_FILENAME = "glossary.textmarker.xml"; private static final String GLOSSARY_FILENAME = "glossary.xml"; private static final String XML_GLOSSARY_ITEM_NAME = "glossentry"; + private static final String XML_REVISION_NAME = "revision"; private static final String GLOSSARY_CONFIG_PROPERTIES_FILE = "glossary.properties"; public static final String NO_MS_VALUE = "ms-none"; public static final String MS_KEY = "morphological.service.identifier"; public static final String REGISTER_ONOFF = "register.index.enabled"; + public static final String EDIT_USERS = "edit.by.users.enabled"; private static final OLATResourceable glossaryEventBus = OresHelper.createOLATResourceableType("glossaryEventBus"); CacheWrapper glossaryCache; private CoordinatorManager coordinatorManager; @@ -225,6 +227,7 @@ public class GlossaryItemManager extends BasicManager { }); xstream.alias(XML_GLOSSARY_ITEM_NAME, GlossaryItem.class); + xstream.alias(XML_REVISION_NAME, Revision.class); glossaryItemArr = removeEmptyGlossaryItems(glossaryItemArr); XStreamHelper.writeObject(xstream, glossaryFile, glossaryItemArr); } @@ -290,6 +293,7 @@ public class GlossaryItemManager extends BasicManager { if (glossaryFile == null) { return new ArrayList<GlossaryItem>(); } XStream xstream = XStreamHelper.createXStreamInstance(); xstream.alias(XML_GLOSSARY_ITEM_NAME, GlossaryItem.class); + xstream.alias(XML_REVISION_NAME, Revision.class); Object glossObj = XStreamHelper.readObject(xstream, glossaryFile.getInputStream()); if (glossObj instanceof ArrayList) { ArrayList<GlossaryItem> glossItemsFromFile = (ArrayList<GlossaryItem>) glossObj; diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryMainController.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryMainController.java index 4e10f657e8942a44965d977621d4d98662a0efa7..d6b659392e106bab34224fbb3cd812a092d69872 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryMainController.java +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryMainController.java @@ -19,8 +19,10 @@ */ package org.olat.core.commons.modules.glossary; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Properties; @@ -41,10 +43,14 @@ import org.olat.core.gui.control.generic.dtabs.Activateable; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.OLATResourceable; -import org.olat.core.logging.activity.LearningResourceLoggingAction; +import org.olat.core.id.context.BusinessControl; +import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.logging.activity.CoreLoggingResourceable; +import org.olat.core.logging.activity.LearningResourceLoggingAction; import org.olat.core.logging.activity.OlatResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.vfs.VFSContainer; @@ -62,7 +68,8 @@ public class GlossaryMainController extends BasicController implements Activatea private VelocityContainer glistVC; private Link addButton; private LockResult lockEntry = null; - private boolean editModeEnabled; + private final GlossarySecurityCallback glossarySecCallback; + private final boolean eventProfil; private DialogBoxController deleteDialogCtr; private Controller glossEditCtrl; private ArrayList<GlossaryItem> glossaryItemList; @@ -73,19 +80,23 @@ public class GlossaryMainController extends BasicController implements Activatea private OLATResourceable resourceable; private static final String CMD_EDIT = "cmd.edit."; private static final String CMD_DELETE = "cmd.delete."; + private static final String CMD_AUTHOR = "cmd.author."; + private static final String CMD_MODIFIER = "cmd.modifier."; private static final String REGISTER_LINK = "register.link."; + private final Formatter formatter; - public GlossaryMainController(WindowControl control, UserRequest ureq, VFSContainer glossaryFolder, OLATResourceable res, boolean allowGlossaryEditing) { + public GlossaryMainController(WindowControl control, UserRequest ureq, VFSContainer glossaryFolder, OLATResourceable res, + GlossarySecurityCallback glossarySecCallback, boolean eventProfil) { super(ureq, control); - this.editModeEnabled = allowGlossaryEditing; + this.glossarySecCallback = glossarySecCallback; this.glossaryFolder = glossaryFolder; + this.eventProfil = eventProfil; this.resourceable = res; addLoggingResourceable(CoreLoggingResourceable.wrap(res, OlatResourceableType.genRepoEntry)); ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_OPEN, getClass()); glistVC = createVelocityContainer("glossarylist"); - addButton = LinkFactory.createButtonSmall("cmd.add", glistVC, this); - initEditView(ureq, allowGlossaryEditing); + formatter = Formatter.getInstance(getLocale()); glossaryItemList = GlossaryItemManager.getInstance().getGlossaryItemListByVFSItem(glossaryFolder); Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); @@ -93,7 +104,12 @@ public class GlossaryMainController extends BasicController implements Activatea glistVC.contextPut("registerEnabled", registerEnabled); if (!registerEnabled) { filterIndex = "all"; - } + } + glistVC.contextPut("userAllowToEditEnabled", new Boolean(glossarySecCallback.isUserAllowToEditEnabled())); + + addButton = LinkFactory.createButtonSmall("cmd.add", glistVC, this); + initEditView(ureq, glossarySecCallback.canAdd()); + updateRegisterAndGlossaryItems(); Link showAllLink = LinkFactory.createCustomLink(REGISTER_LINK + "all", REGISTER_LINK + "all", "glossary.list.showall", Link.LINK, @@ -166,7 +182,7 @@ public class GlossaryMainController extends BasicController implements Activatea protected void event(UserRequest ureq, Component source, Event event) { if (source == addButton) { removeAsListenerAndDispose(glossEditCtrl); - glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, null); + glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, null, true); listenTo(glossEditCtrl); removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), "close", glossEditCtrl.getInitialComponent()); @@ -179,7 +195,7 @@ public class GlossaryMainController extends BasicController implements Activatea GlossaryItem currentGlossaryItem = (GlossaryItem) button.getUserObject(); if (cmd.startsWith(CMD_EDIT)) { removeAsListenerAndDispose(glossEditCtrl); - glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, currentGlossaryItem); + glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, currentGlossaryItem, false); listenTo(glossEditCtrl); removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), "close", glossEditCtrl.getInitialComponent()); @@ -193,14 +209,34 @@ public class GlossaryMainController extends BasicController implements Activatea deleteDialogCtr = activateYesNoDialog(ureq, null, translate("glossary.delete.dialog", currentGlossaryItem.getGlossTerm()), deleteDialogCtr); } - } - else if (button.getCommand().startsWith(REGISTER_LINK)) { + } else if (button.getCommand().startsWith(REGISTER_LINK)) { filterIndex = cmd.substring(cmd.lastIndexOf(".") + 1); updateRegisterAndGlossaryItems(); } + } else if (source == glistVC) { + String cmd = event.getCommand(); + if(cmd.startsWith(CMD_AUTHOR)) { + String url = event.getCommand().substring(CMD_AUTHOR.length()); + openProfil(ureq, url, true); + } else if (cmd.startsWith(CMD_MODIFIER)) { + String url = event.getCommand().substring(CMD_MODIFIER.length()); + openProfil(ureq, url, false); + } + } + } + + private void openProfil(UserRequest ureq, String pos, boolean author) { + int id = Integer.parseInt(pos); + + List<GlossaryItemWrapper> wrappers = (List<GlossaryItemWrapper>)glistVC.getContext().get("editAndDelButtonList"); + for(GlossaryItemWrapper wrapper:wrappers) { + if(id == wrapper.getId()) { + Revision revision = author ? wrapper.getAuthorRevision() : wrapper.getModifierRevision(); + Long identityKey = revision.getAuthor().extractKey(); + fireEvent(ureq, new OpenAuthorProfilEvent(identityKey)); + } } - } @Override @@ -246,11 +282,11 @@ public class GlossaryMainController extends BasicController implements Activatea * @return a list (same size as GlossaryItems) which contains again lists with * one editButton and one deleteButton */ - private List<List<Link>> updateView(ArrayList<GlossaryItem> gIList, String choosenFilterIndex) { - List<List<Link>> editAndDelButtonList = new ArrayList<List<Link>>(gIList.size()); + private List<GlossaryItemWrapper> updateView(ArrayList<GlossaryItem> gIList, String choosenFilterIndex) { int linkNum = 1; Set<String> keys = new HashSet<String>(); StringBuilder bufDublicates = new StringBuilder(); + List<GlossaryItemWrapper> items = new ArrayList<GlossaryItemWrapper>(); Collections.sort(gIList); glistVC.contextPut("filterIndex", choosenFilterIndex); @@ -263,30 +299,31 @@ public class GlossaryMainController extends BasicController implements Activatea } for (GlossaryItem gi : gIList) { - Link tmpEditButton = LinkFactory.createCustomLink(CMD_EDIT + linkNum, CMD_EDIT + linkNum, "cmd.edit", Link.BUTTON_SMALL, glistVC, + boolean canEdit = glossarySecCallback.canEdit(gi); + if(canEdit) { + Link tmpEditButton = LinkFactory.createCustomLink(CMD_EDIT + linkNum, CMD_EDIT + linkNum, "cmd.edit", Link.BUTTON_SMALL, glistVC, this); - tmpEditButton.setUserObject(gi); - Link tmpDelButton = LinkFactory.createCustomLink(CMD_DELETE + linkNum, CMD_DELETE + linkNum, "cmd.delete", Link.BUTTON_SMALL, + tmpEditButton.setUserObject(gi); + Link tmpDelButton = LinkFactory.createCustomLink(CMD_DELETE + linkNum, CMD_DELETE + linkNum, "cmd.delete", Link.BUTTON_SMALL, glistVC, this); - tmpDelButton.setUserObject(gi); - List<Link> tmpList = new ArrayList<Link>(2); - tmpList.add(tmpEditButton); - tmpList.add(tmpDelButton); - + tmpDelButton.setUserObject(gi); + } + + GlossaryItemWrapper wrapper = new GlossaryItemWrapper(gi, linkNum); if (keys.contains(gi.getGlossTerm()) && (bufDublicates.indexOf(gi.getGlossTerm()) == -1)) { bufDublicates.append(gi.getGlossTerm()); bufDublicates.append(" "); } else { keys.add(gi.getGlossTerm()); } - editAndDelButtonList.add(tmpList); + items.add(wrapper); linkNum++; } - if ((bufDublicates.length() > 0) && editModeEnabled) { + if ((bufDublicates.length() > 0) && glossarySecCallback.canAdd()) { showWarning("warning.contains.dublicates", bufDublicates.toString()); } - return editAndDelButtonList; + return items; } /** @@ -307,5 +344,156 @@ public class GlossaryMainController extends BasicController implements Activatea } } } + + public class GlossaryItemWrapper { + + private final int id; + private final GlossaryItem delegate; + + public GlossaryItemWrapper(GlossaryItem delegate, int id) { + this.delegate = delegate; + this.id = id; + } + + public int getId() { + return id; + } + + public String getIndex() { + return delegate.getIndex(); + } + + public boolean hasAuthor() { + Revision authorRev = getAuthorRevision(); + return authorRev != null && StringHelper.containsNonWhitespace(authorRev.getAuthor().getLink()); + } + + public String getAuthorName() { + Revision authorRev = getAuthorRevision(); + return authorRev == null ? null : getRevisionAuthorFullName(authorRev); + } + + public String getAuthorCmd() { + return eventProfil ? CMD_AUTHOR + id : null; + } + + public String getAuthorLink() { + Revision authorRev = getAuthorRevision(); + return getLink(authorRev); + } + + public boolean hasModifier() { + Revision modifierRev = getModifierRevision(); + return modifierRev != null && StringHelper.containsNonWhitespace(modifierRev.getAuthor().getLink()); + } + + public String getModifierName() { + Revision modifierRev = getModifierRevision(); + return modifierRev == null ? null : getRevisionAuthorFullName(modifierRev); + } + + public String getModifierCmd() { + return eventProfil ? CMD_MODIFIER + id : null; + } + + public String getModifierLink() { + Revision modifierRev = getModifierRevision(); + return getLink(modifierRev); + } + + public String getCreationDate() { + Revision authorRev = getAuthorRevision(); + return getMessageDate(authorRev); + } + + public String getLastModificationDate() { + Revision modifierRev = getModifierRevision(); + return getMessageDate(modifierRev); + } + + private String getMessageDate(Revision rev) { + if(rev == null) return ""; + Date date = rev.getJavaDate(); + if(date == null) return ""; + String dateStr = formatter.formatDate(date); + return translate("glossary.item.at", new String[]{ dateStr }); + } + public List<Revision> getRevHistory() { + return delegate.getRevHistory(); + } + + public ArrayList<String> getGlossFlexions() { + return delegate.getGlossFlexions(); + } + + public ArrayList<String> getGlossSynonyms() { + return delegate.getGlossSynonyms(); + } + + public String getGlossDef() { + return delegate.getGlossDef(); + } + + public ArrayList<URI> getGlossLinks() { + return delegate.getGlossLinks(); + } + + public ArrayList<GlossaryItem> getGlossSeeAlso() { + return delegate.getGlossSeeAlso(); + } + + public String getGlossTerm() { + return delegate.getGlossTerm(); + } + + private String getLink(Revision rev) { + if(rev == null || rev.getAuthor() == null) return null; + String url = rev.getAuthor().getLink(); + if(StringHelper.containsNonWhitespace(url) && url.startsWith("[") && url.endsWith("]")) { + int indexUsername = url.indexOf("[Username:"); + if(indexUsername > 0) { + url = url.substring(0, indexUsername); + } + BusinessControl bc = BusinessControlFactory.getInstance().createFromString(url); + return BusinessControlFactory.getInstance().getAsURIString(bc, true); + } + return null; + } + + public Revision getAuthorRevision() { + List<Revision> revisions = delegate.getRevHistory(); + if(revisions == null || revisions.isEmpty()) return null; + Revision revision = revisions.get(0); + if(revision.getAuthor() != null && "added".equals(revision.getRevisionflag())) { + return revision; + } + return null; + } + + public Revision getModifierRevision() { + List<Revision> revisions = delegate.getRevHistory(); + if(revisions == null || revisions.isEmpty()) return null; + + Revision lastRevision = revisions.get(revisions.size() - 1); + if(lastRevision.getAuthor() != null && "changed".equals(lastRevision.getRevisionflag())) { + return lastRevision; + } + return null; + } + + private String getRevisionAuthorFullName(Revision revision) { + if(revision == null || revision.getAuthor() == null) return null; + + StringBuilder sb = new StringBuilder(); + if(StringHelper.containsNonWhitespace(revision.getAuthor().getFirstname())) { + sb.append(revision.getAuthor().getFirstname()); + } + if(StringHelper.containsNonWhitespace(revision.getAuthor().getSurname())) { + if(sb.length() > 0) sb.append(' '); + sb.append(revision.getAuthor().getSurname()); + } + return sb.toString(); + } + } } diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallback.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..d241e161575184e97cc8016ae4d5dbf5d9b3bdaa --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallback.java @@ -0,0 +1,56 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.core.commons.modules.glossary; + +/** + * + * Description:<br> + * SecurityCallback for glossar + * + * <P> + * Initial Date: 16 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http.//www.frentix.com + */ +public interface GlossarySecurityCallback { + + public boolean isUserAllowToEditEnabled(); + + /** + * Can add new glossary items + * @return + */ + public boolean canAdd(); + + /** + * Can edit a glossary item + * @param gi The glossary item + * @return true if can edit the item + */ + public boolean canEdit(GlossaryItem gi); + + /** + * Can edit a glossary item + * @param gi The glossary item + * @return true if can edit the item + */ + public boolean canDelete(GlossaryItem gi); + +} diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallbackImpl.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallbackImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a638afb5bb715c0464af7fa7c8b99110bf19fd67 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossarySecurityCallbackImpl.java @@ -0,0 +1,104 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.core.commons.modules.glossary; + +import java.util.List; + +/** + * + * Description:<br> + * SecurityCallback for glossar + * + * <P> + * Initial Date: 16 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http.//www.frentix.com + */ +public class GlossarySecurityCallbackImpl implements GlossarySecurityCallback { + + private final boolean hasGlossaryRights; + private final boolean owner; + private final boolean editByUserEnabled; + private final Long meKey; + + /** + * Constructor for read-only glossary + */ + public GlossarySecurityCallbackImpl() { + this(false, false, false, new Long(0)); + } + + public GlossarySecurityCallbackImpl(boolean hasGlossaryRights, boolean owner, boolean editByUserEnabled, Long identityKey) { + this.hasGlossaryRights = hasGlossaryRights; + this.owner = owner; + this.editByUserEnabled = editByUserEnabled; + this.meKey = identityKey; + } + + + + @Override + public boolean isUserAllowToEditEnabled() { + return editByUserEnabled; + } + + /** + * @see org.olat.core.commons.modules.glossary.GlossarySecurityCallback#canAdd() + */ + @Override + public boolean canAdd() { + return hasGlossaryRights || owner || editByUserEnabled; + } + + /** + * @see org.olat.core.commons.modules.glossary.GlossarySecurityCallback#canEdit(org.olat.core.commons.modules.glossary.GlossaryItem) + */ + public boolean canEdit(GlossaryItem gi) { + if(hasGlossaryRights || canUserEdit(gi) || owner) { + return true; + } + return false; + } + + /** + * @see org.olat.core.commons.modules.glossary.GlossarySecurityCallback#canDelete(org.olat.core.commons.modules.glossary.GlossaryItem) + */ + @Override + public boolean canDelete(GlossaryItem gi) { + //same as edit permission + return canEdit(gi); + } + + private boolean canUserEdit(GlossaryItem gi) { + if(editByUserEnabled) { + List<Revision> revisions = gi.getRevHistory(); + if(revisions != null) { + for(Revision revision:revisions) { + Author author = revision.getAuthor(); + Long authorKey = author.extractKey(); + if(authorKey != null && meKey.equals(authorKey)) { + return true; + } + } + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryTermAndSynonymController.java b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryTermAndSynonymController.java index ed72d3978369af5186e0cfbd4e016339a6aee2f1..e027eb9d5f8485ba605ef6f3aea1fcc3049e9c07 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/GlossaryTermAndSynonymController.java +++ b/src/main/java/org/olat/core/commons/modules/glossary/GlossaryTermAndSynonymController.java @@ -22,6 +22,7 @@ package org.olat.core.commons.modules.glossary; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -59,9 +60,12 @@ public class GlossaryTermAndSynonymController extends FormBasicController { private GlossaryItem duplicateGlossItem; private static final String CMD_DELETE_SYNONYM = "delete.synonym."; private static final String SYNONYM_TEXT_ELEMENT = "synonym."; + private final boolean add; - protected GlossaryTermAndSynonymController(UserRequest ureq, WindowControl control, GlossaryItem glossaryItem, VFSContainer glossaryFolder) { + protected GlossaryTermAndSynonymController(UserRequest ureq, WindowControl control, GlossaryItem glossaryItem, VFSContainer glossaryFolder, + boolean add) { super(ureq, control, FormBasicController.LAYOUT_VERTICAL); + this.add = add; this.glossaryItem = glossaryItem; this.glossaryFolder = glossaryFolder; initForm(ureq); @@ -99,6 +103,17 @@ public class GlossaryTermAndSynonymController extends FormBasicController { glossaryItem.setGlossSynonyms(glossItemSynonyms); createOrUpdateSynonymLayout(this.flc, glossItemSynonyms); + Revision revision = new Revision(); + revision.setAuthor(new Author(getIdentity())); + revision.setJavaDate(new Date()); + if(add) { + revision.setRevisionflag("added"); + } else { + revision.setRevisionflag("changed"); + } + + glossaryItem.getRevHistory().add(revision); + if (!checkForDuplicatesInGlossary()){ showError("term.error.alreadyused", duplicateGlossItem.getGlossTerm()); glossaryTermField.setErrorKey("term.error.alreadyused", new String[]{duplicateGlossItem.getGlossTerm()} ); diff --git a/src/main/java/org/olat/core/commons/modules/glossary/OpenAuthorProfilEvent.java b/src/main/java/org/olat/core/commons/modules/glossary/OpenAuthorProfilEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..94920fa00e6b9e921fb33e5e3a445f50e4a6cc02 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/OpenAuthorProfilEvent.java @@ -0,0 +1,48 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.core.commons.modules.glossary; + +import org.olat.core.gui.control.Event; + +/** + * + * Description:<br> + * Event to open the profil + * + * <P> + * Initial Date: 16 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class OpenAuthorProfilEvent extends Event { + + private static final long serialVersionUID = -1915843961687124653L; + + private final Long key; + + public OpenAuthorProfilEvent(Long key) { + super("openauthorprofil"); + this.key = key; + } + + public Long getKey() { + return key; + } +} diff --git a/src/main/java/org/olat/core/commons/modules/glossary/Revision.java b/src/main/java/org/olat/core/commons/modules/glossary/Revision.java new file mode 100644 index 0000000000000000000000000000000000000000..168f41c69f2b802940fa09b95e4aaccef94cd8e2 --- /dev/null +++ b/src/main/java/org/olat/core/commons/modules/glossary/Revision.java @@ -0,0 +1,98 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.core.commons.modules.glossary; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; + +/** + * + * Description:<br> + * Revision for DocBook + * + * <P> + * Initial Date: 15 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class Revision { + + private static final OLog log = Tracing.createLoggerFor(Revision.class); + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH); + + private Author author; + private String revisionflag; + private String date; + + public Revision() { + // + } + + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public String getRevisionflag() { + return revisionflag; + } + + public void setRevisionflag(String revisionflag) { + this.revisionflag = revisionflag; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public Date getJavaDate() { + if(StringHelper.containsNonWhitespace(date)) { + synchronized(dateFormat) { + try { + return dateFormat.parse(date); + } catch (ParseException e) { + log.warn("Cannot parse a date: " + date, e); + return null; + } + } + } + return null; + } + + public void setJavaDate(Date date) { + synchronized(dateFormat) { + this.date = dateFormat.format(date); + } + } +} diff --git a/src/main/java/org/olat/core/commons/modules/glossary/_content/glossarylist.html b/src/main/java/org/olat/core/commons/modules/glossary/_content/glossarylist.html index 205010af3bbe443d4a34f290118b4020f62e9b76..c99f6dc625335ed4c389b896e4dc1f45a4513c15 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/_content/glossarylist.html +++ b/src/main/java/org/olat/core/commons/modules/glossary/_content/glossarylist.html @@ -1,8 +1,10 @@ #if ($editModeEnabled) <div class="o_glossary_addcommand"> - $r.render("cmd.add") + #if($r.available("cmd.add")) + $r.render("cmd.add") + #end </div> -#end +#end <div class="o_glossary"> <h3>$r.translate("glossary.title")</h3><br/> #if ($registerEnabled) @@ -15,19 +17,42 @@ </div> #end <dl> - #foreach( $editAndDelButton in $editAndDelButtonList ) - #set ($glossaryItem = $editAndDelButton.get(0).getUserObject()) + #foreach($glossaryItem in $editAndDelButtonList ) #set ($glossaryItemIndex = $glossaryItem.getIndex()) #if ( $filterIndex == $glossaryItemIndex || $filterIndex == "all" ) ## <div class="o_glossary_entry"> #if ($editModeEnabled) <div class="o_glossary_commands"> - $r.render("cmd.edit.$velocityCount") - $r.render("cmd.delete.$velocityCount") + #if($r.available("cmd.edit.$velocityCount")) + $r.render("cmd.edit.$velocityCount") + #end + #if($r.available("cmd.delete.$velocityCount")) + $r.render("cmd.delete.$velocityCount") + #end </div> #end <dt> - $glossaryItem.getGlossTerm() + $glossaryItem.getGlossTerm() + #if($userAllowToEditEnabled) + #if($glossaryItem.hasAuthor()) + <span class="o_glossary_author">$r.translate("glossary.item.published.by") + #if($glossaryItem.authorCmd) + <a href="$r.commandURI($glossaryItem.authorCmd)" target="o_glossary_profil" onclick="return o2cl()">$glossaryItem.authorName</a> + #else + <a href="$glossaryItem.authorLink" class="">$glossaryItem.authorName</a>#end + ${glossaryItem.creationDate} + #if($glossaryItem.hasModifier()), #end</span> + #end + #if($glossaryItem.hasModifier()) + <span class="o_glossary_modifier">$r.translate("glossary.item.modified.by.on") + #if($glossaryItem.modifierCmd) + <a href="$r.commandURI($glossaryItem.modifierCmd)" target="o_glossary_profil" onclick="return o2cl()" >$glossaryItem.modifierName</a> + #else + <a href="$glossaryItem.modifierLink" class="">$glossaryItem.modifierName</a> + #end + $glossaryItem.lastModificationDate</span> + #end + #end #if ( $glossaryItem.getGlossSynonyms().size() > 0) <div class="o_glossary_synonym"> #foreach ( $glossSynonym in $glossaryItem.getGlossSynonyms() ) @@ -47,7 +72,9 @@ #if ($editModeEnabled) <p /> <div class="o_glossary_addcommand"> - $r.render("cmd.add") + #if($r.available("cmd.add")) + $r.render("cmd.add") + #end </div> #end diff --git a/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_de.properties index c5d9c26b4d5f3a5b8fdbcdfe08401341b2f96592..fae9b04b8dd6aef2430ce53a3d0dda709ee3a488 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_de.properties @@ -19,6 +19,9 @@ glossary.list.filter = Liste filtern nach: cmd.add = Eintrag hinzufügen cmd.edit = Ändern cmd.delete = Löschen +glossary.item.modified.by.on = geändert von +glossary.item.at = am {0} +glossary.item.published.by = von warning.contains.dublicates = Die folgenden Begriffe sind im Glossar mehrmals definiert: {0}. Es wird jeweils nur ein Begriff angezeigt, doppelte Begriffe bitte löschen! @@ -43,7 +46,7 @@ synonym.add = $:glossary.term.synonym bearbeiten, hinzuf synonym.button = Speichern synonym.link.delete = - term.error.alreadyused = Dieser Begriff, ein Synonym oder eine flektierte Form ist bereits einmal im Glossar enthalten. Bitte ändern Sie diesen Eintrag oder das Duplikat "{0}"! - +commad.glossary=Autor definition.title = Definition definition.saved = Die $:definition.title wurde gespeichert. diff --git a/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_en.properties index afcf75b289eb1419d6f1712c74793784e0a02499..8b25a08d7a51df3dd82d8dff1e2387493debba65 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/modules/glossary/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 10:46:02 CET 2011 +#Mon May 16 17:10:00 CEST 2011 chelp.flexion.define=Flexions in linguistics are forms of one single word expressing its grammatical function. chelp.flexions.get.button=When clicking on the button "$\:flexions.get.button" a list with all flexions of one single glossary item will appear. chelp.flexions.to.save=Now you can select flexions of your glossary entry. Its definition will then be displayed in the runnning text. @@ -12,6 +12,7 @@ choose.morph.service=Use the following service for this query\: cmd.add=Add entry cmd.delete=Delete cmd.edit=Edit +commad.glossary=Author definition.saved=$\:definition.title has been saved. definition.title=Definition disabled=Not available. Please save this term first. @@ -37,6 +38,9 @@ glossary.form.error.containsKeyAlready=This term already exists in the glossary glossary.form.error.keyToShort=A term must not consist of only one character glossary.form.error.notEmpty=This field is mandatory glossary.form.glossaryKey=Term +glossary.item.at=at {0} +glossary.item.modified.by.on=modified by +glossary.item.published.by=by glossary.list.filter=Retrieve list by using the following criteria\: glossary.list.showall=Show all glossary.locked=This glossary is currently edited by user <b>{0}</b>. Editing is therefore not possible. diff --git a/src/main/java/org/olat/core/commons/modules/glossary/_static/css/glossary.css b/src/main/java/org/olat/core/commons/modules/glossary/_static/css/glossary.css index 9ced1bf8d46887e63ef49d88216909fac4cece2f..f0ca6bed32e3e4c31fd5ff09fb91abf85d96f8db 100644 --- a/src/main/java/org/olat/core/commons/modules/glossary/_static/css/glossary.css +++ b/src/main/java/org/olat/core/commons/modules/glossary/_static/css/glossary.css @@ -21,11 +21,23 @@ } /* the glossary term */ -dt { - font-size: 110%; +.o_glossary dt { font-weight: bold; - font-variant: small-caps; } + +dt span.o_glossary_author { + font-weight: normal; +} + +dt span.o_glossary_modifier { + font-weight: normal; + color: #98221F; +} + +dt span.o_glossary_modifier a, dt span.o_glossary_modifier a:hover { + color: #98221F; +} + dt o_glossary_synonym { font-size: 80%; font-variant: small-caps; diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml b/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml index a4e56696fef4045ffd472afeb1291b2e722ba92d..cf943a6faf1293c254640c6c7a165494f65be868 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/databaseCorecontext.xml @@ -205,7 +205,7 @@ <entry key="hibernate.query.substitutions"><value>true 1, false 0, yes 'Y', no 'N'</value></entry> <!-- for development phase only: --> <entry key="hibernate.c3p0.debugUnreturnedConnectionStackTraces"><value>true</value></entry> - <entry key="hibernate.c3p0.unreturnedConnectionTimeout"><value>120</value></entry> + <entry key="hibernate.c3p0.unreturnedConnectionTimeout"><value>${hibernate.connection.timeout}</value></entry> <!-- end for development phase only --> <entry key="hibernate.transaction.factory_class"><value>org.hibernate.transaction.JDBCTransactionFactory</value></entry> diff --git a/src/main/java/org/olat/core/commons/portlets/didYouKnow/DidYouKnowPortletRunController.java b/src/main/java/org/olat/core/commons/portlets/didYouKnow/DidYouKnowPortletRunController.java index d5c941e23bdf85d38e75237e9de6c1c3c9016653..4c9f81cb4cc40216c1114bef192fbf5377673e44 100644 --- a/src/main/java/org/olat/core/commons/portlets/didYouKnow/DidYouKnowPortletRunController.java +++ b/src/main/java/org/olat/core/commons/portlets/didYouKnow/DidYouKnowPortletRunController.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.Set; @@ -37,8 +38,10 @@ import org.olat.core.gui.control.DefaultController; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.translator.PackageTranslator; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.i18n.I18nManager; +import org.olat.core.util.i18n.I18nModule; /** * Description:<br> @@ -70,6 +73,33 @@ public class DidYouKnowPortletRunController extends DefaultController { int numbTips = -1; // search in property file from this package for all questions Properties propertiesFile = I18nManager.getInstance().getResolvedProperties(trans.getLocale(), trans.getPackageName()); + + //fxdiff FXOLAT-103 lookup in base language if nothing found in original language + if (propertiesFile.size() == 0) { + String langCode = trans.getLocale().getLanguage(); + String countryCode = trans.getLocale().getCountry(); + String variant = trans.getLocale().getVariant(); + // first try without variant + if (StringHelper.containsNonWhitespace(variant)) { + Locale loc = I18nManager.getInstance().getLocaleOrNull(langCode + "_" + countryCode); + if (loc == null) { + loc = I18nManager.getInstance().getLocaleOrNull(langCode); + } + if (loc != null) { + propertiesFile = I18nManager.getInstance().getResolvedProperties(loc, trans.getPackageName()); + } + } + // if still nothing found, try without country + if (propertiesFile.size() == 0) { + if (StringHelper.containsNonWhitespace(countryCode)) { + Locale loc = I18nManager.getInstance().getLocaleOrNull(langCode); + if (loc != null) { + propertiesFile = I18nManager.getInstance().getResolvedProperties(loc, trans.getPackageName()); + } + } + } + } + Set keys = propertiesFile.keySet(); for (Iterator iterator = keys.iterator(); iterator.hasNext();) { String key = (String) iterator.next(); diff --git a/src/main/java/org/olat/core/commons/services/commentAndRating/UserRatingsManager.java b/src/main/java/org/olat/core/commons/services/commentAndRating/UserRatingsManager.java index d723f44190c40aab8cfc8eba9180bf98161be4d5..6aa70696b71b62610c87afaa44bba8a9d25ff773 100644 --- a/src/main/java/org/olat/core/commons/services/commentAndRating/UserRatingsManager.java +++ b/src/main/java/org/olat/core/commons/services/commentAndRating/UserRatingsManager.java @@ -21,7 +21,10 @@ package org.olat.core.commons.services.commentAndRating; +import java.util.List; + import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.commentAndRating.model.OLATResourceableRating; import org.olat.core.commons.services.commentAndRating.model.UserRating; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; @@ -184,5 +187,13 @@ public abstract class UserRatingsManager extends BasicManager { * @return */ public abstract int deleteAllRatingsIgnoringSubPath(); + + /** + * Return the most rated resources + * @param limit The maximum number of resources returned + * @return + */ + //fxdiff + public abstract List<OLATResourceableRating> getMostRatedResourceables(int maxResults); } diff --git a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/UserRatingsManagerImpl.java b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/UserRatingsManagerImpl.java index b439f684bc33308db02295e17dae589134bcabb2..f09930dd4122083b54b52f9d0a33ce6d6f6b7b54 100644 --- a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/UserRatingsManagerImpl.java +++ b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/UserRatingsManagerImpl.java @@ -29,6 +29,7 @@ import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.persistence.DBQuery; import org.olat.core.commons.services.commentAndRating.CommentAndRatingLoggingAction; import org.olat.core.commons.services.commentAndRating.UserRatingsManager; +import org.olat.core.commons.services.commentAndRating.model.OLATResourceableRating; import org.olat.core.commons.services.commentAndRating.model.UserRating; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; @@ -290,4 +291,29 @@ public class UserRatingsManagerImpl extends UserRatingsManager { return false; } + @Override + //fxdiff + public List<OLATResourceableRating> getMostRatedResourceables(int maxResults) { + StringBuilder sb = new StringBuilder(); + sb.append("select new ").append(OLATResourceableRating.class.getName()).append("(") + .append(" rating.resName, rating.resId, rating.resSubPath, avg(rating.rating))") + .append(" from ").append(UserRatingImpl.class.getName()).append(" as rating ") + .append(" where rating.resName=:resName and rating.resId=:resId") + .append(" group by rating.resName, rating.resId, rating.resSubPath") + .append(" order by avg(rating.rating) desc"); + + DBQuery query = DBFactory.getInstance().createQuery(sb.toString()); + query.setString("resName", getOLATResourceable().getResourceableTypeName()); + query.setLong("resId", getOLATResourceable().getResourceableId()); + + if(maxResults > 0) { + query.setMaxResults(maxResults); + } + + List<OLATResourceableRating> mostRated = query.list(); + return mostRated; + } + + + } diff --git a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/UserCommentsAndRatingsController.java b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/UserCommentsAndRatingsController.java index 35ffa60944464826c004c3d121bcb9e584ab2f15..34c2b731580decde4742f7a2bf66a81bb295efe7 100644 --- a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/UserCommentsAndRatingsController.java +++ b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/UserCommentsAndRatingsController.java @@ -116,10 +116,10 @@ public class UserCommentsAndRatingsController extends BasicController implements } // Add ratings view this.userCommentsAndRatingsVC.contextPut("viewIdent", CodeHelper.getRAMUniqueID()); + this.userCommentsAndRatingsVC.contextPut("enableRatings", Boolean.valueOf(enableRatings)); if (enableRatings) { this.userRatingsManager = UserRatingsManager.getInstance(ores, oresSubPath); if (securityCallback.canRate()) { - this.userCommentsAndRatingsVC.contextPut("enableRatings", Boolean.valueOf(enableRatings)); ratingUserC = new RatingComponent("userRating", 0, RATING_MAX, true); ratingUserC.addListener(this); this.userCommentsAndRatingsVC.put("ratingUserC", ratingUserC); diff --git a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/_content/userCommentsAndRatings.html b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/_content/userCommentsAndRatings.html index 7f865933e886ac6a993963e8948e443ac0b0ae3c..2df0f4ed84ba9878e7d971615232de534b0ec264 100644 --- a/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/_content/userCommentsAndRatings.html +++ b/src/main/java/org/olat/core/commons/services/commentAndRating/impl/ui/_content/userCommentsAndRatings.html @@ -17,7 +17,7 @@ </div> ## Display user rating when hovering over rating area -#if($r.render("ratingUserC")) +#if($r.available("ratingUserC")) <script type="text/javascript"> /* <![CDATA[ */ Ext.onReady(function(){ diff --git a/src/main/java/org/olat/core/commons/services/commentAndRating/model/OLATResourceableRating.java b/src/main/java/org/olat/core/commons/services/commentAndRating/model/OLATResourceableRating.java new file mode 100644 index 0000000000000000000000000000000000000000..707ad7d2b46f640fb711049c495832ba297453f9 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/commentAndRating/model/OLATResourceableRating.java @@ -0,0 +1,62 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.core.commons.services.commentAndRating.model; + +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.resource.OresHelper; + +/** + * + * <h3>Description:</h3> + * Return wrapper for the average rating over severals resources + * <p> + * Initial Date: 17 déc. 2010 <br> + * @author srosse, stephane.rosse@frentix.com, www.frentix.com + */ +public class OLATResourceableRating { + + private final OLATResourceable ores; + private final String resSubPath; + private final Double rating; + + public OLATResourceableRating(String resName, Long resId, String resSubPath, Double rating) { + ores = OresHelper.createOLATResourceableInstance(resName, resId); + this.resSubPath = resSubPath; + this.rating = rating; + } + + public OLATResourceable getOres() { + return ores; + } + + public String getResSubPath() { + return resSubPath; + } + + /** + * @return The average rating + */ + public Double getRating() { + return rating; + } + +} diff --git a/src/main/java/org/olat/core/commons/services/search/SearchModule.java b/src/main/java/org/olat/core/commons/services/search/SearchModule.java index 6ca5236ec1dbd2d49ce7f405d2212f807ad57d20..6346f67a203b239eabab3a64593a71a0337a430c 100644 --- a/src/main/java/org/olat/core/commons/services/search/SearchModule.java +++ b/src/main/java/org/olat/core/commons/services/search/SearchModule.java @@ -26,6 +26,7 @@ package org.olat.core.commons.services.search; * @author Christian Guretzki */ import java.io.File; +import java.util.ArrayList; import java.util.List; import org.olat.core.commons.modules.bc.FolderConfig; @@ -33,6 +34,7 @@ import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.PersistedProperties; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; /** * Description:<br> @@ -61,12 +63,14 @@ public class SearchModule extends AbstractOLATModule { private static final String CONF_RESTART_DAY_OF_WEEK = "restartDayOfWeek"; private static final String CONF_PPT_FILE_ENABLED = "pptFileEnabled"; private static final String CONF_EXCEL_FILE_ENABLED = "excelFileEnabled"; + private static final String CONF_PDF_FILE_ENABLED = "pdfFileEnabled"; private static final String CONF_PDF_TEXT_BUFFERING = "pdfTextBuffering"; private static final String CONF_SPELL_CHECK_ENABLED = "spellCheckEnabled"; private static final String CONF_TEMP_PDF_TEXT_BUF_PATH = "pdfTextBufferPath"; private static final String CONF_MAX_FILE_SIZE = "maxFileSize"; private static final String CONF_RAM_BUFFER_SIZE_MB = "ramBufferSizeMb"; private static final String CONF_USE_COMPOUND_FILE = "useCompoundFile"; + private static final String CONF_FILE_BLACK_LIST = "fileBlackList"; // Default values private static final int DEFAULT_RESTART_INTERVAL = 0; @@ -91,6 +95,7 @@ public class SearchModule extends AbstractOLATModule { private int maxHits; private int maxResults; private List<String> fileBlackList; + private List<String> customFileBlackList; private int numberIndexWriter; private int folderPoolSize; @@ -101,6 +106,7 @@ public class SearchModule extends AbstractOLATModule { private int restartDayOfWeek; private boolean pptFileEnabled; private boolean excelFileEnabled; + private boolean pdfFileEnabled; private boolean pdfTextBuffering; private boolean isSpellCheckEnabled; private String fullTempPdfTextBufferPath; @@ -145,7 +151,8 @@ public class SearchModule extends AbstractOLATModule { /** * Read config-parameter from configuration and store this locally. */ - public void init() { + @Override + public void initDefaultProperties() { log.debug("init start..."); String indexPath = getStringConfigParameter(CONF_INDEX_PATH, "/tmp", false); log.debug("init indexPath=" + indexPath); @@ -171,12 +178,65 @@ public class SearchModule extends AbstractOLATModule { restartDayOfWeek = getIntConfigParameter(CONF_RESTART_DAY_OF_WEEK, DEFAULT_RESTART_DAY_OF_WEEK); pptFileEnabled = getBooleanConfigParameter(CONF_PPT_FILE_ENABLED, true); excelFileEnabled = getBooleanConfigParameter(CONF_EXCEL_FILE_ENABLED, true); + pdfFileEnabled = getBooleanConfigParameter(CONF_PDF_FILE_ENABLED, true); pdfTextBuffering = getBooleanConfigParameter(CONF_PDF_TEXT_BUFFERING, true); isSpellCheckEnabled = getBooleanConfigParameter(CONF_SPELL_CHECK_ENABLED, true); maxFileSize = Integer.parseInt(getStringConfigParameter(CONF_MAX_FILE_SIZE, "0", false)); ramBufferSizeMB = Double.parseDouble(getStringConfigParameter(CONF_RAM_BUFFER_SIZE_MB, DEFAULT_RAM_BUFFER_SIZE_MB, false)); useCompoundFile = getBooleanConfigParameter(CONF_USE_COMPOUND_FILE, false); } + + @Override + public void init() { + //black list + String blackList = getStringPropertyValue(CONF_FILE_BLACK_LIST, true); + if(StringHelper.containsNonWhitespace(blackList)) { + String[] files = blackList.split(","); + if(customFileBlackList == null) { + customFileBlackList = new ArrayList<String>(); + } else { + customFileBlackList.clear(); + } + for(String file:files) { + if(!customFileBlackList.contains(file) && !fileBlackList.contains(file)) { + customFileBlackList.add(file); + } + } + } + + //ppt enabled + String pptEnabled = getStringPropertyValue(CONF_PPT_FILE_ENABLED, true); + if(StringHelper.containsNonWhitespace(pptEnabled)) { + pptFileEnabled = "true".equals(pptEnabled); + } + + //excel enabled + String excelEnabled = getStringPropertyValue(CONF_EXCEL_FILE_ENABLED, true); + if(StringHelper.containsNonWhitespace(excelEnabled)) { + excelFileEnabled = "true".equals(excelEnabled); + } + + //pdf enabled + String pdfEnabled = getStringPropertyValue(CONF_PDF_FILE_ENABLED, true); + if(StringHelper.containsNonWhitespace(pdfEnabled)) { + pdfFileEnabled = "true".equals(pdfEnabled); + } + + } + + @Override + protected void initFromChangedProperties() { + init(); + } + + public void setCustomFileBlackList(List<String> files) { + StringBuilder sb = new StringBuilder(); + for(String file:files) { + if(sb.length() > 0) sb.append(','); + sb.append(file); + } + setStringProperty(CONF_FILE_BLACK_LIST, sb.toString(), true); + } /** * @return Absolute file path for the full-index. @@ -239,7 +299,18 @@ public class SearchModule extends AbstractOLATModule { * @return Space seperated list of non indexed files. */ public List<String> getFileBlackList() { - return fileBlackList; + List<String> list = new ArrayList<String>(); + if(fileBlackList != null) { + list.addAll(fileBlackList); + } + if(customFileBlackList != null) { + list.addAll(customFileBlackList); + } + return list; + } + + public List<String> getCustomFileBlackList() { + return customFileBlackList; } /** @@ -297,13 +368,32 @@ public class SearchModule extends AbstractOLATModule { public boolean getPptFileEnabled() { return pptFileEnabled; } - + + public void setPptFileEnabled(boolean enabled) { + String value = Boolean.toString(enabled); + this.setStringProperty(CONF_PPT_FILE_ENABLED, value, true); + } + /** * @return TRUE: index Excel-files. */ public boolean getExcelFileEnabled() { return excelFileEnabled; } + + public void setExcelFileEnabled(boolean enabled) { + String value = Boolean.toString(enabled); + this.setStringProperty(CONF_EXCEL_FILE_ENABLED, value, true); + } + + public boolean getPdfFileEnabled() { + return pdfFileEnabled; + } + + public void setPdfFileEnabled(boolean enabled) { + String value = Boolean.toString(enabled); + this.setStringProperty(CONF_PDF_FILE_ENABLED, value, true); + } /** * @return TRUE: store a temporary text file with content of extracted PDF text. @@ -334,18 +424,6 @@ public class SearchModule extends AbstractOLATModule { public List<Long> getRepositoryBlackList() { return repositoryBlackList; } - - @Override - protected void initDefaultProperties() { - // TODO Auto-generated method stub - - } - - @Override - protected void initFromChangedProperties() { - // TODO Auto-generated method stub - - } @Override public void setPersistedProperties(PersistedProperties persistedProperties) { diff --git a/src/main/java/org/olat/core/commons/services/thumbnail/impl/PDFToThumbnail.java b/src/main/java/org/olat/core/commons/services/thumbnail/impl/PDFToThumbnail.java index 2e60fefd1d0f9c67126c75d84d88cd4980a28db5..1d2846c47216e87ece15bdf7b23a5e4bf02367ba 100644 --- a/src/main/java/org/olat/core/commons/services/thumbnail/impl/PDFToThumbnail.java +++ b/src/main/java/org/olat/core/commons/services/thumbnail/impl/PDFToThumbnail.java @@ -36,6 +36,7 @@ import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.ImageHelper; +import org.olat.core.util.WorkThreadInformations; import org.olat.core.util.ImageHelper.Size; import org.olat.core.util.vfs.VFSLeaf; @@ -64,6 +65,8 @@ public class PDFToThumbnail implements ThumbnailSPI { InputStream in = null; PDDocument document = null; try { + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.set("Generate thumbnail VFSLeaf=" + pdfFile); in = pdfFile.getInputStream(); document = PDDocument.load(in); if (document.isEncrypted()) { @@ -86,6 +89,8 @@ public class PDFToThumbnail implements ThumbnailSPI { log.warn("Unable to create image from pdf file.", e); throw new CannotGenerateThumbnailException(e); } finally { + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.unset(); FileUtils.closeSafely(in); if (document != null) { try { diff --git a/src/main/java/org/olat/core/configuration/PersistedProperties.java b/src/main/java/org/olat/core/configuration/PersistedProperties.java index 81bf5a16ede01cee516df16c1b270389a69f5d98..78616a78afefc5a07977a5da4b9a4e16777c08b0 100644 --- a/src/main/java/org/olat/core/configuration/PersistedProperties.java +++ b/src/main/java/org/olat/core/configuration/PersistedProperties.java @@ -25,9 +25,21 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.security.SecureRandom; +import java.security.Security; import java.util.Properties; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.LogDelegator; import org.olat.core.util.StringHelper; @@ -133,15 +145,33 @@ public class PersistedProperties extends LogDelegator implements Initializable, private OLATResourceable PROPERTIES_CHANGED_EVENT_CHANNEL; private CoordinatorManager coordinatorManager; + private boolean secured; + + static { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + } + + public PersistedProperties(CoordinatorManager coordinatorManager, GenericEventListener listener, boolean secured) { + this.coordinatorManager = coordinatorManager; + this.propertiesChangedEventListener = listener; + this.secured = secured; + } + /** * [used by spring] * @param provide coordinatorManager via DI + * fxdiff: needs to be public while another constructor is also public (due to spring loading) */ - private PersistedProperties(CoordinatorManager coordinatorManager, GenericEventListener listener) { + public PersistedProperties(CoordinatorManager coordinatorManager, GenericEventListener listener) { this.coordinatorManager = coordinatorManager; // Keep handle for dispose process this.propertiesChangedEventListener = listener; } + + // fxdiff: backward compatibility + public PersistedProperties(GenericEventListener listener) { + this(CoordinatorManager.getInstance(), listener); + } /** * Constructor for a PersistedProperties object. The calling class must @@ -168,15 +198,25 @@ public class PersistedProperties extends LogDelegator implements Initializable, public void loadPropertiesFromFile() { // Might get an event after beeing disposed. Should not be the case, but you never know with multi user events accross nodes. if (propertiesChangedEventListener != null && configurationPropertiesFile.exists()) { - FileInputStream is; + InputStream is; try { is = new FileInputStream(configurationPropertiesFile); + + if(secured) { + SecretKey key = generateKey("rk6R9pQy7dg3usJk"); + Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING"); + cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random); + is = new CipherInputStream(is, cipher); + } + configuredProperties.load(is); is.close(); } catch (FileNotFoundException e) { logError("Could not load config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); } catch (IOException e) { logError("Could not load config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); + } catch (Exception e) { + logError("Could not load config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); } } } @@ -374,6 +414,14 @@ public class PersistedProperties extends LogDelegator implements Initializable, if (!directory.exists()) directory.mkdirs(); } fileStream = new FileOutputStream(configurationPropertiesFile); + + if(secured) { + SecretKey key = generateKey("rk6R9pQy7dg3usJk"); + Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING"); + cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random); + fileStream = new CipherOutputStream(fileStream, cipher); + } + configuredProperties.store(fileStream, null); // Flush and close before sending events to other nodes to make changes appear on other node fileStream.flush(); @@ -385,6 +433,8 @@ public class PersistedProperties extends LogDelegator implements Initializable, logError("Could not write config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); } catch (IOException e) { logError("Could not write config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); + } catch (Exception e) { + logError("Could not write config file from path::" + configurationPropertiesFile.getAbsolutePath(), e); } finally { try { if (fileStream != null ) fileStream.close(); @@ -430,4 +480,22 @@ public class PersistedProperties extends LogDelegator implements Initializable, return tmp; } + + private static final String salt = "A long, but constant phrase that will be used each time as the salt."; + private static final int iterations = 2000; + private static final int keyLength = 128; + private static final SecureRandom random = new SecureRandom(); + + private static SecretKey generateKey(String passphrase) throws Exception { + PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC"); + return keyFactory.generateSecret(keySpec); + } + + private static IvParameterSpec generateIV(Cipher cipher) throws Exception { + byte [] ivBytes = new byte[cipher.getBlockSize()]; + random.nextBytes(ivBytes); + return new IvParameterSpec(ivBytes); + } + } diff --git a/src/main/java/org/olat/core/defaults/dispatcher/ClassPathStaticDispatcher.java b/src/main/java/org/olat/core/defaults/dispatcher/ClassPathStaticDispatcher.java index 02e4ca38325533f674fd29ea263cbd23b8332bd0..dd1efae501874e046fa961060e226b53179ef17e 100644 --- a/src/main/java/org/olat/core/defaults/dispatcher/ClassPathStaticDispatcher.java +++ b/src/main/java/org/olat/core/defaults/dispatcher/ClassPathStaticDispatcher.java @@ -196,8 +196,12 @@ public class ClassPathStaticDispatcher extends LogDelegator implements Dispatche // /bla/blu.html Package pakkage; pakkage = Package.getPackage(packageName); - MediaResource mr = createClassPathStaticFileMediaResourceFor(pakkage, relPath); - ServletUtil.serveResource(hreq, hres, mr); + if(pakkage == null){ + logWarn("could not find package '"+packageName+"' and relPath '"+relPath+"'", null); + }else{ + MediaResource mr = createClassPathStaticFileMediaResourceFor(pakkage, relPath); + ServletUtil.serveResource(hreq, hres, mr); + } } /** diff --git a/src/main/java/org/olat/core/dispatcher/ErrorFeedbackMailer.java b/src/main/java/org/olat/core/dispatcher/ErrorFeedbackMailer.java index c339033b861e4c50ac275b59d766da09c3e4d8b0..716c8640c7422e81001ffa8b8b51564021d634fc 100644 --- a/src/main/java/org/olat/core/dispatcher/ErrorFeedbackMailer.java +++ b/src/main/java/org/olat/core/dispatcher/ErrorFeedbackMailer.java @@ -1,41 +1,35 @@ /** -* 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br> -* University of Zurich, Switzerland. -* <p> -*/ + * 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br> + * University of Zurich, Switzerland. + * <p> + */ package org.olat.core.dispatcher; import java.util.Collection; import java.util.Iterator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.mail.MessagingException; -import javax.mail.SendFailedException; -import javax.mail.internet.AddressException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.olat.core.CoreSpringFactory; import org.olat.core.id.Identity; import org.olat.core.id.IdentityManager; -import org.olat.core.logging.AssertException; import org.olat.core.logging.LogFileParser; import org.olat.core.logging.Tracing; import org.olat.core.util.WebappHelper; @@ -68,7 +62,10 @@ public class ErrorFeedbackMailer implements Dispatcher { */ public void sendMail(HttpServletRequest request) { String feedback = request.getParameter("textarea"); - String errorNr = feedback.substring(0, feedback.indexOf("\n") - 1); + // fxdiff: correctly get the error-number + // was : String errorNr = feedback.substring(0, feedback.indexOf("\n") - + // 1); + String errorNr = request.getParameter("fx_errnum"); String username = request.getParameter("username"); try { IdentityManager im = (IdentityManager) CoreSpringFactory.getBean("core.id.IdentityManager"); diff --git a/src/main/java/org/olat/core/gui/WindowManager.java b/src/main/java/org/olat/core/gui/WindowManager.java index 4b87d9b28db7364f024477b921e71d07be0cc152..3330570e81886dfc6cb154124f4ff5f01de75160 100644 --- a/src/main/java/org/olat/core/gui/WindowManager.java +++ b/src/main/java/org/olat/core/gui/WindowManager.java @@ -125,6 +125,10 @@ public interface WindowManager extends Disposable { * @return */ public PopupBrowserWindow createNewPopupBrowserWindowFor(UserRequest ureq, ControllerCreator controllerCreator); + + //fxdiff + public PopupBrowserWindow createNewUnauthenticatedPopupWindowFor(UserRequest ureq, ControllerCreator controllerCreator); + /** * diff --git a/src/main/java/org/olat/core/gui/components/Window.java b/src/main/java/org/olat/core/gui/components/Window.java index 6545edc46ab53a76e97d3a759fc4150153b042ae..5cb6c586cbb17e33c0db54f795340e456df7eabc 100644 --- a/src/main/java/org/olat/core/gui/components/Window.java +++ b/src/main/java/org/olat/core/gui/components/Window.java @@ -49,6 +49,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.info.WindowControlInfo; import org.olat.core.gui.control.winmgr.Command; import org.olat.core.gui.control.winmgr.CommandFactory; +import org.olat.core.gui.control.winmgr.JSCommand; import org.olat.core.gui.control.winmgr.MediaResourceMapper; import org.olat.core.gui.control.winmgr.WindowBackOfficeImpl; import org.olat.core.gui.exception.MsgFactory; @@ -73,6 +74,7 @@ import org.olat.core.id.context.HistoryPoint; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; import org.olat.core.util.component.ComponentTraverser; import org.olat.core.util.component.ComponentVisitor; import org.olat.testutils.codepoints.server.Codepoint; @@ -437,6 +439,8 @@ public class Window extends Container { Tracing.logDebug("Perf-Test: Window durationBeforeHandleDirties=" + durationBeforeHandleDirties, Window.class); } Command co = handleDirties(); + //fxdiff FXOLAT-119: update business path + Command co2 = handleBusinessPath(ureq); if (isDebugLog) { long durationAfterHandleDirties = System.currentTimeMillis() - debug_start; Tracing.logDebug("Perf-Test: Window durationAfterHandleDirties=" + durationAfterHandleDirties, Window.class); @@ -450,6 +454,9 @@ public class Window extends Container { if (co != null) { // see method handleDirties for the rare case of co == null even if there are dirty components; wbackofficeImpl.sendCommandTo(co); } + if (co2 != null) { // see method handleDirties for the rare case of co == null even if there are dirty components; + wbackofficeImpl.sendCommandTo(co2); + } } } } else { // not inline @@ -840,6 +847,19 @@ public class Window extends Container { return attributes.remove(key); } + //fxdiff FXOLAT-119: update business path + public Command handleBusinessPath(UserRequest ureq) { + HistoryPoint p = ureq.getUserSession().getLastHistoryPoint(); + if(p != null && StringHelper.containsNonWhitespace(p.getBusinessPath())) { + StringBuilder sb = new StringBuilder(); + List<ContextEntry> ces = BusinessControlFactory.getInstance().createCEListFromString(p.getBusinessPath()); + String url = BusinessControlFactory.getInstance().getAsURIString(ces, true); + sb.append("try { o_info.businessPath='").append(url).append("';"); + sb.append("b_shareActiveSocialUrl(); } catch(e) { }"); + return new JSCommand(sb.toString()); + } + return null; + } /** * to be called by Window.java or the AjaxController only! diff --git a/src/main/java/org/olat/core/gui/components/form/_static/js/form.js b/src/main/java/org/olat/core/gui/components/form/_static/js/form.js index efdb4ee12b3643abf5cc3051fd6f1aabc8fb7ce9..7e7d9ca7826f43d2c2869c600e4533703956ef06 100644 --- a/src/main/java/org/olat/core/gui/components/form/_static/js/form.js +++ b/src/main/java/org/olat/core/gui/components/form/_static/js/form.js @@ -28,7 +28,10 @@ function b_form_updateFormElementVisibility(formName, selectionElementName, depe // dependentElement can be null if dependentElement is of type spacer or static text // in this case the element is not a form element and thus won't be found - if (selectionValue == ruleValue) { + // <OLATCE-289> + if (selectionValue == ruleValue + || (selectionElement.checked != null && ruleValue == selectionElement.checked.toString())) { + // </OLATCE-289> if (ruleResult) { b_form_enableFormElement(formName, dependentElement, dependentElementName, hideDisabledElements); } else { diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextElementImpl.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextElementImpl.java index 14d999f56cf5e7a663707199bfa2fd599d6a20ca..3935a59c6a0edb88a92a518b685b92667de2ea4f 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextElementImpl.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextElementImpl.java @@ -38,6 +38,7 @@ import org.olat.core.gui.render.RenderingState; import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; +import org.olat.core.helpers.Settings; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; @@ -316,7 +317,7 @@ public class TextElementImpl extends AbstractTextElement implements InlineTextEl @Override public void addActionListener(Controller listener, int action) { super.addActionListener(listener, action); - if (action == FormEvent.ONCHANGE) { + if (action == FormEvent.ONCHANGE && Settings.isDebuging()) { log.warn("Do not use the onChange event in Textfields / TextAreas as this has often unwanted side effects. " + "As the onchange event is only tiggered when you click outside a field or navigate with the tab to the next element " + "it will suppress the first attempt to the submit click as by clicking " + diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementImpl.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementImpl.java index 7bfeb99acf24fe6badf95187c5f45c0f1e7a5276..b464c505ea9a4e35089e715ab624225664c1c863 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementImpl.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementImpl.java @@ -32,6 +32,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Disposable; import org.olat.core.gui.control.WindowBackOffice; import org.olat.core.gui.control.winmgr.JSCommand; +import org.olat.core.helpers.Settings; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.filter.Filter; @@ -190,7 +191,7 @@ public class RichTextElementImpl extends AbstractTextElement implements @Override public void addActionListener(Controller listener, int action) { super.addActionListener(listener, action); - if (action == FormEvent.ONCHANGE) { + if (action == FormEvent.ONCHANGE && Settings.isDebuging()) { log.warn("Do not use the onChange event in Textfields / TextAreas as this has often unwanted side effects. " + "As the onchange event is only tiggered when you click outside a field or navigate with the tab to the next element " + "it will suppress the first attempt to the submit click as by clicking " + diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TextFlexiCellRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TextFlexiCellRenderer.java index 232701d500173fb8ff48c51da3c4dddc8fe330fa..58be6ec2a8da248006fc26a129519ff67d79a223 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TextFlexiCellRenderer.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TextFlexiCellRenderer.java @@ -94,7 +94,9 @@ class TextFlexiCellRenderer implements FlexiCellRenderer { Formatter formatter = Formatter.getInstance(translator.getLocale()); target.append( formatter.formatDateAndTime((Date)cellValue) ); } else { - target.append( cellValue.toString() ); + if (cellValue != null) { + target.append( cellValue.toString() ); + } } } diff --git a/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomCSS.java b/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomCSS.java index 3863b15c593ddd9bf02f5452555ad93a137775d2..19944163e66e03ef375cb61dd74e35e9cf24f6b5 100644 --- a/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomCSS.java +++ b/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomCSS.java @@ -51,6 +51,7 @@ import org.olat.core.util.vfs.VFSMediaResource; public class CustomCSS extends LogDelegator implements Disposable { private String mapperBaseURI; private String relCssFilename; + private String relCssFileIframe; private Mapper cssUriMapper; private MapperRegistry registry; private JSAndCSSComponent jsAndCssComp; @@ -69,19 +70,39 @@ public class CustomCSS extends LogDelegator implements Disposable { */ public CustomCSS(final VFSContainer cssBaseContainer, final String relCssFilename, UserSession uSess) { - cssUriMapper = new Mapper() { - public MediaResource handle(String relPath, - HttpServletRequest request) { - VFSItem vfsItem = cssBaseContainer.resolve(relPath); - MediaResource mr; - if (vfsItem == null || !(vfsItem instanceof VFSLeaf)) - mr = new NotFoundMediaResource(relPath); - else - mr = new VFSMediaResource((VFSLeaf) vfsItem); - return mr; - } - }; + createCSSUriMapper(cssBaseContainer); this.relCssFilename = relCssFilename; + registerMapper(cssBaseContainer, uSess); + // initialize js and css component + this.jsAndCssComp = new JSAndCSSComponent("jsAndCssComp", this.getClass(), + null, null, false, null); + String fulluri = mapperBaseURI + relCssFilename; + // load CSS after the theme + this.jsAndCssComp.addAutoRemovedCssPathName(fulluri, JSAndCSSAdder.CSS_INDEX_AFTER_THEME); + } + + public CustomCSS(final VFSContainer cssBaseContainer, + final String relCssFileMain, final String relCssFileIFrame, UserSession uSess ) { + createCSSUriMapper(cssBaseContainer); + this.relCssFilename = relCssFileMain; + this.relCssFileIframe = relCssFileIFrame; + registerMapper(cssBaseContainer, uSess); + + // initialize js and css component + this.jsAndCssComp = new JSAndCSSComponent("jsAndCssComp", this.getClass(), + null, null, false, null); + String fulluri = mapperBaseURI + relCssFilename; + // load CSS after the theme + this.jsAndCssComp.addAutoRemovedCssPathName(fulluri, JSAndCSSAdder.CSS_INDEX_AFTER_THEME); + + } + + /** + * @param cssBaseContainer + * @param uSess + */ + private void registerMapper(final VFSContainer cssBaseContainer, + UserSession uSess) { this.registry = MapperRegistry.getInstanceFor(uSess); // Register mapper as cacheable String mapperID = VFSManager.getRealPath(cssBaseContainer); @@ -91,17 +112,31 @@ public class CustomCSS extends LogDelegator implements Disposable { } else { // Add classname to the file path to remove conflicts with other // usages of the same file path - mapperID = this.getClass().getSimpleName() + ":" + mapperID; + mapperID = this.getClass().getSimpleName() + ":" + mapperID + System.currentTimeMillis(); this.mapperBaseURI = registry.registerCacheable(mapperID, cssUriMapper); } - // initialize js and css component - this.jsAndCssComp = new JSAndCSSComponent("jsAndCssComp", this.getClass(), - null, null, false, null); - String fulluri = mapperBaseURI + relCssFilename; - // load CSS after the theme - this.jsAndCssComp.addAutoRemovedCssPathName(fulluri, JSAndCSSAdder.CSS_INDEX_AFTER_THEME); } + /** + * @param cssBaseContainer + */ + private void createCSSUriMapper(final VFSContainer cssBaseContainer) { + cssUriMapper = new Mapper() { + public MediaResource handle(String relPath, + HttpServletRequest request) { + VFSItem vfsItem = cssBaseContainer.resolve(relPath); + MediaResource mr; + if (vfsItem == null || !(vfsItem instanceof VFSLeaf)) + mr = new NotFoundMediaResource(relPath); + else + mr = new VFSMediaResource((VFSLeaf) vfsItem); + return mr; + } + }; + } + + + /** * Get the js and css component that embedds the CSS file * @@ -120,6 +155,17 @@ public class CustomCSS extends LogDelegator implements Disposable { return mapperBaseURI + relCssFilename; } + /** + * @return Returns the relCssFileIframe. + */ + public String getCSSURLIFrame() { + if (relCssFileIframe != null) { + return mapperBaseURI + relCssFileIframe; + } else { + return getCSSURL(); + } + } + /** * @see org.olat.core.gui.control.Disposable#dispose() */ diff --git a/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomJSComponent.java b/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomJSComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..cc44edb9c1ec2ec2af2f2906d5f37d138fea51df --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/htmlheader/jscss/CustomJSComponent.java @@ -0,0 +1,92 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.gui.components.htmlheader.jscss; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.control.JSAndCSSAdder; +import org.olat.core.gui.render.ValidationResult; + +/** + * + * Description:<br> + * CustomJSComponent allows to add js-files to the OLAT head-tag.<br /> + * Use this class, if you want to add js-files that do not reside under a + * /package/_static/js folder. You can specify the url to the file directly. + * + * <p> + * Usage: <br /> + * <code> + * CustomJSComponent customJS = new CustomJSComponent("customThemejs", new String[] { "/olat/raw/BUILDID/themes/frentix/theme.js" }); + * </code> + * </p> + * (originated from FXOLAT-310) + * <P> + * Initial Date: 28.10.2011 <br> + * + * @author strentini, sergio.trentini@frentix.com, www.frentix.com + */ +public class CustomJSComponent extends Component { + + private final String[] jsFilePaths; + + private static final ComponentRenderer RENDERER = new JSAndCSSComponentRenderer(); + + /** + * + * @param name + * @param jsFilePaths + */ + public CustomJSComponent(String name, String[] jsFilePaths) { + super(name); + this.jsFilePaths = jsFilePaths; + } + + /** + * + * @see org.olat.core.gui.components.Component#validate(org.olat.core.gui.UserRequest, + * org.olat.core.gui.render.ValidationResult) + */ + public void validate(UserRequest ureq, ValidationResult vr) { + super.validate(ureq, vr); + JSAndCSSAdder jsadder = vr.getJsAndCSSAdder(); + if (jsFilePaths != null) { + int len = jsFilePaths.length; + for (int i = 0; i < len; i++) { + String jsFileP = jsFilePaths[i]; + jsadder.addRequiredJsFile(null, jsFileP); + } + } + } + + @Override + @SuppressWarnings("unused") + protected void doDispatchRequest(UserRequest ureq) { + // do nothing here + } + + @Override + public ComponentRenderer getHTMLRendererSingleton() { + return RENDERER; + } + +} diff --git a/src/main/java/org/olat/core/gui/components/panel/Panel.java b/src/main/java/org/olat/core/gui/components/panel/Panel.java index f0a5cd9ea11c017330d950932e257f61cac2528b..5030a9a272bdfe7dcc18b54babf442c99321550f 100644 --- a/src/main/java/org/olat/core/gui/components/panel/Panel.java +++ b/src/main/java/org/olat/core/gui/components/panel/Panel.java @@ -22,7 +22,7 @@ package org.olat.core.gui.components.panel; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Collections; import java.util.List; import org.olat.core.gui.UserRequest; @@ -32,9 +32,12 @@ import org.olat.core.gui.components.Container; import org.olat.core.gui.control.dragdrop.DragAndDrop; import org.olat.core.gui.control.dragdrop.DragAndDropImpl; import org.olat.core.gui.control.dragdrop.DragSource; +import org.olat.core.gui.control.dragdrop.DragSourceImpl; import org.olat.core.gui.control.dragdrop.Draggable; import org.olat.core.gui.control.dragdrop.DraggableCreator; import org.olat.core.gui.control.dragdrop.DropEvent; +import org.olat.core.gui.control.dragdrop.DropTarget; +import org.olat.core.gui.control.dragdrop.DropTargetImpl; import org.olat.core.gui.control.dragdrop.DroppableImpl; import org.olat.core.logging.AssertException; @@ -71,17 +74,17 @@ public class Panel extends Container { */ protected void doDispatchRequest(UserRequest ureq) { if (dragAndDropImpl != null) { + //fxdiff // a drop is dispatched to the panel DroppableImpl di = dragAndDropImpl.getDroppableImpl(); if (di != null) { String dropid = ureq.getParameter("v"); - List accDrags = di.getAccepted(); - for (Iterator it_accdrags = accDrags.iterator(); it_accdrags.hasNext();) { - Draggable dr = (Draggable) it_accdrags.next(); + for (Draggable dr:di.getAccepted()) { DragSource ds = dr.find(dropid); if (ds != null) { // found! - fireEvent(ureq, new DropEvent(ds, null)); + DropTarget dt = new DropTargetImpl(this); + fireEvent(ureq, new DropEvent(ds, dt)); return; } @@ -182,10 +185,12 @@ public class Panel extends Container { dragAndDropImpl = new DragAndDropImpl(new DraggableCreator() { public Draggable createDraggable() { Draggable drag = new Draggable() { - public List getContainerIds() { + @Override + public List<String> getContainerIds() { return Panel.this.draggableGetContainerIds(); } + @Override public DragSource find(String dragElementId) { return Panel.this.draggableFind(dragElementId); }}; @@ -207,33 +212,28 @@ public class Panel extends Container { * @return */ protected DragSource draggableFind(String dragElementId) { + //fxdiff Component toRender = getContent(); - DragSource ds = null; if (toRender != null) { - String id = "o_c"+toRender.getDispatchID(); + String id = "o_c" + toRender.getDispatchID(); if (dragElementId.equals(id)) { - ds = new DragSource() { - - public Object getSource() { - return Panel.this; - } - - public String getSubId() { - // no subid for the panel, since the panel itself is the only thing that can be dragged - return null; - }}; + return new DragSourceImpl(this); } - } // else: the object dropped disappear in the meantime...? TODO:double-check - return ds; + } + String id = "o_c" + getDispatchID(); + if (dragElementId.equals(id)) { + return new DragSourceImpl(this); + } + // else: the object dropped disappear in the meantime...? TODO:double-check + return null; } /** * @return */ - protected List draggableGetContainerIds() { - List ids = new ArrayList(); - ids.add("o_c"+getDispatchID()); - return ids; + protected List<String> draggableGetContainerIds() { + //fxdiff + return Collections.singletonList("o_c" + getDispatchID()); } diff --git a/src/main/java/org/olat/core/gui/components/panel/PanelRenderer.java b/src/main/java/org/olat/core/gui/components/panel/PanelRenderer.java index cd88ab5d433fb205e21a5b596e1e295a15c07ac6..ca73e3ec414d3edc0e3ad5059408fe05ba15f92c 100644 --- a/src/main/java/org/olat/core/gui/components/panel/PanelRenderer.java +++ b/src/main/java/org/olat/core/gui/components/panel/PanelRenderer.java @@ -21,8 +21,14 @@ package org.olat.core.gui.components.panel; +import java.util.List; + import org.olat.core.gui.components.Component; import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.control.dragdrop.DragAndDropImpl; +import org.olat.core.gui.control.dragdrop.Draggable; +import org.olat.core.gui.control.dragdrop.DroppableImpl; +import org.olat.core.gui.control.winmgr.AJAXFlags; import org.olat.core.gui.render.RenderResult; import org.olat.core.gui.render.Renderer; import org.olat.core.gui.render.RenderingState; @@ -56,20 +62,20 @@ public class PanelRenderer implements ComponentRenderer { Component toRender = panel.getContent(); - // alpha-quality for drag and drop - /*if (renderer.getGlobalSettings().getAjaxFlags().isDragAndDropEnabled()) { + //fxdiff alpha-quality for drag and drop + if (renderer.getGlobalSettings().getAjaxFlags().isIframePostEnabled()) { DragAndDropImpl dndi = panel.doGetDragAndDrop(); if (dndi != null) { DroppableImpl di = dndi.getDroppableImpl(); if (di != null) { - String urivar = renderer.getComponentPrefix(panel)+"_dropurl"; + String urivar = Renderer.getComponentPrefix(panel) + "_dropurl"; sb.append("<script type=\"text/javascript\">var ").append(urivar).append(" = \""); boolean iframePostEnabled = renderer.getGlobalSettings().getAjaxFlags().isIframePostEnabled(); ubu.buildURI(sb, null, null, iframePostEnabled? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL); sb.append("\";</script>"); } } - }*/ + } if (toRender != null) { @@ -100,7 +106,15 @@ public class PanelRenderer implements ComponentRenderer { Panel panel = (Panel) source; Component toRender = panel.getContent(); - /*if (renderer.getGlobalSettings().getAjaxFlags().isDragAndDropEnabled()) { + if (toRender != null) { + // delegate header rendering to the content + renderer.renderBodyOnLoadJSFunctionCall(sb, toRender, rstate); + } + + //fxdiff + if (renderer.getGlobalSettings().getAjaxFlags().isIframePostEnabled() + && panel.doGetDragAndDrop() != null) { + //renderer.getGlobalSettings().getAjaxFlags().isDragAndDropEnabled() // first activate the drag and drop // drag and drop to look at DragAndDropImpl dndi = panel.doGetDragAndDrop(); @@ -108,29 +122,27 @@ public class PanelRenderer implements ComponentRenderer { DroppableImpl di = dndi.getDroppableImpl(); if (di != null) { boolean iframePostEnabled = renderer.getGlobalSettings().getAjaxFlags().isIframePostEnabled(); - String compPrefix = renderer.getComponentPrefix(panel); + String compPrefix = Renderer.getComponentPrefix(panel); + String urivar = Renderer.getComponentPrefix(panel) + "_dropurl"; + // we have a droppable - List dragIds = di.getAccepted(); - if (dragIds.size() > 0) { - for (Iterator it_dragids = dragIds.iterator(); it_dragids.hasNext();) { - Draggable draga = (Draggable) it_dragids.next(); - List cids = draga.getContainerIds(); - sb.append("Droppables.add('").append(compPrefix).append("',{containment:["); - int clen = cids.size(); - for (int i = 0; i < clen; i++) { - sb.append("\"").append((String)cids.get(i)).append("\""); - if (i < clen-1) sb.append(","); - } - String urivar = renderer.getComponentPrefix(panel)+"_dropurl"; - sb.append("],onDrop:function(el){o_info.drop=true;"); - if (iframePostEnabled) { - sb.append("var f = $('o_oaap'); f.v.value=el.id; f.action = ").append(urivar).append("; f.submit();"); - } else { - //TODO: also use the global post form, but the form must have a different target(self) - sb.append("document.location.href = ").append(urivar).append(" + \"?v=\"+el.id;"); - } - sb.append("}});\n"); + for (Draggable draga: di.getAccepted()) { + List<String> cids = draga.getContainerIds(); + sb.append("Droppables.add('").append(compPrefix).append("',{containment:["); + int clen = cids.size(); + for (int i = 0; i < clen; i++) { + sb.append("\"").append(cids.get(i)).append("\""); + if (i < clen-1) sb.append(","); } + sb.append("]"); + sb.append(",onDrop:function(el){o_info.drop=true;"); + if (iframePostEnabled) { + sb.append("var f = $('o_oaap'); f.v.value=el.id; f.action = ").append(urivar).append("; f.submit();"); + } else { + //TODO: also use the global post form, but the form must have a different target(self) + sb.append("document.location.href = ").append(urivar).append(" + \"?v=\"+el.id;"); + } + sb.append("}});\n"); } } @@ -139,28 +151,11 @@ public class PanelRenderer implements ComponentRenderer { // render code for - draggable - Draggable drag = dndi.getDraggable(); if (drag != null) { - String id = renderer.getComponentPrefix(toRender); - sb.append("new Draggable('").append(id).append("', {revert:function(el){if (o_info.drop) {o_info.drop = false; return false;} else return true;}});\n"); + String id = Renderer.getComponentPrefix(toRender); + sb.append("new Draggable('").append(id).append("',{handle:'handle',revert:function(el){if (o_info.drop) {o_info.drop = false; return false;} else return true;}});\n"); } } } - - - //<script type="text/javascript"> - //Droppables.add('cart', {accept:'products', - //onDrop:function(element){ - //new Ajax.Updater('items', '/shop/add', { - //onLoading:function(request){Element.show('indicator')}, - //onComplete:function(request){Element.hide('indicator')}, - //parameters:'id=' + encodeURIComponent(element.id), - //evalScripts:true, asynchronous:true})}, hoverclass:'cart-active'})</script> - - - }*/ - - if (toRender != null) { - // delegate header rendering to the content - renderer.renderBodyOnLoadJSFunctionCall(sb, toRender, rstate); } } diff --git a/src/main/java/org/olat/core/gui/components/tree/DnDFeedbackMapper.java b/src/main/java/org/olat/core/gui/components/tree/DnDFeedbackMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..ad45f12a1159856a805ce47fcf99996c049bd34e --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/tree/DnDFeedbackMapper.java @@ -0,0 +1,66 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ +package org.olat.core.gui.components.tree; + +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.StringMediaResource; + +/** + * + * Description:<br> + * Mapper which return feedback to the mouse over + * + * <P> + * Initial Date: 23 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-9: drag and drop in menu tree +public class DnDFeedbackMapper implements Mapper { + + private static final String ENCODING_UTF_8 = "utf-8"; + private static final String CONTENT_TYPE_JAVASCRIPT = "application/javascript;"; + + private final MenuTree menuTree; + + public DnDFeedbackMapper(MenuTree tree) { + menuTree = tree; + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + String dropNodeId = request.getParameter(MenuTree.NODE_IDENT); + String targetNodeId = request.getParameter(MenuTree.TARGET_NODE_IDENT); + boolean sibling = "end".equals(request.getParameter(MenuTree.SIBLING_NODE)) + || "yes".equals(request.getParameter(MenuTree.SIBLING_NODE)); + + StringMediaResource jsonResource = new StringMediaResource(); + jsonResource.setEncoding(ENCODING_UTF_8); + jsonResource.setContentType(CONTENT_TYPE_JAVASCRIPT); + if(menuTree.canDrop(dropNodeId, targetNodeId, sibling)) { + jsonResource.setData("{\"dropAllowed\":true}"); + } else { + jsonResource.setData("{\"dropAllowed\":false}"); + } + return jsonResource; + } +} diff --git a/src/main/java/org/olat/core/gui/components/tree/DnDTreeModel.java b/src/main/java/org/olat/core/gui/components/tree/DnDTreeModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a9fcbfd22e76ef26324e293a19b66495a346de2b --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/tree/DnDTreeModel.java @@ -0,0 +1,36 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ +package org.olat.core.gui.components.tree; + +/** + * + * Description:<br> + * Extension to the standard tree model + * + * <P> + * Initial Date: 23 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-9: drag and drop in menu tree +public interface DnDTreeModel extends TreeModel { + + public boolean canDrop(TreeNode droppedNode, TreeNode targetNode, boolean sibling); + +} diff --git a/src/main/java/org/olat/core/gui/components/tree/MenuTree.java b/src/main/java/org/olat/core/gui/components/tree/MenuTree.java index 8516904565dfd8100d5d5fa5455aefea03290032..13f2ba590de17897a3f96f599303fa8e2672539d 100644 --- a/src/main/java/org/olat/core/gui/components/tree/MenuTree.java +++ b/src/main/java/org/olat/core/gui/components/tree/MenuTree.java @@ -21,12 +21,23 @@ package org.olat.core.gui.components.tree; + +import static org.olat.core.gui.components.velocity.VelocityContainer.COMMAND_ID; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.olat.core.dispatcher.mapper.MapperRegistry; import org.olat.core.gui.GUIInterna; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent; import org.olat.core.gui.control.Controller; import org.olat.core.gui.render.ValidationResult; +import org.olat.core.util.StringHelper; +import org.olat.core.util.nodes.INode; import org.olat.core.util.tree.TreeHelper; /** @@ -42,6 +53,34 @@ public class MenuTree extends Component { */ public static final String NODE_IDENT = "nidle"; + /** + * Comment for <code>NODE_IDENT</code> + */ + //fxdiff VCRP-9: drag and drop in menu tree + public static final String TARGET_NODE_IDENT = "tnidle"; + + + /** + * Comment for <code>NODE_IDENT</code> + */ + //fxdiff VCRP-9: drag and drop in menu tree + public static final String SIBLING_NODE = "sne"; + + /** + * Comment for <code>NODE_IDENT</code> + */ + public static final String COMMAND_TREENODE = "ctntr"; + + /** + * Comment for <code>NODE_IDENT</code> + */ + public static final String TREENODE_OPEN = "open"; + + /** + * Comment for <code>NODE_IDENT</code> + */ + public static final String TREENODE_CLOSE = "close"; + /** * event fired when a treenode was clicked (all leaf nodes) */ @@ -51,10 +90,24 @@ public class MenuTree extends Component { * event fired when a treenode was expanded (all nodes except leafs) */ public static final String COMMAND_TREENODE_EXPANDED = "ctnex"; + + /** + * event fired when a treenode is dropper + */ + public static final String COMMAND_TREENODE_DROP = "ctdrop"; private TreeModel treeModel; private String selectedNodeId = null; + private Set<String> openNodeIds = new HashSet<String>(); private boolean expandServerOnly = true; // default is serverside menu + private boolean dragAndDropEnabled = false; + private boolean expandSelectedNode = true; + //fxdiff VCRP-9: drag and drop in menu tree + private String dragAndDropGroup; + private String dndFeedbackUri; + + private MapperRegistry mreg; + private final JSAndCSSComponent dragAndDropCmp; // for recording / visual marking purposes TreeNode markingTreeNode; @@ -66,6 +119,8 @@ public class MenuTree extends Component { */ public MenuTree(String name) { super(name); + //fxdiff VCRP-9: drag and drop in menu tree + dragAndDropCmp = new JSAndCSSComponent("jsComp", MenuTree.class, new String[]{"dd.js"}, null, false); } /** @@ -73,7 +128,7 @@ public class MenuTree extends Component { * @param eventListener */ public MenuTree(String name, Controller eventListener) { - super(name); + this(name); addListener(eventListener); } @@ -81,15 +136,38 @@ public class MenuTree extends Component { * @see org.olat.core.gui.components.Component#dispatchRequest(org.olat.core.gui.UserRequest) */ protected void doDispatchRequest(UserRequest ureq) { - selectedNodeId = ureq.getParameter(NODE_IDENT); - if (GUIInterna.isLoadPerformanceMode()) { String compPath = ureq.getParameter("en"); TreeNode selTreeNode = TreeHelper.resolveTreeNode(compPath, getTreeModel()); selectedNodeId = selTreeNode.getIdent(); } - handleClick(ureq, selectedNodeId); + //fxdiff VCRP-9: drag and drop in menu tree + String cmd = ureq.getParameter(COMMAND_ID); + String nodeId = ureq.getParameter(NODE_IDENT); + if(COMMAND_TREENODE_CLICKED.equals(cmd)) { + String openClose = ureq.getParameter(COMMAND_TREENODE); + if(!StringHelper.containsNonWhitespace(openClose)) { + selectedNodeId = nodeId; + } + handleClick(ureq, openClose, nodeId); + } else if (COMMAND_TREENODE_DROP.equals(cmd)) { + String targetNodeId = ureq.getParameter(TARGET_NODE_IDENT); + String sneValue = ureq.getParameter(SIBLING_NODE); + boolean sibling = StringHelper.containsNonWhitespace(sneValue); + boolean atTheEnd = "end".equals(sneValue); + handleDropped(ureq, nodeId, targetNodeId, sibling, atTheEnd); + } + } + + //fxdiff VCRP-9: drag and drop in menu tree + boolean canDrop(String dropNodeId, String targetNodeId, boolean sibling) { + if(treeModel instanceof DnDTreeModel) { + TreeNode dropNode = treeModel.getNodeById(dropNodeId); + TreeNode targetNode = treeModel.getNodeById(targetNodeId); + return ((DnDTreeModel)treeModel).canDrop(dropNode, targetNode, sibling); + } + return true; } /** @@ -111,11 +189,17 @@ public class MenuTree extends Component { } // -- recorder methods + //fxdiff VCRP-9: drag and drop in menu tree + private void handleDropped(UserRequest ureq, String droppedNodeId, String targetNodeId, boolean sibling, boolean atTheEnd) { + TreeDropEvent te = new TreeDropEvent(COMMAND_TREENODE_DROP, droppedNodeId, targetNodeId, !sibling, atTheEnd); + fireEvent(ureq, te); + super.setDirty(true); + } /** * @param selTreeNode */ - private void handleClick(UserRequest ureq, String selNodeId) { + private void handleClick(UserRequest ureq, String cmd, String selNodeId) { TreeNode selTreeNode = treeModel.getNodeById(selNodeId); // could be if upon click, an error occured -> timestamp check does not apply, but the tree model was regenerated (as in course) @@ -131,12 +215,22 @@ public class MenuTree extends Component { } TreeNode deleg = selTreeNode.getDelegate(); + boolean changeSelectedNodeId = false; if (deleg != null) { + changeSelectedNodeId = updateOpenedNode(selTreeNode, selNodeId, cmd); selNodeId = deleg.getIdent(); selTreeNode = deleg; } + + String subCmd = null; + if(TREENODE_CLOSE.equals(cmd)) { + subCmd = TreeEvent.COMMAND_TREENODE_CLOSE; + } else if (TREENODE_OPEN.equals(cmd)) { + subCmd = TreeEvent.COMMAND_TREENODE_OPEN; + } + changeSelectedNodeId |= updateOpenedNode(selTreeNode, selNodeId, cmd); - TreeEvent te = new TreeEvent(COMMAND_TREENODE_CLICKED, selNodeId); + TreeEvent te = new TreeEvent(COMMAND_TREENODE_CLICKED, subCmd, selNodeId); if (selTreeNode.getChildCount() > 0) { dirtyForUser = true; } // else dirtyForUser is false, since we clicked a node (which only results in the node beeing marked in a visual style) @@ -144,8 +238,67 @@ public class MenuTree extends Component { fireEvent(ureq, te); } + private boolean updateOpenedNode(TreeNode treeNode, String nodeId, String cmd) { + if(TREENODE_CLOSE.equals(cmd)) { + removeTreeNodeFromOpenList(treeNode, nodeId); + if(selectedNodeId != null && isChildOf(treeNode, selectedNodeId)) { + clearSelection(); + setSelectedNodeId(nodeId); + return true; + } + } else if (TREENODE_OPEN.equals(cmd)) { + openNodeIds.add(nodeId); + if(treeNode.getUserObject() instanceof String) { + openNodeIds.add((String)treeNode.getUserObject()); + } + } else if (cmd == null) { + openNodeIds.add(nodeId); + if(treeNode.getUserObject() instanceof String) { + openNodeIds.add((String)treeNode.getUserObject()); + } + } + return false; + } + private void removeTreeNodeFromOpenList(TreeNode treeNode, String nodeId) { + openNodeIds.remove(nodeId); + openNodeIds.remove(treeNode.getUserObject()); + + for(int i=treeNode.getChildCount(); i-->0; ) { + TreeNode child = (TreeNode)treeNode.getChildAt(i); + String childId = child.getIdent(); + TreeNode deleg = child.getDelegate(); + if (deleg != null) { + childId = deleg.getIdent(); + child = deleg; + } + removeTreeNodeFromOpenList(child, childId); + } + } + + private boolean isChildOf(INode treeNode, String childId) { + int childCount = treeNode.getChildCount(); + for(int i=0; i<childCount; i++) { + INode childNode = treeNode.getChildAt(i); + if(childNode.getIdent().equals(childId) || + (childNode instanceof TreeNode && childId.equals(((TreeNode)childNode).getUserObject())) || + (isChildOf(childNode, childId))) { + return true; + } + } + return false; + } + + @Override + //fxdiff VCRP-9: drag and drop in menu tree public void validate(UserRequest ureq, ValidationResult vr) { + if(dragAndDropEnabled) { + dragAndDropCmp.validate(ureq, vr); + if(mreg == null && treeModel instanceof DnDTreeModel) { + mreg = MapperRegistry.getInstanceFor(ureq.getUserSession()); + dndFeedbackUri = mreg.register(new DnDFeedbackMapper(this)); + } + } super.validate(ureq, vr); } @@ -171,6 +324,27 @@ public class MenuTree extends Component { setDirty(true); } + public Collection<String> getOpenNodeIds() { + return openNodeIds; + } + + public void setOpenNodeIds(Collection<String> openNodeIds) { + if(openNodeIds == null) { + this.openNodeIds.clear(); + } else { + this.openNodeIds = new HashSet<String>(openNodeIds); + } + setDirty(true); + } + + String getDndFeedbackUri() { + return dndFeedbackUri; + } + + public JSAndCSSComponent getDragAndDropCmp() { + return dragAndDropCmp; + } + /** * */ @@ -210,6 +384,47 @@ public class MenuTree extends Component { public void setExpandServerOnly(boolean expandServerOnly) { this.expandServerOnly = expandServerOnly; } + + /** + * @return Is Drag & Drop enable for the tree + */ + public boolean isDragAndDropEnabled() { + return dragAndDropEnabled; + } + + /** + * @param enableDragAndDrop Enable or not drag and drop + */ + public void setDragAndDropEnabled(boolean dragAndDropEnabled) { + this.dragAndDropEnabled = dragAndDropEnabled; + } + + /** + * @return The group of drag and drop (cannot be null). + */ + public String getDragAndDropGroup() { + return dragAndDropGroup == null ? "dndGroup" : dragAndDropGroup; + } + + /** + * @param dragAndDropGroup The group of drag and drop + */ + public void setDragAndDropGroup(String dragAndDropGroup) { + this.dragAndDropGroup = dragAndDropGroup; + } + + + /** + * Expand the selected node to view its children + * @return + */ + public boolean isExpandSelectedNode() { + return expandSelectedNode; + } + + public void setExpandSelectedNode(boolean expandSelectedNode) { + this.expandSelectedNode = expandSelectedNode; + } /** * @param nodeForum diff --git a/src/main/java/org/olat/core/gui/components/tree/MenuTreeRenderer.java b/src/main/java/org/olat/core/gui/components/tree/MenuTreeRenderer.java index 0fa9bed657654dedaf3def0277d89d0fab71b6b4..a8ef687133d5d6e177d1f59db00cd8172451e1ce 100644 --- a/src/main/java/org/olat/core/gui/components/tree/MenuTreeRenderer.java +++ b/src/main/java/org/olat/core/gui/components/tree/MenuTreeRenderer.java @@ -21,7 +21,14 @@ package org.olat.core.gui.components.tree; +import static org.olat.core.gui.components.velocity.VelocityContainer.COMMAND_ID; +import static org.olat.core.gui.components.tree.MenuTree.NODE_IDENT; +import static org.olat.core.gui.components.tree.MenuTree.COMMAND_TREENODE; +import static org.olat.core.gui.components.tree.MenuTree.COMMAND_TREENODE_CLICKED; +import static org.olat.core.gui.components.tree.MenuTree.COMMAND_TREENODE_DROP; + import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; @@ -43,6 +50,7 @@ import org.olat.core.util.tree.TreeHelper; * * @author Felix Jost, Florian Gnaegi */ +//fxdiff VCRP-9: there is here lots of change for drag and drop / open close in menu tree public class MenuTreeRenderer implements ComponentRenderer { /** @@ -59,34 +67,38 @@ public class MenuTreeRenderer implements ComponentRenderer { * org.olat.core.gui.render.URLBuilder, org.olat.core.gui.translator.Translator, * org.olat.core.gui.render.RenderResult, java.lang.String[]) */ + @Override public void render(Renderer renderer, StringOutput target, Component source, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { MenuTree tree = (MenuTree) source; - // unique ID used for DOM component identification - String compPrefix = renderer.getComponentPrefix(tree); - INode selNode = tree.getSelectedNode(); TreeNode root = tree.getTreeModel().getRootNode(); if (root == null) return; // tree is completely empty + Collection<String> openNodeIds = tree.getOpenNodeIds(); if (tree.isExpandServerOnly()) { // render only the expanded path using no javascript - List selPath = new ArrayList(5); + List<INode> selPath = new ArrayList<INode>(5); INode cur = selNode; if (cur == null) cur = root; // if no selection, select the first node to // expand the children // add all elems from selected path to reversed list -> first elem is // selected nodeid of the root node - while (cur != null) { selPath.add(0, cur); cur = cur.getParent(); } - target.append("\n<div class=\"b_tree\">"); + + AJAXFlags flags = renderer.getGlobalSettings().getAjaxFlags(); + target.append("\n<div id='dd1-ct' class='b_tree"); + if(tree.isDragAndDropEnabled()) { + target.append(" b_dd_ct"); + } + target.append("'>\n"); target.append("<ul class=\"b_tree_l0\">"); - renderLevel(target, 0, root, selPath, ubu, renderer.getGlobalSettings().getAjaxFlags(), compPrefix, tree.markingTreeNode); + renderLevel(target, 0, root, selPath, openNodeIds, ubu, flags, tree.markingTreeNode, tree); target.append("</ul>"); target.append("\n</div>"); } else { @@ -94,28 +106,43 @@ public class MenuTreeRenderer implements ComponentRenderer { } } - private void renderLevel(StringOutput target, int level, TreeNode curRoot, List selPath, URLBuilder ubu, AJAXFlags flags, String componentPrefix, TreeNode markedNode) { + private void renderLevel(StringOutput target, int level, TreeNode curRoot, List<INode> selPath, Collection<String> openNodeIds, URLBuilder ubu, AJAXFlags flags, TreeNode markedNode, MenuTree tree) { //TODO make performant INode curSel = null; if (level < selPath.size()) { - curSel = (INode) selPath.get(level); + curSel = selPath.get(level); } + + int chdCnt = curRoot.getChildCount(); + boolean iframePostEnabled = flags.isIframePostEnabled(); + boolean selected = (!selPath.isEmpty() && selPath.get(selPath.size() - 1) == curRoot); + boolean renderChildren = isRenderChildren(curSel, curRoot, selected, tree, openNodeIds); // get css class // item icon css class and icon decorator (for each icon quadrant a div, eclipse style) - // open menu item String cssClass = curRoot.getCssClass(); target.append("\n<li class=\""); // add custom css class target.append((cssClass == null ? "" : cssClass)); + if(selected) { + target.append(" b_tree_selected"); + } + String ident = curRoot.getIdent(); + target.append("\"><div id='dd").append(ident).append("' class=\"b_tree_item_wrapper"); + if(tree.isDragAndDropEnabled()) { + target.append(" b_dd_item"); + } + if(selected) { + target.append(" b_tree_selected"); + } target.append("\">"); - /*if (menuitemsDraggable && curSel == curRoot) { - //TODO:collect all ids as done in constructor and "[1,435,323].each(function(val){new Draggabl...." - target.append("<script type=\"text/javascript\">new Draggable('").append(curId).append("', {revert:function(el){o_indrag=true;return true;}});</script>"); - }*/ + if(tree.isDragAndDropEnabled()) { + appendDragAndDropObj(curRoot, tree, target, ubu, flags); + } + // render link String title = curRoot.getTitle(); title = StringEscapeUtils.escapeHtml(title).toString(); @@ -123,23 +150,69 @@ public class MenuTreeRenderer implements ComponentRenderer { if (markedNode != null && markedNode == curRoot) { target.append("<span style=\"border:2px solid red;\">"); } + // expand icon + // add ajax support and real open/close function + if (level != 0 && chdCnt > 0) { // root has not open/close icon, append open / close icon only if there is children + target.append("<a onclick=\"try {return o2cl()} catch(e){return false}\" href=\""); + + // Build menu item URI + if (GUIInterna.isLoadPerformanceMode()) { + // if in load perf test -> generate the treeposition and include it as param, since the nodeid itself is random and thus not replayable + String treePath = TreeHelper.buildTreePath(curRoot); + ubu.buildURI(target, new String[] { "en" }, new String[] { treePath }); + } else { + String cmd = renderChildren ? MenuTree.TREENODE_CLOSE : MenuTree.TREENODE_OPEN; + if (iframePostEnabled) { + ubu.buildURI(target, new String[] { COMMAND_ID, NODE_IDENT, COMMAND_TREENODE }, new String[] { COMMAND_TREENODE_CLICKED, curRoot.getIdent(), cmd }, AJAXFlags.MODE_TOBGIFRAME); + } else { + ubu.buildURI(target, new String[] { COMMAND_ID, NODE_IDENT, cmd }, new String[] { COMMAND_TREENODE_CLICKED, curRoot.getIdent(), cmd }); + } + } + + target.append("\""); + if(iframePostEnabled) { + ubu.appendTarget(target); + } + target.append(" class=\""); + if (renderChildren) { + target.append("b_tree_level_close"); + } else { + target.append("b_tree_level_open"); + } + target.append(" b_tree_oc_l").append(level).append("\"><span> </span></a>"); + } else if (level != 0 && chdCnt == 0) { + target.append("<span class=\"b_tree_level_leaf b_tree_oc_l").append(level).append("\"> </span>"); + } // Render menu item as link, also for active elements // mark active item as strong for accessablity reasons - target.append(selPath.get(selPath.size()-1) == curRoot ? "<strong>" : ""); + + target.append(selected ? "<strong>" : ""); target.append("<a class=\""); // add icon css class String iconCssClass = curRoot.getIconCssClass(); if (iconCssClass != null) { target.append(" b_tree_icon ").append(iconCssClass); } - if (selPath.get(selPath.size()-1) == curRoot) { + if (selected) { // add css class to identify active element target.append(" b_tree_selected"); } else if (curSel == curRoot) { // add css class to identify parents of active element target.append(" b_tree_selected_parents"); } + + //reapply the same rules to the second link + if(level != 0 && chdCnt > 0) { + if (renderChildren) { + target.append(" b_tree_level_label_close"); + } else { + target.append(" b_tree_level_label_open"); + } + } else if (level != 0 && chdCnt == 0) { + target.append(" b_tree_level_label_leaf"); + } + // add css class to identify level target.append(" b_tree_l").append(level); @@ -147,16 +220,15 @@ public class MenuTreeRenderer implements ComponentRenderer { target.append("\" onclick=\"try {if(o2cl()){Effect.ScrollTo('b_top'); return true;} else {return false;}} catch(e){return false}\" href=\""); // Build menu item URI - boolean iframePostEnabled = flags.isIframePostEnabled(); if (GUIInterna.isLoadPerformanceMode()) { // if in load perf test -> generate the treeposition and include it as param, since the nodeid itself is random and thus not replayable String treePath = TreeHelper.buildTreePath(curRoot); ubu.buildURI(target, new String[] { "en" }, new String[] { treePath }); } else { if (iframePostEnabled) { - ubu.buildURI(target, new String[] { MenuTree.NODE_IDENT }, new String[] { curRoot.getIdent() }, AJAXFlags.MODE_TOBGIFRAME); + ubu.buildURI(target, new String[] { COMMAND_ID, NODE_IDENT }, new String[] { COMMAND_TREENODE_CLICKED, curRoot.getIdent() }, AJAXFlags.MODE_TOBGIFRAME); } else { - ubu.buildURI(target, new String[] { MenuTree.NODE_IDENT }, new String[] { curRoot.getIdent() }); + ubu.buildURI(target, new String[] { COMMAND_ID, NODE_IDENT }, new String[] { COMMAND_TREENODE_CLICKED, curRoot.getIdent() }); } } // Add menu item title as alt hoover text @@ -169,64 +241,121 @@ public class MenuTreeRenderer implements ComponentRenderer { target.append(">"); - int chdCnt = curRoot.getChildCount(); - if (iconCssClass != null) { - String deco1 = curRoot.getIconDecorator1CssClass(); - if (deco1 != null) - target.append("<span class=\"b_tree_icon_decorator ").append(deco1).append("\"></span>"); - - String deco2 = curRoot.getIconDecorator2CssClass(); - if (deco2 != null) - target.append("<span class=\"b_tree_icon_decorator ").append(deco2).append("\"></span>"); - - String deco3 = curRoot.getIconDecorator3CssClass(); - if (deco3 != null) - target.append("<span class=\"b_tree_icon_decorator ").append(deco3).append("\"></span>"); - - String deco4 = curRoot.getIconDecorator4CssClass(); - if (deco4 != null) - target.append("<span class=\"b_tree_icon_decorator ").append(deco4).append("\"></span>"); - } - // expand icon - // FIXME:FG: add ajax support and real open/close function - if (level != 0) { // RootNode has no open / close icon - if (chdCnt > 0) { // append open / close icon - if (curSel == curRoot) { - target.append("<span class=\"b_tree_level_close\"></span>"); - } else { - target.append("<span class=\"b_tree_level_open\"></span>"); - } - } - } + appendDecorators(curRoot, target); // display title and close menu item + target.append("<span"); + if(tree.isDragAndDropEnabled()) { + target.append(" class='b_dd_item' id='da").append(ident).append("'"); + } + target.append(">"); if(title != null && title.equals("")) title = " "; - target.append(title).append("</a>"); + target.append(title).append("</span></a>"); // mark active item as strong for accessablity reasons - target.append(selPath.get(selPath.size()-1) == curRoot ? "</strong>" : ""); - + target.append(selected ? "</strong>" : ""); if (markedNode != null && markedNode == curRoot) { target.append("</span>"); } + target.append("</div>"); + + //append div to drop as sibling + if(!renderChildren && tree.isDragAndDropEnabled()) { + appendSiblingDropObj(curRoot, level, tree, target, false); + } - if (curRoot.getChildCount() > 0 && curSel == curRoot) { - // render children as new level - target.append("\n<ul class=\""); - // add css class to identify level - target.append(" b_tree_l").append(level + 1 ); - target.append("\">"); - // render all the nodes from this level - for (int i = 0; i < chdCnt; i++) { - TreeNode curChd = (TreeNode) curRoot.getChildAt(i); - renderLevel(target, level + 1, curChd, selPath, ubu, flags, componentPrefix, markedNode); + if (renderChildren) { + //open / close ul + renderChildren(target, level, curRoot, selPath, openNodeIds, ubu, flags, markedNode, tree); + + //append div to drop as sibling after the children + if(tree.isDragAndDropEnabled()) { + appendSiblingDropObj(curRoot, level, tree, target, true); } - target.append("</ul>"); } // close item level target.append("</li>"); + } + + //fxdiff VCRP-9: drag and drop in menu tree + private void renderChildren(StringOutput target, int level, TreeNode curRoot, List<INode> selPath, Collection<String> openNodeIds, URLBuilder ubu, AJAXFlags flags, TreeNode markedNode, MenuTree tree) { + int chdCnt = curRoot.getChildCount(); + // render children as new level + target.append("\n<ul class=\""); + // add css class to identify level + target.append(" b_tree_l").append(level + 1); + target.append("\">"); + // render all the nodes from this level + for (int i = 0; i < chdCnt; i++) { + TreeNode curChd = (TreeNode) curRoot.getChildAt(i); + renderLevel(target, level + 1, curChd, selPath, openNodeIds, ubu, flags, markedNode, tree); + } + target.append("</ul>"); + } + + //fxdiff VCRP-9: drag and drop in menu tree + private void appendSiblingDropObj(TreeNode node, int level, MenuTree tree, StringOutput target, boolean after) { + String id = (after ? "dt" : "ds") + node.getIdent(); + String dndGroup = tree.getDragAndDropGroup(); + target.append("<div id='").append(id).append("' class='b_dd_sibling b_dd_sibling_l").append(level).append("'>") + .append("<script type='text/javascript'>Ext.get('").append(id).append("').dd = new Ext.dd.DDTarget('").append(id).append("','").append(dndGroup).append("');</script>") + .append(" </div>"); + } + + //fxdiff VCRP-9: drag and drop in menu tree + private void appendDragAndDropObj(TreeNode node, MenuTree tree, StringOutput target, URLBuilder ubu, AJAXFlags flags) { + String id = node.getIdent(); + String dndGroup = tree.getDragAndDropGroup(); + String feedBackUri = tree.getDndFeedbackUri(); + StringOutput endUrl = new StringOutput(); + ubu.buildURI(endUrl, new String[] { COMMAND_ID, NODE_IDENT }, new String[] { COMMAND_TREENODE_DROP, id }, flags.isIframePostEnabled() ? AJAXFlags.MODE_TOBGIFRAME : AJAXFlags.MODE_NORMAL); + target.append("<script type='text/javascript'>") + .append("Ext.get('dd").append(id).append("').dd = new Ext.fxMenuTree.DDProxy('dd").append(id).append("','").append(dndGroup).append("','").append(endUrl).append("','").append(feedBackUri).append("');") + .append("Ext.get('da").append(id).append("').dd = new Ext.fxMenuTree.DDProxy('da").append(id).append("','").append(dndGroup).append("','").append(endUrl).append("','").append(feedBackUri).append("');") + .append("</script>"); + } + + //fxdiff VCRP-9: drag and drop in menu tree + private void appendDecorators(TreeNode curRoot, StringOutput target) { + String deco1 = curRoot.getIconDecorator1CssClass(); + if (deco1 != null) + target.append("<span class=\"b_tree_icon_decorator ").append(deco1).append("\"></span>"); + + String deco2 = curRoot.getIconDecorator2CssClass(); + if (deco2 != null) + target.append("<span class=\"b_tree_icon_decorator ").append(deco2).append("\"></span>"); + String deco3 = curRoot.getIconDecorator3CssClass(); + if (deco3 != null) + target.append("<span class=\"b_tree_icon_decorator ").append(deco3).append("\"></span>"); + + String deco4 = curRoot.getIconDecorator4CssClass(); + if (deco4 != null) + target.append("<span class=\"b_tree_icon_decorator ").append(deco4).append("\"></span>"); + } + + private boolean isRenderChildren(INode curSel, TreeNode curRoot, boolean selected, MenuTree tree, Collection<String> openNodeIds) { + if(curRoot.getChildCount() == 0) { + //nothing to do + return false; + } + + //open open nodes + if(openNodeIds != null && !openNodeIds.isEmpty()) { + if(openNodeIds.contains(curRoot.getIdent())) { + return true; + } else if (curRoot.getUserObject() instanceof String && openNodeIds.contains(curRoot.getUserObject())) { + return true; + } + } + + //don't automatically open the children of the selected node + if(selected && !tree.isExpandSelectedNode()) { + return false; + } + //open the path of the selected node + return (curSel == curRoot); } /** diff --git a/src/main/java/org/olat/core/gui/components/tree/TreeDropEvent.java b/src/main/java/org/olat/core/gui/components/tree/TreeDropEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..38725cbf3041ea457bfc72f32cdd2161da9d1431 --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/tree/TreeDropEvent.java @@ -0,0 +1,82 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ +package org.olat.core.gui.components.tree; + +import org.olat.core.gui.control.Event; + +/** + * + * Description:<br> + * Event for the drag and drop function in menu tree + * + * <P> + * Initial Date: 23 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-9: drag and drop in menu tree +public class TreeDropEvent extends Event { + + private static final long serialVersionUID = -2204436311054973710L; + + private final String droppedNodeId; + private final String targetNodeId; + private final boolean asChild; + private final boolean atTheEnd; + + /** + * + * @param command + * @param nodeId + */ + public TreeDropEvent(String command, String droppedNodeId, String targetNodeId, boolean asChild, boolean atTheEnd) { + super(command); + this.droppedNodeId = droppedNodeId; + this.targetNodeId = targetNodeId; + this.asChild = asChild; + this.atTheEnd = atTheEnd; + } + + /** + * @return the dropped nodeId + */ + public String getDroppedNodeId() { + return droppedNodeId; + } + + /** + * @return The targeted node id + */ + public String getTargetNodeId() { + return targetNodeId; + } + + public boolean isAsChild() { + return asChild; + } + + public boolean isAtTheEnd() { + return atTheEnd; + } + + @Override + public String toString() { + return "TreeDropEvent:{cmd:"+getCommand()+", droppedNodeId:"+droppedNodeId+", targetNodeId:"+targetNodeId+"}"; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/components/tree/TreeEvent.java b/src/main/java/org/olat/core/gui/components/tree/TreeEvent.java index 7fa24b0343d7676945199490063aa4c2c76ffe3b..bfdb19360e7e6d97203b99c37cca423c5cd174b5 100644 --- a/src/main/java/org/olat/core/gui/components/tree/TreeEvent.java +++ b/src/main/java/org/olat/core/gui/components/tree/TreeEvent.java @@ -34,6 +34,14 @@ public class TreeEvent extends Event { * Comment for <code>COMMAND_TREENODE_CLICKED</code> */ public static final String COMMAND_TREENODE_CLICKED = "tectncl"; + /** + * Comment for <code>COMMAND_TREENODE_CLICKED</code> + */ + public static final String COMMAND_TREENODE_OPEN = "tectnopen"; + /** + * Comment for <code>COMMAND_TREENODE_CLICKED</code> + */ + public static final String COMMAND_TREENODE_CLOSE = "tectnclose"; /** * Comment for <code>COMMAND_TREENODES_SELECTED</code> */ @@ -50,6 +58,7 @@ public class TreeEvent extends Event { private String nodeId; private List nodeIds; + private String subCommand; /** * @@ -57,7 +66,12 @@ public class TreeEvent extends Event { * @param nodeId */ public TreeEvent(String command, String nodeId) { + this(command, null, nodeId); + } + + public TreeEvent(String command, String subCommand, String nodeId) { super(command); + this.subCommand = subCommand; this.nodeId = nodeId; } @@ -66,7 +80,16 @@ public class TreeEvent extends Event { * @param nodeIds */ public TreeEvent(String command, List nodeIds) { + this(command, null, nodeIds); + } + + /** + * @param command + * @param nodeIds + */ + public TreeEvent(String command, String subCommand, List nodeIds) { super(command); + this.subCommand = subCommand; this.nodeIds = nodeIds; } @@ -84,8 +107,12 @@ public class TreeEvent extends Event { return nodeIds; } + public String getSubCommand() { + return subCommand; + } + public String toString() { - return "TreeEvent:{cmd:"+getCommand()+", nodeid:"+nodeId+"}"; + return "TreeEvent:{cmd:"+getCommand()+"," + (subCommand == null ? "" : "sub:" + subCommand) + " nodeid:"+nodeId+"}"; } } diff --git a/src/main/java/org/olat/core/gui/components/tree/_static/js/dd.js b/src/main/java/org/olat/core/gui/components/tree/_static/js/dd.js new file mode 100644 index 0000000000000000000000000000000000000000..29c069ed80810bfcc9115972e9bd2f8372f0f8b5 --- /dev/null +++ b/src/main/java/org/olat/core/gui/components/tree/_static/js/dd.js @@ -0,0 +1,79 @@ +Ext.namespace('Ext.fxMenuTree'); +Ext.fxMenuTree.DDProxy = function(id, group, dropUrl, overUrl) { + var config = {dragData:{end:dropUrl, over:overUrl}, scope:this }; + Ext.fxMenuTree.DDProxy.superclass.constructor.call(this, id, group, config); +}; + +Ext.extend(Ext.fxMenuTree.DDProxy, Ext.dd.DDProxy, { + startDrag: function(x, y) { + var dragEl = Ext.get(this.getDragEl()); + var el = Ext.get(this.getEl()); + dragEl.applyStyles({border:'','z-index':2000}); + dragEl.update(el.dom.innerHTML); + dragEl.addClass(el.dom.className + ' b_dd_proxy'); + }, + + onDragOver: function(e, targetId) { + if(targetId && (targetId.indexOf('dd') == 0 || targetId.indexOf('ds') == 0 || targetId.indexOf('dt') == 0 || targetId.indexOf('da') == 0)) { + var target = Ext.get(targetId); + this.lastTarget = target; + if(this.config.dragData.over && this.config.dragData.over.length > 0) { + var url = this.config.dragData.over + "/"; + var dropId = this.id.substring(2,this.id.length); + var targetId = this.lastTarget.id.substring(2,this.lastTarget.id.length); + var sibling = ""; + if(this.lastTarget.id.indexOf('ds') == 0) { + sibling = "yes"; + } else if(this.lastTarget.id.indexOf('dt') == 0) { + sibling = "end"; + } + //use prototype for the Ajax call + var stat = new Ajax.Request(url, { + method: 'get', + asynchronous : false, + parameters : { nidle:dropId, tnidle:targetId, sne:sibling }, + onSuccess: function(transport) { + //use prototype to parse JSON response + var response = transport.responseText.evalJSON(); + if(response.dropAllowed) { + target.addClass('b_dd_over'); + } else { + target.removeClass('b_dd_over'); + } + } + }); + } else { + target.addClass('b_dd_over'); + } + } + }, + + onDragOut: function(e, targetId) { + if(targetId && (targetId.indexOf('dd') == 0 || targetId.indexOf('ds') == 0 || targetId.indexOf('dt') == 0 || targetId.indexOf('da') == 0)) { + var target = Ext.get(targetId); + this.lastTarget = null; + target.removeClass('b_dd_over'); + } + }, + + endDrag: function() { + var dragEl = Ext.get(this.getDragEl()); + var el = Ext.get(this.getEl()); + if(this.lastTarget) { + Ext.get(this.lastTarget).appendChild(el); + el.applyStyles({position:'', width:''}); + var url = this.config.dragData.end; + if(url.lastIndexOf('/') == (url.length - 1)) { + url = url.substring(0,url.length-1); + } + var targetId = this.lastTarget.id.substring(2,url.length); + url += '%3Atnidle%3A' + targetId; + if(this.lastTarget.id.indexOf('ds') == 0) { + url += '%3Asne%3Ayes'; + } else if(this.lastTarget.id.indexOf('dt') == 0) { + url += '%3Asne%3Aend'; + } + frames['oaa0'].location.href = url + '/'; + } + } +}); \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/control/JSAndCSSAdderImpl.java b/src/main/java/org/olat/core/gui/control/JSAndCSSAdderImpl.java index 492f7f586d79472c519065c136d5559aa67d6c9c..4817763ed16333e721fed39bebbe408879326984 100644 --- a/src/main/java/org/olat/core/gui/control/JSAndCSSAdderImpl.java +++ b/src/main/java/org/olat/core/gui/control/JSAndCSSAdderImpl.java @@ -293,6 +293,11 @@ public class JSAndCSSAdderImpl extends JSAndCSSAdder implements ComponentRendere * @see org.olat.core.gui.control.JSAndCSSAdder#getMappedPathFor(java.lang.Class, java.lang.String) */ public String getMappedPathFor(Class baseClass, String fileName) { + //fxdiff make it possible to put absolute paths to js-files + // e.g. /olat/raw/fx-OLAT/themes/frentix/myjs.js FXOLAT-310 + if(baseClass == null){ + return fileName; + } String packkey = getKey(baseClass); String mappath = keyToPath.get(packkey); if (mappath == null) { diff --git a/src/main/java/org/olat/core/gui/control/dragdrop/DragSourceImpl.java b/src/main/java/org/olat/core/gui/control/dragdrop/DragSourceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..79bae7334f48985a4305513d5ad94756466e30de --- /dev/null +++ b/src/main/java/org/olat/core/gui/control/dragdrop/DragSourceImpl.java @@ -0,0 +1,41 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.core.gui.control.dragdrop; + +public class DragSourceImpl implements DragSource { + + private final Object source; + + public DragSourceImpl(Object source) { + this.source = source; + } + + @Override + public Object getSource() { + return source; + } + + @Override + public String getSubId() { + return null; + } +} diff --git a/src/main/java/org/olat/core/gui/control/dragdrop/Draggable.java b/src/main/java/org/olat/core/gui/control/dragdrop/Draggable.java index 8a4feb6657d1aec2591047545ab7c19fbd83e4b6..589f1a9c3ab8549f25a13daa939696ca1be09592 100644 --- a/src/main/java/org/olat/core/gui/control/dragdrop/Draggable.java +++ b/src/main/java/org/olat/core/gui/control/dragdrop/Draggable.java @@ -36,7 +36,7 @@ public interface Draggable { * used by the Droppable to add these ids (the ids of the div(s) surrounding the draggable elements) to the ids which are accepted to be dropped. * @return a list of ids(Strings) */ - public List getContainerIds(); + public List<String> getContainerIds(); /** * diff --git a/src/main/java/org/olat/core/gui/control/dragdrop/DropTarget.java b/src/main/java/org/olat/core/gui/control/dragdrop/DropTarget.java index a43471ecddc6db85f1814b53bfc38c4f83cd326e..5ffec8933dc142c088ddaef06e53e0a73d6e9da9 100644 --- a/src/main/java/org/olat/core/gui/control/dragdrop/DropTarget.java +++ b/src/main/java/org/olat/core/gui/control/dragdrop/DropTarget.java @@ -30,5 +30,7 @@ package org.olat.core.gui.control.dragdrop; * @author Felix */ public interface DropTarget { + + public Object getTarget(); } diff --git a/src/main/java/org/olat/core/gui/control/dragdrop/DropTargetImpl.java b/src/main/java/org/olat/core/gui/control/dragdrop/DropTargetImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a0078188362197e6f1616fe96224059ac387389b --- /dev/null +++ b/src/main/java/org/olat/core/gui/control/dragdrop/DropTargetImpl.java @@ -0,0 +1,43 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.gui.control.dragdrop; + +/** + * + * <h3>Description:</h3> + * Trivial implementation of DropTarget + * <p> + * Initial Date: 31 aug. 2010 <br> + * @author srosse, stephanerosse@frentix.com, http://www.frentix.com + */ +public class DropTargetImpl implements DropTarget { + + private final Object source; + + public DropTargetImpl(Object source) { + this.source = source; + } + + @Override + public Object getTarget() { + return source; + } +} diff --git a/src/main/java/org/olat/core/gui/control/dragdrop/DroppableImpl.java b/src/main/java/org/olat/core/gui/control/dragdrop/DroppableImpl.java index bc10f779175222cb62ea2d5a347480f2bee471d1..cdfcbb686567d222ca48bfabf775e1e6ca9eb4fa 100644 --- a/src/main/java/org/olat/core/gui/control/dragdrop/DroppableImpl.java +++ b/src/main/java/org/olat/core/gui/control/dragdrop/DroppableImpl.java @@ -33,7 +33,7 @@ import java.util.List; * @author Felix Jost */ public class DroppableImpl implements Droppable { - private List accepted = new ArrayList(); + private List<Draggable> accepted = new ArrayList<Draggable>(); /** * @see org.olat.core.gui.control.dragdrop.Droppable#addAcceptedDraggable(org.olat.core.gui.control.dragdrop.Draggable) @@ -46,7 +46,7 @@ public class DroppableImpl implements Droppable { * * @return a list of accepted Dragables */ - public List getAccepted() { + public List<Draggable> getAccepted() { return accepted; } diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/tree/TreeController.java b/src/main/java/org/olat/core/gui/control/generic/ajax/tree/TreeController.java index 38ac0156386d04529d8f3bbe66d481d5d49403b8..55b8127f3b19b14e9bbfc5c39298fbe9836679a7 100644 --- a/src/main/java/org/olat/core/gui/control/generic/ajax/tree/TreeController.java +++ b/src/main/java/org/olat/core/gui/control/generic/ajax/tree/TreeController.java @@ -214,6 +214,11 @@ public class TreeController extends BasicController { putInitialPanel(mainVC); } + + //fxdiff FXOLAT-132: alert unsaved changes in HTML editor + public long getTreePanelID() { + return mainVC.getDispatchID(); + } /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, diff --git a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java index 816047e2c6176ed3c6208af17159158802cfded2..25b17cdabcf966ceb0a7a6ca2190d7d953173746 100644 --- a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java +++ b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableCalloutWindowController.java @@ -118,6 +118,7 @@ public class CloseableCalloutWindowController extends BasicController { } else { // Fallback to old-school modal dialog cmc = new CloseableModalController(wControl, "close", calloutWindowContent, true, title, closable); + listenTo(cmc); putInitialPanel(new Panel("empty")); } } diff --git a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableModalWindowController.java b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableModalWindowController.java index b34c2b869943783b227f5c8e9bb9b1ca8a2b91fc..35cbd1c7148af7684876d3f51dc6e480b20d02dc 100644 --- a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableModalWindowController.java +++ b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/CloseableModalWindowController.java @@ -57,11 +57,34 @@ public class CloseableModalWindowController extends BasicController { mainVC = createVelocityContainer("modalwindow"); if (title != null) mainVC.contextPut("title", title); mainVC.put("content", modalContent); + setCloseable(true); + setIgnoreCookie(false); modalId = "o_"+id; mainVC.contextPut("panelName", modalId); putInitialPanel(mainVC); } + /** + * fxdiff:: FXOLAT-232 + * make the "CloseableModal..." not closable ^^ + * + * @param closeable + */ + public void setCloseable(boolean closeable){ + mainVC.contextPut("closeable", closeable); + } + + /** + * fxdiff:: FXOLAT-232 + * make the ext-window ignore the cookie-value for width and height. + * i.e. every subsequent <code>setInitialWindowSize</code> call will ignore the cookie + * + * @param ignore + */ + public void setIgnoreCookie(boolean ignore){ + mainVC.contextPut("ignorecookie", ignore); + } + /** * set the initial size of modal window. if changed by user once, this won't * be read until cookies have expired or got deleted! @@ -74,6 +97,27 @@ public class CloseableModalWindowController extends BasicController { mainVC.contextPut("height", height); } + /** + * fxdiff: FXOLAT-232 make ext-window resizable + * + * resizes the Ext-Window to the given size. + * Note: this is async. resize is done on next successful poll + * + * @param width + * @param height + */ + public void resizeWindow(int width, int height){ + setInitialWindowSize(width, height); + + StringBuilder sb = new StringBuilder(); + sb.append("if( Ext.getCmp('").append(modalId).append("') != null ) {"); + sb.append(" Ext.getCmp('").append(modalId).append("').setHeight(").append(height).append(");"); + sb.append(" Ext.getCmp('").append(modalId).append("').setWidth(").append(width).append(");"); + //sb.append("console.log('manually resize the window to ").append(width).append(" and ").append(height).append(" (the js-way)'); "); + sb.append("}"); + getWindowControl().getWindowBackOffice().sendCommandTo(new JSCommand(sb.toString())); + } + /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, * org.olat.core.gui.components.Component, diff --git a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/_content/modalwindow.html b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/_content/modalwindow.html index 187435f1485569b89325ca6388e45ba5b89e42e9..6b065af02ada3cb079b0be27a47591b714530494 100644 --- a/src/main/java/org/olat/core/gui/control/generic/closablewrapper/_content/modalwindow.html +++ b/src/main/java/org/olat/core/gui/control/generic/closablewrapper/_content/modalwindow.html @@ -20,7 +20,8 @@ Ext.onReady(function(){ ${panelName}winpop.close(); } else { var pos = Ext.util.Cookies.get('${panelName}_pos'); - if(pos != undefined) { + + if(pos != undefined && ($ignorecookie != true )) { var posArr = pos.split(','); winHeight = parseInt(posArr[0]); winWidth = parseInt(posArr[1]); @@ -62,7 +63,7 @@ Ext.onReady(function(){ var ${panelName}winpop = new Ext.Window({ id: '${panelName}', title: '$!title', - closable:true, + closable: $closeable, collapsible:false, constrain: true, resizable: true, diff --git a/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java index 1a965bf95634dfa7abb17e16cb6e9c02b879299e..38b013d71058a8be130310802a0e192fa5a8aa34 100644 --- a/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java +++ b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java @@ -30,7 +30,6 @@ import org.olat.core.commons.modules.bc.meta.MetaInfoFactory; import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.gui.components.tree.GenericTreeModel; -import org.olat.core.gui.components.tree.GenericTreeNode; import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.OlatRelPathImpl; import org.olat.core.util.vfs.VFSItem; @@ -63,13 +62,16 @@ public class OlatRootFolderTreeModel extends GenericTreeModel { public OlatRootFolderTreeModel(OlatRootFolderImpl root) { setRootNode(createNode(root)); - makeChildren(getRootNode(), root); + getRootNode().getChildCount(); + // fxdiff: + //makeChildren(getRootNode(), root); } public OlatRootFolderTreeModel(OlatRootFolderImpl root, VFSItemFilter filter) { this.filter = filter; setRootNode(createNode(root)); - makeChildren(getRootNode(), root); + getRootNode().getChildCount(); + //makeChildren(getRootNode(), root); } public OlatRootFolderTreeModel(OlatRootFolderImpl root, @@ -77,7 +79,8 @@ public class OlatRootFolderTreeModel extends GenericTreeModel { this.filter = filter; this.comparator = comparator; setRootNode(createNode(root)); - makeChildren(getRootNode(), root); + getRootNode().getChildCount(); + //makeChildren(getRootNode(), root); } /** @@ -86,14 +89,14 @@ public class OlatRootFolderTreeModel extends GenericTreeModel { * @param node * @param root */ - private void makeChildren(GenericTreeNode node, OlatRootFolderImpl root) { + protected void makeChildren(OlatRootFolderTreeNode node, OlatRootFolderImpl root) { List<MetaTagged> children = castToMetaTaggables(root.getItems(filter)); if (comparator != null) { Collections.sort(children, comparator); } for (OlatRelPathImpl child : castToRelPathItems(children)) { // create a node for each child and add it - GenericTreeNode childNode = createNode(child); + OlatRootFolderTreeNode childNode = createNode(child); node.addChild(childNode); if (child instanceof OlatRootFolderImpl) { // add the child's children recursively @@ -142,8 +145,8 @@ public class OlatRootFolderTreeModel extends GenericTreeModel { * * @param item */ - private GenericTreeNode createNode(OlatRelPathImpl item) { - GenericTreeNode node = new GenericTreeNode(); + private OlatRootFolderTreeNode createNode(OlatRelPathImpl item) { + OlatRootFolderTreeNode node = new OlatRootFolderTreeNode(item, this); MetaInfo meta = MetaInfoFactory.createMetaInfoFor(item); if (meta != null) { String title = meta.getTitle(); @@ -165,8 +168,8 @@ public class OlatRootFolderTreeModel extends GenericTreeModel { * @see org.olat.core.gui.components.tree.GenericTreeModel#getRootNode() */ @Override - public GenericTreeNode getRootNode() { - return (GenericTreeNode) super.getRootNode(); + public OlatRootFolderTreeNode getRootNode() { + return (OlatRootFolderTreeNode) super.getRootNode(); } } diff --git a/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeNode.java b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeNode.java new file mode 100644 index 0000000000000000000000000000000000000000..a754c263bf9a0fd4e0550a09f3c1c7244b5936de --- /dev/null +++ b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeNode.java @@ -0,0 +1,57 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.core.gui.control.generic.folder; + +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.components.tree.GenericTreeNode; +import org.olat.core.util.vfs.OlatRelPathImpl; + +/** + * + * <h3>Description:</h3> + * <p> + * Initial Date: 26 nov. 2010 <br> + * @author srosse, srosse@frentix.com, www.frentix.com + */ +public class OlatRootFolderTreeNode extends GenericTreeNode { + + private boolean loaded = false; + private final OlatRelPathImpl item; + private final OlatRootFolderTreeModel model; + + public OlatRootFolderTreeNode(OlatRelPathImpl item, OlatRootFolderTreeModel model) { + super(); + this.item = item; + this.model = model; + } + + @Override + public int getChildCount() { + int count = super.getChildCount(); + if(count == 0 && !loaded && item instanceof OlatRootFolderImpl) { + model.makeChildren(this, (OlatRootFolderImpl)item); + loaded = true; + count = super.getChildCount(); + } + return count; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/control/generic/iframe/IFrameDisplayController.java b/src/main/java/org/olat/core/gui/control/generic/iframe/IFrameDisplayController.java index 44cb4fb977ecffde2c64cab12dd411b17de4f953..790d5d4e76e2696c0db7261b5b401287a1c8961a 100644 --- a/src/main/java/org/olat/core/gui/control/generic/iframe/IFrameDisplayController.java +++ b/src/main/java/org/olat/core/gui/control/generic/iframe/IFrameDisplayController.java @@ -823,7 +823,7 @@ public class IFrameDisplayController extends BasicController implements GenericE // window. Window myWindow = getWindowControl().getWindowBackOffice().getWindow(); CustomCSS currentCustomCSS = (CustomCSS) myWindow.getAttribute(BaseFullWebappController.CURRENT_CUSTOM_CSS_KEY); - if (currentCustomCSS != null) customCssURL = currentCustomCSS.getCSSURL(); + if (currentCustomCSS != null) customCssURL = currentCustomCSS.getCSSURLIFrame(); // done, remove us as listener getWindowControl().getWindowBackOffice().removeCycleListener(this); } diff --git a/src/main/java/org/olat/core/gui/control/generic/popup/BasePopupWindowControllerCreator.java b/src/main/java/org/olat/core/gui/control/generic/popup/BasePopupWindowControllerCreator.java index cb923487ff26fbb2bb3abc1af41b4caec422f70b..28f993cfdba4ff74ec02e0755fc840712a3b94f4 100644 --- a/src/main/java/org/olat/core/gui/control/generic/popup/BasePopupWindowControllerCreator.java +++ b/src/main/java/org/olat/core/gui/control/generic/popup/BasePopupWindowControllerCreator.java @@ -38,5 +38,10 @@ public class BasePopupWindowControllerCreator implements PopupBrowserWindowContr PopupBrowserWindowController pbwc = new SimplePopupWindowBaseController(lureq, lwControl, contentControllerCreator); return pbwc; } - + + //fxdiff + public PopupBrowserWindowController createNewUnauthenticatedPopupWindowController(UserRequest lureq, WindowControl lwControl, + ControllerCreator contentControllerCreator) { + return createNewPopupBrowserController(lureq, lwControl, contentControllerCreator); + } } diff --git a/src/main/java/org/olat/core/gui/control/generic/popup/PopupBrowserWindowControllerCreator.java b/src/main/java/org/olat/core/gui/control/generic/popup/PopupBrowserWindowControllerCreator.java index 90d5cead5c6f2caceba3b1c41ea2243f0e475bf8..8eadadf713ac8c06a5df0a9e52733431ecfb76e7 100644 --- a/src/main/java/org/olat/core/gui/control/generic/popup/PopupBrowserWindowControllerCreator.java +++ b/src/main/java/org/olat/core/gui/control/generic/popup/PopupBrowserWindowControllerCreator.java @@ -40,4 +40,7 @@ public interface PopupBrowserWindowControllerCreator { * @return */ public PopupBrowserWindowController createNewPopupBrowserController(UserRequest lureq, WindowControl lwControl, ControllerCreator contentControllerCreator); + + //fxdiff + public PopupBrowserWindowController createNewUnauthenticatedPopupWindowController(UserRequest lureq, WindowControl lwControl, ControllerCreator contentControllerCreator); } diff --git a/src/main/java/org/olat/core/gui/control/generic/portal/Portal.java b/src/main/java/org/olat/core/gui/control/generic/portal/Portal.java index 2a2d32caf0a10de32b21066ca83abcfa2c24835c..3c8bc34b3fd227c2edef352bb177de7b3edd3dac 100644 --- a/src/main/java/org/olat/core/gui/control/generic/portal/Portal.java +++ b/src/main/java/org/olat/core/gui/control/generic/portal/Portal.java @@ -22,7 +22,6 @@ package org.olat.core.gui.control.generic.portal; import java.util.List; -import java.util.Map; import org.olat.core.gui.control.Disposable; /** diff --git a/src/main/java/org/olat/core/gui/control/generic/portal/PortalImpl.java b/src/main/java/org/olat/core/gui/control/generic/portal/PortalImpl.java index 89aec4555b3fd7c33012fce6c5f9c0673fb88c29..6ed590bad8da7a10d9a319de27cb4c535feab588 100644 --- a/src/main/java/org/olat/core/gui/control/generic/portal/PortalImpl.java +++ b/src/main/java/org/olat/core/gui/control/generic/portal/PortalImpl.java @@ -221,7 +221,13 @@ public class PortalImpl extends DefaultController implements Portal, ControllerE Portlet portlet = portletsIter.next(); log.debug("initPortlets portletName=" + portlet.getName()); if (portlet.isEnabled()) { - PortletContainer pc = PortletFactory.getPortletContainerFor(portlet, getWindowControl(), ureq); + PortletContainer pc = null; + //fxdiff make the system tolerant to portlet errors + try { + pc = PortletFactory.getPortletContainerFor(portlet, getWindowControl(), ureq); + } catch (Exception e) { + log.error("Cannot open a portlet: " + portlet, e); + } pc.addControllerListener(this); // remember this portlet container this.portletContainers.put(portlet.getName(), pc); diff --git a/src/main/java/org/olat/core/gui/control/generic/textmarker/TextMarkerJsGenerator.java b/src/main/java/org/olat/core/gui/control/generic/textmarker/TextMarkerJsGenerator.java index fc4d333eb924eba449477ac1eab0e163834fc259..b22a2eaa5ed07cc1039836b6a3cf41f4d4ba9d74 100644 --- a/src/main/java/org/olat/core/gui/control/generic/textmarker/TextMarkerJsGenerator.java +++ b/src/main/java/org/olat/core/gui/control/generic/textmarker/TextMarkerJsGenerator.java @@ -24,6 +24,7 @@ package org.olat.core.gui.control.generic.textmarker; import java.util.ArrayList; import java.util.Iterator; +import org.apache.commons.lang.StringEscapeUtils; import org.olat.core.commons.modules.glossary.GlossaryItem; import org.olat.core.commons.modules.glossary.GlossaryItemManager; import org.olat.core.util.Encoder; @@ -77,6 +78,9 @@ public class TextMarkerJsGenerator { sb.append("new Array(\""); for (Iterator iterator2 = allHighlightStrings.iterator(); iterator2.hasNext();) { String termFlexionSynonym = (String) iterator2.next(); + //fxdiff: FXOLAT-235 fix quotationsmarks that break the js code + termFlexionSynonym = StringEscapeUtils.escapeJava(termFlexionSynonym); + sb.append(termFlexionSynonym); sb.append("\""); if (iterator2.hasNext()) sb.append(",\""); diff --git a/src/main/java/org/olat/core/gui/control/winmgr/WindowManagerImpl.java b/src/main/java/org/olat/core/gui/control/winmgr/WindowManagerImpl.java index 9326acd8a012056c8df573130f7511d0bfbf4487..9b157daab58391acd81f575c236db44260cfc37d 100644 --- a/src/main/java/org/olat/core/gui/control/winmgr/WindowManagerImpl.java +++ b/src/main/java/org/olat/core/gui/control/winmgr/WindowManagerImpl.java @@ -286,6 +286,7 @@ public class WindowManagerImpl extends BasicManager implements WindowManager { */ public PopupBrowserWindow createNewPopupBrowserWindowFor(UserRequest ureq, ControllerCreator contentControllerCreator) { BaseChiefController cc = new BaseChiefController(ureq); + cc.addBodyCssClass("b_body_popup"); //supports the open(ureq) method PopupBrowserWindowController sbasec = pbwcc.createNewPopupBrowserController(ureq, cc.getWindowControl(), contentControllerCreator); //the content controller for the popupwindow is generated and set @@ -293,6 +294,20 @@ public class WindowManagerImpl extends BasicManager implements WindowManager { cc.setContentController(true, sbasec); return sbasec; } + + //fxdiff + public PopupBrowserWindow createNewUnauthenticatedPopupWindowFor(UserRequest ureq, ControllerCreator contentControllerCreator) { + BaseChiefController cc = new BaseChiefController(ureq); + cc.addBodyCssClass("b_body_popup"); + //supports the open(ureq) method + PopupBrowserWindowController sbasec = pbwcc.createNewUnauthenticatedPopupWindowController(ureq, cc.getWindowControl(), contentControllerCreator); + //the content controller for the popupwindow is generated and set + //at the moment the open method is called!! + cc.setContentController(true, sbasec); + return sbasec; + } + + /** * needed only by guidebugdispatchercontroller for the gui debug mode! * @param idDivsForced diff --git a/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html b/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html index dd2ecb30aa7e2d6b2dd1c71d8734fcd4a991633e..665eea1a82bef0c17202612adbe4fd6f83840b55 100644 --- a/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html +++ b/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html @@ -155,6 +155,7 @@ JSON <iframe src="javascript:false;document.write('');" style="height:300px; wid </div> #end ## below we need to provide an action attribute to make it html 4.01 transitional -## not needed now, later for dragging, see panel.java <form id="o_oaap" method="post" action="#" onsubmit="return false" target="oaa"><input type="hidden" name="v"></form> +##fxdiff + <form id="o_oaap" method="post" action="#" onsubmit="return false" target="oaa0"><input type="hidden" name="v"></form> </div> diff --git a/src/main/java/org/olat/core/gui/exception/ExceptionWindowController.java b/src/main/java/org/olat/core/gui/exception/ExceptionWindowController.java index 4f0593af8e4fbeb37a5b1a4fffc0d6b5fdfba705..e1cc58a1fcc6cb81eb0a809c47c52f9cf4c9036b 100644 --- a/src/main/java/org/olat/core/gui/exception/ExceptionWindowController.java +++ b/src/main/java/org/olat/core/gui/exception/ExceptionWindowController.java @@ -23,7 +23,9 @@ package org.olat.core.gui.exception; import java.util.Date; +import java.util.List; +import org.apache.velocity.context.Context; import org.olat.core.gui.UserRequest; import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; @@ -38,13 +40,19 @@ import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.HistoryPoint; import org.olat.core.logging.KnownIssueException; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.Tracing; 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; import org.olat.core.util.i18n.I18nManager; +import org.olat.core.util.mail.manager.MailManager; /** * Description: <br> @@ -166,6 +174,7 @@ public class ExceptionWindowController extends DefaultChiefController { } else { msg.contextPut("debug", Boolean.FALSE); } + msg.contextPut("listenerInfoRaw", componentListenerInfo); msg.contextPut("listenerInfo", Formatter.escWithBR(componentListenerInfo).toString()); msg.contextPut("stacktrace", OLATRuntimeException.throwableToHtml(th)); @@ -187,8 +196,30 @@ public class ExceptionWindowController extends DefaultChiefController { // out the correct value msg.contextPut("theme", w.getGuiTheme()); msg.contextPut("globalSettings", ws.getWindowManager().getGlobalSettings()); - + UserSession session = ureq.getUserSession(); + if(session != null && session.getLastHistoryPoint() != null) { + HistoryPoint point = session.getLastHistoryPoint(); + String businessPath = point.getBusinessPath(); + if(StringHelper.containsNonWhitespace(businessPath)) { + List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromString(businessPath); + String url = BusinessControlFactory.getInstance().getAsURIString(entries, true); + msg.contextPut("lastbusinesspath", url); + } + + List<HistoryPoint> stack = session.getHistoryStack(); + if(stack != null && stack.size() > 1) { + HistoryPoint prevPoint = stack.get(stack.size() - 2); + String prevBusinessPath = prevPoint.getBusinessPath(); + if(StringHelper.containsNonWhitespace(prevBusinessPath)) { + List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromString(prevBusinessPath); + String url = BusinessControlFactory.getInstance().getAsURIString(entries, true); + msg.contextPut("prevbusinesspath", url); + } + } + + } + w.setContentPane(msg); setWindow(w); } diff --git a/src/main/java/org/olat/core/gui/exception/_content/exception_page.html b/src/main/java/org/olat/core/gui/exception/_content/exception_page.html index 609292740b5897dc4940abbcc5633fe05691cca7..d1d3e77a1ae627f85510375349a4af0675c2d383 100644 --- a/src/main/java/org/olat/core/gui/exception/_content/exception_page.html +++ b/src/main/java/org/olat/core/gui/exception/_content/exception_page.html @@ -104,6 +104,11 @@ $r.renderHeaderIncludes() $r.translate('error.time') <strong>$time</strong> <br /> $r.translate('error.addinfo') <strong>$detailedmessage</strong> + <br /> + $r.translate('error.businesspath.previous') <strong>#if($!prevbusinesspath) $prevbusinesspath #else $r.translate('error.businesspath.unkown') #end </strong> + <br /> + $r.translate('error.businesspath') <strong>#if($!lastbusinesspath) $lastbusinesspath #else $r.translate('error.businesspath.unkown') #end </strong> + #if($!knownissuelink) <br /> Known Issue: <strong>$knownissuelink</strong> @@ -127,9 +132,8 @@ $r.renderHeaderIncludes() <div class="b_with_small_icon_left"> <form method="get" name="reportform" action="$r.relLink('')error/" id="ofo_366191"> <input type="hidden" name="username" value="$username" /> - <textarea cols="70" rows="15" name="textarea" >$r.translate('error.errnum') $errnum - $r.translate('error.time') $time - --------------------------------------------- + <input type="hidden" name="fx_errnum" value="${errnum}" /> + <textarea cols="70" rows="15" name="textarea" >$r.translate('error.errnum') ${errnum} $r.translate('error.businesspath.previous') #if($!prevbusinesspath) $prevbusinesspath #else $r.translate('error.businesspath.unkown') #{end} $r.translate('error.businesspath') #if($!lastbusinesspath) $lastbusinesspath #else $r.translate('error.businesspath.unkown') #{end} $r.translate('error.time') ${time} --------------------------------------------- </textarea> <div class="b_button_group"> <input type="submit" name="olat_fosm" value="$r.translateInAttribute('send.report')" class="b_button" /> diff --git a/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_de.properties index 151a1aa718c9f5ead604ea701dc864b2426b3c9e..509ee5c810468fbcaf4f5587377fdf842d31cbf1 100644 --- a/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_de.properties @@ -2,6 +2,9 @@ cancel=L\u00F6schen error.addinfo=Zus\u00E4tzliche Informationen\: error.back=Zur\u00FCck +error.businesspath=Business Pfad\: +error.businesspath.unkown=Unbekannt +error.businesspath.previous=Vorherige Business Pfad\: error.databaseexception=Unerwarteter Datenbankfehler. error.errnum=Fehlernummer\: error.header=Fehler diff --git a/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_en.properties index ba89dc5db42eb38dcb8d57d4c37f30e10de2bd37..7f4ddf24a3accb79faa2efc172d2361a6aed231c 100644 --- a/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/gui/exception/_i18n/LocalStrings_en.properties @@ -1,7 +1,10 @@ -#Thu Jan 20 20:10:42 CET 2011 +#Thu Jun 30 11:00:18 CEST 2011 cancel=Delete error.addinfo=Additional information\: error.back=Back +error.businesspath=Business path\: +error.businesspath.previous=Last business path\: +error.businesspath.unkown=Unknown error.databaseexception=Unexpected database error. error.errnum=Error code\: error.header=Error diff --git a/src/main/java/org/olat/core/gui/media/ClasspathMediaResource.java b/src/main/java/org/olat/core/gui/media/ClasspathMediaResource.java index 380341788ca70ea183232f7e92b9e45ea50fd0ee..f9abf7a125353071382b734cf260487e8c4be6a8 100644 --- a/src/main/java/org/olat/core/gui/media/ClasspathMediaResource.java +++ b/src/main/java/org/olat/core/gui/media/ClasspathMediaResource.java @@ -63,8 +63,13 @@ public class ClasspathMediaResource extends LogDelegator implements MediaResourc * @param location the relative file path (e.g. _static/my/file.css) */ public ClasspathMediaResource(Package pakkage, String location) { - this.location = location; - this.url = getClass().getResource("/" + pakkage.getName().replace(".", "/") + "/" + location); + this.location = location; + //fxdiff FXOLAT-185:fix loading of files in jar + StringBuilder sb = new StringBuilder(); + sb.append('/').append(pakkage.getName().replace(".", "/")); + if(!location.startsWith("/")) sb.append('/'); + sb.append(location); + this.url = getClass().getResource(sb.toString()); init(pakkage.getName()); } diff --git a/src/main/java/org/olat/core/gui/media/JSONMediaResource.java b/src/main/java/org/olat/core/gui/media/JSONMediaResource.java new file mode 100644 index 0000000000000000000000000000000000000000..1ecca167b86b51a10233ab98945edccf5d0db86b --- /dev/null +++ b/src/main/java/org/olat/core/gui/media/JSONMediaResource.java @@ -0,0 +1,73 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.gui.media; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +import org.json.JSONArray; +import org.olat.core.logging.AssertException; + +/** + * Description:<br> + * A JSON MediaResource. Represents a String holding JSON-data + * + * <P> + * Initial Date: 31.08.2011 <br> + * + * @author mkuendig + */ +public class JSONMediaResource extends DefaultMediaResource { + private static final String ENCODING_DEFAULT = "iso-8859-1"; + + private String encoding = ""; + private JSONArray json; + + public JSONMediaResource(JSONArray json, String encoding) { + this.json = json; + this.encoding = encoding; + this.setContentType("application/json; charset=" + encoding); + } + + /** + * @see org.olat.core.gui.media.MediaResource#getInputStream() + */ + public InputStream getInputStream() { + ByteArrayInputStream bis = null; + try { + bis = new ByteArrayInputStream(json.toString().getBytes(encoding)); + } catch (UnsupportedEncodingException e) { + try { + bis = new ByteArrayInputStream(json.toString().getBytes(ENCODING_DEFAULT)); + } catch (UnsupportedEncodingException ec) { + throw new AssertException(encoding + " encoding not supported??"); + // iso-8859-1 must be supported on the platform + } + } + return new BufferedInputStream(bis); + // nputStream sis = new + // ByteArrayInputStream(json.toString().getBytes()); + // return sis; + } + +} diff --git a/src/main/java/org/olat/core/gui/media/ServletUtil.java b/src/main/java/org/olat/core/gui/media/ServletUtil.java index 455fe3ca1fe98e5753679e88d8ff338ad169d453..59b50c5bf493e72568c71ef54e938953f0125684 100644 --- a/src/main/java/org/olat/core/gui/media/ServletUtil.java +++ b/src/main/java/org/olat/core/gui/media/ServletUtil.java @@ -27,6 +27,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -115,9 +119,12 @@ public class ServletUtil { try { Long size = mr.getSize(); - // if the size is known, set it to make browser's life easier - if (size != null) { - httpResp.setContentLength(size.intValue()); + Long lastModified = mr.getLastModified(); + + //fxdiff FXOLAT-118: accept range to deliver videos for iPad (implementation based on Tomcat) + List<Range> ranges = parseRange(httpReq, httpResp, (lastModified == null ? -1 : lastModified.longValue()), (size == null ? 0 : size.longValue())); + if(ranges != null) { + httpResp.setHeader("Accept-Ranges", "bytes"); } // maybe some more preparations mr.prepare(httpResp); @@ -137,10 +144,33 @@ public class ServletUtil { } else { out = httpResp.getOutputStream(); } + + if (ranges != null && ranges.size() == 1) { + Range range = ranges.get(0); + httpResp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length); + long length = range.end - range.start + 1; + if (length < Integer.MAX_VALUE) { + httpResp.setContentLength((int) length); + } else { + // Set the content-length as String to be able to use a long + httpResp.setHeader("content-length", "" + length); + } + + try { + httpResp.setBufferSize(2048); + } catch (IllegalStateException e) { + // Silent catch + } + copy(out, in, range); + } else { + if (size != null) { + httpResp.setContentLength(size.intValue()); + } + // buffer input stream + bis = new BufferedInputStream(in); + FileUtils.copy(bis, out); + } - // buffer input stream - bis = new BufferedInputStream(in); - FileUtils.copy(bis, out); if (debug) { long rstop = System.currentTimeMillis(); log.debug("time to serve (mr="+mr.getClass().getName()+") "+ (size == null ? "n/a" : "" + size) + " bytes: " + (rstop - rstart)); @@ -154,7 +184,151 @@ public class ServletUtil { } } + //fxdiff FXOLAT-118: accept range to deliver videos for iPad + protected static void copy(OutputStream ostream, InputStream resourceInputStream, Range range) throws IOException { + IOException exception = null; + + InputStream istream = new BufferedInputStream(resourceInputStream, 2048); + exception = copyRange(istream, ostream, range.start, range.end); + + // Clean up the input stream + istream.close(); + + // Rethrow any exception that has occurred + if (exception != null) throw exception; + } + + //fxdiff FXOLAT-118: accept range to deliver videos for iPad + protected static IOException copyRange(InputStream istream, OutputStream ostream, long start, long end) { + try { + istream.skip(start); + } catch (IOException e) { + return e; + } + + IOException exception = null; + long bytesToRead = end - start + 1; + + byte buffer[] = new byte[2048]; + int len = buffer.length; + while ((bytesToRead > 0) && (len >= buffer.length)) { + try { + len = istream.read(buffer); + if (bytesToRead >= len) { + ostream.write(buffer, 0, len); + bytesToRead -= len; + } else { + ostream.write(buffer, 0, (int) bytesToRead); + bytesToRead = 0; + } + } catch (IOException e) { + exception = e; + len = -1; + } + if (len < buffer.length) break; + } + + return exception; + } + + //fxdiff FXOLAT-118: accept range to deliver videos for iPad + protected static List<Range> parseRange(HttpServletRequest request, HttpServletResponse response, long lastModified, long fileLength) + throws IOException { + + String headerValue = request.getHeader("If-Range"); + + if (headerValue != null) { + long headerValueTime = (-1L); + try { + headerValueTime = request.getDateHeader("If-Range"); + } catch (IllegalArgumentException e) { + // + } + + if (headerValueTime != (-1L)) { + // If the timestamp of the entity the client got is older than + // the last modification date of the entity, the entire entity + // is returned. + if (lastModified > (headerValueTime + 1000)) + return Collections.emptyList(); + } + } + + if (fileLength == 0) return null; + // Retrieving the range header (if any is specified + String rangeHeader = request.getHeader("Range"); + + if (rangeHeader == null) return null; + // bytes is the only range unit supported (and I don't see the point + // of adding new ones). + if (!rangeHeader.startsWith("bytes")) { + response.addHeader("Content-Range", "bytes */" + fileLength); + response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + return null; + } + + rangeHeader = rangeHeader.substring(6); + + // Vector which will contain all the ranges which are successfully + // parsed. + List<Range> result = new ArrayList<Range>(); + StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ","); + + // Parsing the range list + while (commaTokenizer.hasMoreTokens()) { + String rangeDefinition = commaTokenizer.nextToken().trim(); + + Range currentRange = new Range(); + currentRange.length = fileLength; + + int dashPos = rangeDefinition.indexOf('-'); + + if (dashPos == -1) { + response.addHeader("Content-Range", "bytes */" + fileLength); + response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + return null; + } + + if (dashPos == 0) { + + try { + long offset = Long.parseLong(rangeDefinition); + currentRange.start = fileLength + offset; + currentRange.end = fileLength - 1; + } catch (NumberFormatException e) { + response.addHeader("Content-Range", "bytes */" + fileLength); + response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + return null; + } + + } else { + + try { + currentRange.start = Long.parseLong(rangeDefinition.substring(0, dashPos)); + if (dashPos < rangeDefinition.length() - 1) currentRange.end = Long.parseLong(rangeDefinition.substring(dashPos + 1, + rangeDefinition.length())); + else currentRange.end = fileLength - 1; + } catch (NumberFormatException e) { + response.addHeader("Content-Range", "bytes */" + fileLength); + response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + return null; + } + + } + + if (!currentRange.validate()) { + response.addHeader("Content-Range", "bytes */" + fileLength); + response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); + return null; + } + + result.add(currentRange); + } + + return result; + } + private static void pseudoStreamFlashResource(HttpServletRequest httpReq, HttpServletResponse httpResp, MediaResource mr) { Long range = getRange(httpReq); long seekPos = range == null ? 0l : range.longValue(); @@ -290,4 +464,32 @@ public class ServletUtil { } } } + + //fxdiff FXOLAT-118: accept range to deliver videos for iPad + protected static class Range { + public long start; + public long end; + public long length; + + /** + * Validate range. + */ + public boolean validate() { + if (end >= length) + end = length - 1; + return ( (start >= 0) && (end >= 0) && (start <= end) + && (length > 0) ); + } + + public void recycle() { + start = 0; + end = 0; + length = 0; + } + + @Override + public String toString() { + return start + "-" + end + "/" + length; + } + } } \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/themes/Theme.java b/src/main/java/org/olat/core/gui/themes/Theme.java index aca18f3524db46c65777eb2d0ac4c2607a99b52a..ccb251d9dd423e0cb0ec87d5ee8e06c95fa84ec7 100644 --- a/src/main/java/org/olat/core/gui/themes/Theme.java +++ b/src/main/java/org/olat/core/gui/themes/Theme.java @@ -20,12 +20,15 @@ */ package org.olat.core.gui.themes; +import java.io.File; + import org.olat.core.defaults.dispatcher.StaticMediaDispatcher; import org.olat.core.gui.render.StringOutput; +import org.olat.core.helpers.Settings; +import org.olat.core.util.WebappHelper; /** - * <h3>Description:</h3> - * A class that represents a GUI theme + * <h3>Description:</h3> A class that represents a GUI theme * <p> * Initial Date: 31.03.2008 <br> * @@ -35,8 +38,11 @@ public class Theme { private String identifyer; private String baseURI; + private static String CUSTOMFILENAME = "theme.js"; + /** - * @param name The unique theme identifyer + * @param name + * The unique theme identifyer */ public Theme(String themeIdentifyer) { init(themeIdentifyer); @@ -50,12 +56,53 @@ public class Theme { } /** - * @return The base URI for this theme, e.g. 'http://www.myserver.com/olat/raw/61x/themes/default/' + * checks whether the OLAT-Theme-Folder of this theme contains a file + * "theme.js" + * + * @return returns if the OLAT-Theme-Folder contains a file "theme.js" + */ + public boolean hasCustomJS() { + return (getCustomJSFile().exists()); + } + + /** + * returns a new File-instance that points to the "theme.js" file in the + * current OLAT-Theme folder + * + * @return + */ + private File getCustomJSFile() { + String staticThemesPath = WebappHelper.getContextRoot() + "/static/themes/"; + File themeFolder = new File(staticThemesPath, Settings.getGuiThemeIdentifyer()); + return new File(themeFolder, CUSTOMFILENAME); + } + + /** + * @return The base URI for this theme, e.g. + * 'http://www.myserver.com/olat/raw/61x/themes/default/' */ public String getBaseURI() { return baseURI; } + /** + * returns the path to the custom js <br /> + * ( check first with <code>hasCustomJS()</code> )<br /> + * <p> + * Example usage:<br /> + * <br /> + * + * <code>if (currTheme.hasCustomJS()) <br /> + * CustomJSComponent customJS = new CustomJSComponent("customThemejs", new String[] { currTheme.getFullPathToCustomJS() });</code> + * </p> + * + * @return the path to the custom layout js :: + * /olat/raw/fx-olat7/themes/frentix/theme.js + */ + public String getFullPathToCustomJS() { + return baseURI + CUSTOMFILENAME; + } + /** * Update values in this theme with the values from the given identifyer. * diff --git a/src/main/java/org/olat/core/gui/translator/PackageTranslator.java b/src/main/java/org/olat/core/gui/translator/PackageTranslator.java index ad9730361f9926d0682a096e4227130d3e38318e..44054a55147c3987b18f99bc6116ba020fcf390c 100644 --- a/src/main/java/org/olat/core/gui/translator/PackageTranslator.java +++ b/src/main/java/org/olat/core/gui/translator/PackageTranslator.java @@ -49,6 +49,10 @@ public class PackageTranslator extends LogDelegator implements Translator { this.fallBackTranslator = fallBackTranslator; this.fallBack = fallBack; } + + public void setFallBack(PackageTranslator fallback){ + this.fallBackTranslator = fallback; + } /** * @param packageName @@ -146,6 +150,9 @@ public class PackageTranslator extends LogDelegator implements Translator { // try with fallBackToDefaultLocale val = translate(key, args, true ); } + } + if (val != null){ + fallBackLevel = 0; } // else value got translated or there is at least an error message telling // which key was not found. diff --git a/src/main/java/org/olat/core/helpers/Settings.java b/src/main/java/org/olat/core/helpers/Settings.java index bcf8341747e4b7392fcf1ea7e1cc7a6e54fcb599..a99c593dd0060c2ede0a174202dc4f478dabc6bb 100644 --- a/src/main/java/org/olat/core/helpers/Settings.java +++ b/src/main/java/org/olat/core/helpers/Settings.java @@ -77,19 +77,30 @@ public class Settings implements Initializable, Destroyable, GenericEventListene private static int nodeId; private static String clusterMode; private static Date buildDate; + private static String repoRevision; + private static String patchRepoRevision; /** * [used by spring] */ Settings() { + // + } + + // fxdiff: only set build id from build date if none is provided in olat.local.properties! + private static void setBuildIdFromBuildDate() { + SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); + buildIdentifier = formatter.format(buildDate); + } + + // fxdiff: only set build date + private static void setBuildDate(){ //extract the latest build number as date where this class was compiled Resource res = new ClassPathResource("org/olat/core/helpers/Settings.class"); try { - SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); buildDate = new Date(res.lastModified()); - buildIdentifier = formatter.format(buildDate); } catch (IOException e) { - buildIdentifier = "00000000"; + buildDate = new Date(); } } @@ -98,14 +109,37 @@ public class Settings implements Initializable, Destroyable, GenericEventListene * @return a identifier for this build e.g. the build date of a class like 20100329 */ public static String getBuildIdentifier() { + if (buildIdentifier == null) { + setBuildIdFromBuildDate(); + } return buildIdentifier; } + /** + * [spring] + * @param buildId + */ + public void setBuildIdentifier(String buildId){ + buildIdentifier = buildId; + } + + public void setRepoRevision(String repoRev){ + repoRevision = repoRev; + } + + //fxdiff: get the mercurial changeset Information from the time this release had been built + public static String getRepoRevision(){ + return repoRevision; + } + /** * * @return the exacte date and time this class was comiled */ public static Date getBuildDate() { + if (buildDate == null){ + setBuildDate(); + } return buildDate; } @@ -120,7 +154,6 @@ public class Settings implements Initializable, Destroyable, GenericEventListene return debug; } - /** * @return if ajax mode is system-wide enabled or not */ @@ -234,7 +267,7 @@ public class Settings implements Initializable, Destroyable, GenericEventListene public static String getFullVersionInfo() { StringBuilder sb = new StringBuilder(); - sb.append(applicationName).append(" ").append(version).append(" (Build ").append(buildIdentifier).append(")"); + sb.append(applicationName).append(" ").append(getVersion()).append(" (Build ").append(getBuildIdentifier()).append(")"); return sb.toString(); } @@ -283,7 +316,7 @@ public class Settings implements Initializable, Destroyable, GenericEventListene public void setDebug(boolean debug) { Settings.debug = debug; } - + /** * @see org.olat.core.configuration.ServiceLifeCycle#init() */ @@ -456,6 +489,8 @@ public class Settings implements Initializable, Destroyable, GenericEventListene * IMPORTANT: as long as this is used to track errors also, the format must be the same as in error-logs! * therefore also return something in single-vm-mode! */ + // as long as this is used to track errors also, the format must be the same as in error-logs! + // therefore also return something in single-vm-mode! public static String getNodeInfo() { return "N"+nodeId; // if (clusterMode.equalsIgnoreCase("Cluster")) { diff --git a/src/main/java/org/olat/core/logging/LogRealTimeViewerController.java b/src/main/java/org/olat/core/logging/LogRealTimeViewerController.java index aa5d965cbd4e29a8c1cead71362f6a8c7d292de5..a631685f8b93879c54b0719057daacc555e52f1c 100644 --- a/src/main/java/org/olat/core/logging/LogRealTimeViewerController.java +++ b/src/main/java/org/olat/core/logging/LogRealTimeViewerController.java @@ -166,6 +166,8 @@ public class LogRealTimeViewerController extends BasicController implements JobL } private void updateLogViewFromWriter() { + if(logViewerVC == null) return; + StringBuffer sb = writer.getBuffer(); String log = sb.toString(); if (removeLogNoise) { diff --git a/src/main/java/org/olat/core/logging/activity/CourseLoggingAction.java b/src/main/java/org/olat/core/logging/activity/CourseLoggingAction.java index a9a36bbfb642db596da5deac8b5ce59008d4d3db..372641e7d67c30b3d0da0ebbfd15beb5c69332f2 100644 --- a/src/main/java/org/olat/core/logging/activity/CourseLoggingAction.java +++ b/src/main/java/org/olat/core/logging/activity/CourseLoggingAction.java @@ -83,12 +83,14 @@ public class CourseLoggingAction extends BaseLoggingAction { public static final ILoggingAction COURSE_ENTERING = new CourseLoggingAction(ActionType.statistic, CrudAction.retrieve, ActionVerb.launch, ActionObject.course).setTypeList( - new ResourceableTypeList().addMandatory(OlatResourceableType.course)); + new ResourceableTypeList().addMandatory(OlatResourceableType.course).addOptional(StringResourceableType.targetIdentity)); public static final ILoggingAction COURSE_LEAVING = new CourseLoggingAction(ActionType.statistic, CrudAction.exit, ActionVerb.exit, ActionObject.course).setTypeList( new ResourceableTypeList().addMandatory(OlatResourceableType.course). - or().addMandatory(OlatResourceableType.course, OlatResourceableType.genRepoEntry).addOptional(OlatResourceableType.businessGroup)); + or().addMandatory(OlatResourceableType.course, OlatResourceableType.genRepoEntry).addOptional(OlatResourceableType.businessGroup). + or().addMandatory(OlatResourceableType.genRepoEntry, StringResourceableType.targetIdentity).addOptional(OlatResourceableType.businessGroup).addOptional(OlatResourceableType.sharedFolder).addOptional(OlatResourceableType.course). + or().addMandatory(OlatResourceableType.course, StringResourceableType.targetIdentity)); public static final ILoggingAction COURSE_NAVIGATION_NODE_ACCESS = new CourseLoggingAction(ActionType.statistic, CrudAction.retrieve, ActionVerb.launch, ActionObject.node).setTypeList( diff --git a/src/main/java/org/olat/core/logging/activity/UserActivityLoggerImpl.java b/src/main/java/org/olat/core/logging/activity/UserActivityLoggerImpl.java index 8262943a893dd4074e25bbba636eedfe2da9face..0cb9090b49f926a1f0c78e8851ad2acbf763642a 100644 --- a/src/main/java/org/olat/core/logging/activity/UserActivityLoggerImpl.java +++ b/src/main/java/org/olat/core/logging/activity/UserActivityLoggerImpl.java @@ -527,7 +527,7 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { ILoggingResourceable resourceInfo = it2.next(); log_.info("id: "+resourceInfo.getId()+", name="+resourceInfo.getName()+", type="+resourceInfo.getType()+", toString: "+resourceInfo.toString()); } - log_.error("Could not find any LoggingResourceable corresponding to this ContextEntry: "+ce.toString(), + log_.warn("Could not find any LoggingResourceable corresponding to this ContextEntry: "+ce.toString(), new Exception("UserActivityLoggerImpl.getCombinedOrderedLoggingResourceables()")); } } @@ -555,6 +555,10 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { } public void log(ILoggingAction loggingAction, Class<?> callingClass, ILoggingResourceable... lriOrNull) { + Long logStart = null; + if (log_.isDebug()) { + logStart = System.currentTimeMillis(); + } final ActionType actionType = stickyActionType_!=null ? stickyActionType_ : loggingAction.getResourceActionType(); // don't log entries with loggingAction type 'tracking' @@ -571,20 +575,8 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { // to the database below right away List<ILoggingResourceable> resourceInfos = getCombinedOrderedLoggingResourceables(lriOrNull); - if (loggingAction.getTypeListDefinition()==null) { - // this is a foul! - log_.warn("LoggingAction has no ResourceableTypeList defined: action="+loggingAction+", fieldId="+loggingAction.getJavaFieldIdForDebug()); - } else { - // good boy - String errorMsg = loggingAction.getTypeListDefinition().executeCheckAndGetErrorMessage(resourceInfos); - if (errorMsg!=null) { - // we found an inconsistency - // lets make this a warn - log_.warn("LoggingAction reported an inconsistency: "+loggingAction.getActionVerb()+" "+loggingAction.getActionObject()+", action="+loggingAction+", fieldId="+loggingAction.getJavaFieldIdForDebug()+ - ", expected: "+loggingAction.getTypeListDefinition().toString()+ - ", actual: "+convertLoggingResourceableListToString(resourceInfos), new Exception("OLAT-4653")); - } - } +//fxdiff: see FXOLAT-104, move up here to remove targetIdentity before checking the LoggingResourcables, because of often obsolete delivery of targetIdentity. +// TargetIdentity is often missing in XYLoggingAction. if (session_==null) { // then I can't log - log information without session/user information isn't of much use @@ -614,27 +606,44 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { crudAction.name()+":"+actionVerb.name()+", "+actionObject+", "+ convertLoggingResourceableListToString(resourceInfos), new Exception()); return; - } - - Long identityKey = identity.getKey(); + } + Long identityKey = identity.getKey(); if (actionType!=ActionType.admin) { final String identityKeyStr = String.valueOf(identityKey); - for (Iterator it = resourceInfos.iterator(); it.hasNext();) { - ILoggingResourceable lr = (ILoggingResourceable) it.next(); - if (lr.getResourceableType()==StringResourceableType.targetIdentity) { - if (log_.isDebug() && !lr.getId().equals(identityKeyStr)) { + for (Iterator<ILoggingResourceable> it = resourceInfos.iterator(); it.hasNext();) { + ILoggingResourceable lr = it.next(); + // fxdiff: we want this info as too much actionTypes are non-admin and log-entry will then be without value not containing targetIdent!, see FXOLAT-104 + if (lr.getResourceableType()==StringResourceableType.targetIdentity && lr.getId().equals(identityKeyStr)) { + if (log_.isDebug()) { // complain final Writer strackTraceAsStringWriter = new StringWriter(); final PrintWriter printWriter = new PrintWriter(strackTraceAsStringWriter); (new Exception("OLAT-4955 debug stacktrac")).printStackTrace(printWriter); log_.debug("OLAT-4955: Not storing targetIdentity for non-admin logging actions. A non-admin logging action wanted to store a user other than the one from the session: action="+loggingAction+", fieldId="+loggingAction.getJavaFieldIdForDebug(), strackTraceAsStringWriter.toString()); } - // OLAT-4955: remove targetIdentity + // OLAT-4955: remove targetIdentity (fxdiff: only if same as executing identity!) it.remove(); } } } +// fxdiff: end of moved code + + if (loggingAction.getTypeListDefinition()==null) { + // this is a foul! + log_.warn("LoggingAction has no ResourceableTypeList defined: action="+loggingAction+", fieldId="+loggingAction.getJavaFieldIdForDebug()); + } else { + // good boy + String errorMsg = loggingAction.getTypeListDefinition().executeCheckAndGetErrorMessage(resourceInfos); + if (errorMsg!=null) { + // we found an inconsistency + // lets make this a warn + log_.warn("LoggingAction reported an inconsistency: "+loggingAction.getActionVerb()+" "+loggingAction.getActionObject()+", action="+loggingAction+", fieldId="+loggingAction.getJavaFieldIdForDebug()+ + ", expected: "+loggingAction.getTypeListDefinition().toString()+ + ", actual: "+convertLoggingResourceableListToString(resourceInfos), new Exception("OLAT-4653")); + } + } + String identityName; if(isLogAnonymous_ && (actionType != ActionType.admin)) { @@ -647,8 +656,11 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { final LoggingObject logObj = new LoggingObject(sessionId, identityKey, identityName, crudAction.name().substring(0,1), actionVerb.name(), actionObject); // do simpleDuration calculation & storing - LoggingObject lastLogObj = (LoggingObject) session_.getEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG); - if (lastLogObj!=null) { +// fxdiff: FXOLAT-94 don't do duration calculation, as its quite senseless (duration = timestamp of click 2 - click 1) +// if still needed once, dont update the lastLogObj, but save duration with current one, 50x faster! + +// LoggingObject lastLogObj = (LoggingObject) session_.getEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG); +// if (lastLogObj!=null) { //lastLogObj = (LoggingObject) DBFactory.getInstance().loadObject(lastLogObj); // DBFactory.getInstance().updateObject(lastLogObj); // Implementation Note: @@ -675,35 +687,35 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { // if that would be called it would simply fail in the BLACKHOLE@UZH setup // calculate the duration - take the simple diff of the two creationDate fields - Date currentTime = logObj.getCreationDate(); - Date lastTime = lastLogObj.getCreationDate(); - long duration; - if (lastTime==null) { - duration = -1; - } else if (currentTime==null) { - duration = System.currentTimeMillis() - lastTime.getTime(); - } else { - duration = currentTime.getTime() - lastTime.getTime(); - } - - DB db = DBFactory.getInstanceForClosing(); - if (db!=null && db.isError()) { - // then we would run into an ERROR when we'd do more with this DB - // hence we just issue a log.info here with the details - //@TODO: lower to log_.info once we checked that it doesn't occur very often (best for 6.4) - log_.warn("log: DB is in Error state therefore the UserActivityLoggerImpl cannot update the simpleDuration of log_id "+lastLogObj.getKey()+" with value "+duration+", loggingObject: "+lastLogObj); - } else { - DBQuery update = DBFactory.getInstance().createQuery( - "update org.olat.core.logging.activity.LoggingObject set simpleDuration = :duration where log_id = :logid"); - update.setLong("duration", duration); - update.setLong("logid", lastLogObj.getKey()); - // we have to do FlushMode.AUTO (which is the default anyway) - update.executeUpdate(FlushMode.AUTO); - } - } +// Date currentTime = logObj.getCreationDate(); +// Date lastTime = lastLogObj.getCreationDate(); +// long duration; +// if (lastTime==null) { +// duration = -1; +// } else if (currentTime==null) { +// duration = System.currentTimeMillis() - lastTime.getTime(); +// } else { +// duration = currentTime.getTime() - lastTime.getTime(); +// } +// +// DB db = DBFactory.getInstanceForClosing(); +// if (db!=null && db.isError()) { +// // then we would run into an ERROR when we'd do more with this DB +// // hence we just issue a log.info here with the details +// //@TODO: lower to log_.info once we checked that it doesn't occur very often (best for 6.4) +// log_.warn("log: DB is in Error state therefore the UserActivityLoggerImpl cannot update the simpleDuration of log_id "+lastLogObj.getKey()+" with value "+duration+", loggingObject: "+lastLogObj); +// } else { +// DBQuery update = DBFactory.getInstance().createQuery( +// "update org.olat.core.logging.activity.LoggingObject set simpleDuration = :duration where log_id = :logid"); +// update.setLong("duration", duration); +// update.setLong("logid", lastLogObj.getKey()); +// // we have to do FlushMode.AUTO (which is the default anyway) +// update.executeUpdate(FlushMode.AUTO); +// } +// } // store the current logging object in the session - for duration calculation at next log - session_.putEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG, logObj); +// session_.putEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG, logObj); if (resourceInfos!=null && resourceInfos.size()!=0) { // this should be the normal case - we do have LoggingResourceables which we can log @@ -744,11 +756,11 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { // fill the remaining fields logObj.setBusinessPath(businessPath_); logObj.setSourceClass(callingClass.getCanonicalName()); - logObj.setSimpleDuration(-1); +// logObj.setSimpleDuration(duration); logObj.setResourceAdminAction(actionType.equals(ActionType.admin)?true:false); Locale locale = I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()); - //prepate the user properties, set them at once + //prepare the user properties, set them at once List<String> tmpUserProperties = new ArrayList<String>(12); for(Iterator<String> iterator = userProperties_.iterator(); iterator.hasNext();) { String userPropString = identity.getUser().getPropertyOrIdentityEnvAttribute(iterator.next(), locale); @@ -779,6 +791,10 @@ public class UserActivityLoggerImpl implements IUserActivityLogger { } else { DBFactory.getInstance().saveObject(logObj); } + if (log_.isDebug()) { + Long logEnd = System.currentTimeMillis(); + log_.debug("log duration = " + (logEnd - logStart)); + } } /** toString for debug **/ diff --git a/src/main/java/org/olat/core/servlets/OLATServlet.java b/src/main/java/org/olat/core/servlets/OLATServlet.java index 2cbcbfa6a946bcf48fed002a24b86ffeaede4456..39da87fb5a3bdc805cea71ac16a7c6807c4a88d4 100644 --- a/src/main/java/org/olat/core/servlets/OLATServlet.java +++ b/src/main/java/org/olat/core/servlets/OLATServlet.java @@ -38,6 +38,7 @@ import org.olat.core.gui.GUIInterna; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.ThreadLocalUserActivityLoggerInstaller; +import org.olat.core.util.WorkThreadInformations; import org.olat.core.util.event.FrameworkStartupEventChannel; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.threadlog.RequestBasedLogLevelManager; @@ -123,6 +124,8 @@ public class OLATServlet extends HttpServlet { Tracing.setUreq(request); I18nManager.attachI18nInfoToThread(request); ThreadLocalUserActivityLoggerInstaller.initUserActivityLogger(request); + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.set("Serve request: " + request.getRequestURI()); try{ if (requestBasedLogLevelManager!=null) requestBasedLogLevelManager.activateRequestBasedLogLevel(request); @@ -130,6 +133,8 @@ public class OLATServlet extends HttpServlet { dispatcher.execute(request, response, null); } finally { if (requestBasedLogLevelManager!=null) requestBasedLogLevelManager.deactivateRequestBasedLogLevel(); + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.unset(); ThreadLocalUserActivityLoggerInstaller.resetUserActivityLogger(); I18nManager.remove18nInfoFromThread(); Tracing.setUreq(null); diff --git a/src/main/java/org/olat/core/util/ArrayHelper.java b/src/main/java/org/olat/core/util/ArrayHelper.java index 4e4755661f7fd19a18197a8901fd995e8ac5e474..55b2cd40d43c2707e972624fd3e0e3592ea2b708 100644 --- a/src/main/java/org/olat/core/util/ArrayHelper.java +++ b/src/main/java/org/olat/core/util/ArrayHelper.java @@ -122,7 +122,7 @@ public class ArrayHelper { String[] newArray = new String[oldArray.length + 1]; int targetPos = (addAtTheEnd ? 0 : 1); System.arraycopy(oldArray, 0, newArray, targetPos, oldArray.length); - int newValuePos = (addAtTheEnd ? newArray.length : 0); + int newValuePos = (addAtTheEnd ? newArray.length : 1); newArray[newValuePos - 1] = doBeAddedValue; return newArray; } diff --git a/src/main/java/org/olat/core/util/FileUtils.java b/src/main/java/org/olat/core/util/FileUtils.java index c4699cc9e5dca93d2c1b7f69277d3b487d1c790c..275cd63cb5c47201dacfb3f9d0adb8513dfac181 100644 --- a/src/main/java/org/olat/core/util/FileUtils.java +++ b/src/main/java/org/olat/core/util/FileUtils.java @@ -902,4 +902,34 @@ public class FileUtils { log.debug(String.format("cpio %,13d bytes %6.2f ms avg %6.1f Mbps %s%n", tot, dtim/1000/1000, bps/1024, wt)); return tot; } + + /** + * from a newer version of apache commons.io Determines whether the specified + * file is a Symbolic Link rather than an actual file. + * <p> + * Will not return true if there is a Symbolic Link anywhere in the path, only + * if the specific file is. + * + * @param file the file to check + * @return true if the file is a Symbolic Link + * @throws IOException if an IO error occurs while checking the file + * @since Commons IO 2.0 + */ + public static boolean isSymlink(File file) throws IOException { + if (file == null) { throw new NullPointerException("File must not be null"); } + if ("\\".equals(File.separatorChar) ) { return false; } // Windows doesn't know symlinks! + File fileInCanonicalDir = null; + if (file.getParent() == null) { + fileInCanonicalDir = file; + } else { + File canonicalDir = file.getParentFile().getCanonicalFile(); + fileInCanonicalDir = new File(canonicalDir, file.getName()); + } + + if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { + return false; + } else { + return true; + } + } } diff --git a/src/main/java/org/olat/core/util/IImageHelper.java b/src/main/java/org/olat/core/util/IImageHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..1c41ceeb9f07740b167cad7b457efe5b5f054563 --- /dev/null +++ b/src/main/java/org/olat/core/util/IImageHelper.java @@ -0,0 +1,43 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.util; + +import java.io.InputStream; + +import org.olat.core.util.ImageHelper.Size; +import org.olat.core.util.vfs.VFSLeaf; + +/** + * Description:<br> + * yet a dummy interface for ImageHelper to allow later replacement (i.e. + * ImageMagick) by config + * + * <P> + * Initial Date: 04.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public interface IImageHelper { + + Size scaleImage(InputStream image, VFSLeaf scaledImage, int maxWidth, + int maxHeight); + +} diff --git a/src/main/java/org/olat/core/util/ImageHelper.java b/src/main/java/org/olat/core/util/ImageHelper.java index 1dab5f47737590a1de58785aedac37e61056c313..6816e1847f1503c283b1563b7371439431160ead 100644 --- a/src/main/java/org/olat/core/util/ImageHelper.java +++ b/src/main/java/org/olat/core/util/ImageHelper.java @@ -27,6 +27,7 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.Transparency; +import java.awt.color.CMMException; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.File; @@ -54,7 +55,7 @@ import org.olat.core.util.vfs.VFSLeaf; * * @author Alexander Schneider, srosse */ -public class ImageHelper { +public class ImageHelper implements IImageHelper { private static final String OUTPUT_FORMAT = "jpeg"; @@ -108,7 +109,7 @@ public class ImageHelper { * @param maxSize the maximum size (height or width) of the new scaled image * @return */ - public static Size scaleImage(InputStream image, VFSLeaf scaledImage, int maxWidth, int maxHeight) { + public Size scaleImage(InputStream image, VFSLeaf scaledImage, int maxWidth, int maxHeight) { OutputStream bos = new BufferedOutputStream(scaledImage.getOutputStream(false)); try { BufferedImage imageSrc = ImageIO.read(image); @@ -158,6 +159,9 @@ public class ImageHelper { return null; } catch (IOException e) { return null; + //fxdiff FXOLAT-109: prevent red screen if the image has wrong EXIF data + } catch (CMMException e) { + return null; } finally { FileUtils.closeSafely(ins); FileUtils.closeSafely(bos); @@ -218,6 +222,9 @@ public class ImageHelper { return writeTo(scaleTo(imageSrc, scaledSize), scaledImage, scaledSize, getImageFormat(scaledImage)); } catch (IOException e) { return false; + //fxdiff FXOLAT-109: prevent red screen if the image has wrong EXIF data + } catch (CMMException e) { + return false; } } diff --git a/src/main/java/org/olat/core/util/UserSession.java b/src/main/java/org/olat/core/util/UserSession.java index 9327e36811a329a2757b254444c2d1d4e85dd842..36a8d24017bd5f641a8f4b7ad4d54c15c8438a72 100644 --- a/src/main/java/org/olat/core/util/UserSession.java +++ b/src/main/java/org/olat/core/util/UserSession.java @@ -82,7 +82,8 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList //clusterNOK cache ?? private static Set<UserSession> authUserSessions = new HashSet<UserSession>(101); private static Map<String, Identity> userNameToIdentity = new HashMap<String, Identity>(101); - private static int sessionTimeoutInSec = 1800; + private static int sessionTimeoutInSec = 300; + private static int sessionTimeoutAuthInSec = 7200; private static Set<String> authUsersNamesOtherNodes = new HashSet<String>(101); // things to put into that should not be clear when signing on (e.g. remember @@ -139,7 +140,11 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList } } //set a possible changed session timeout interval - session.setMaxInactiveInterval(UserSession.sessionTimeoutInSec); + if(us.isAuthenticated()) { + session.setMaxInactiveInterval(UserSession.sessionTimeoutAuthInSec); + } else { + session.setMaxInactiveInterval(UserSession.sessionTimeoutInSec); + } return us; } @@ -162,9 +167,15 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList if (session==null) { return null; } - session.setMaxInactiveInterval(UserSession.sessionTimeoutInSec); + synchronized (session) {//o_clusterOK by:se - return (UserSession) session.getAttribute(USERSESSIONKEY); + UserSession us = (UserSession) session.getAttribute(USERSESSIONKEY); + if(us != null && us.isAuthenticated()) { + session.setMaxInactiveInterval(UserSession.sessionTimeoutAuthInSec); + } else { + session.setMaxInactiveInterval(UserSession.sessionTimeoutInSec); + } + return us; } } @@ -254,6 +265,12 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList */ public void setIdentity(Identity identity) { identityEnvironment.setIdentity(identity); + //fxdiff FXOLAT-231: event on GUI Preferences extern changes + if(identity.getKey() != null) { + OLATResourceable ores = OresHelper.createOLATResourceableInstance(Preferences.class, identity.getKey()); + CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, ores); + CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, null, ores); + } } /** @@ -479,6 +496,9 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(new SignOnOffEvent(identity, false), ORES_USERSESSION); Tracing.logDebug("signOffAndClear() deregistering usersession from eventbus, id="+sessionInfo, getClass()); CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, ORES_USERSESSION); + //fxdiff FXOLAT-231: event on GUI Preferences extern changes + OLATResourceable ores = OresHelper.createOLATResourceableInstance(Preferences.class, identity.getKey()); + CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, ores); registeredWithBus = false; } } catch (Exception e) { @@ -671,6 +691,13 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList * @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event) */ public void event(Event event) { + //fxdiff FXOLAT-231: event on GUI Preferences extern changes + if("preferences.changed".equals(event.getCommand())) { + Identity identity = identityEnvironment.getIdentity(); + guiPreferences = PreferencesFactory.getInstance().getPreferencesFor(identity, identityEnvironment.getRoles().isGuestOnly()); + return; + } + Tracing.logDebug("event() START", getClass()); SignOnOffEvent se = (SignOnOffEvent) event; Tracing.logDebug("event() is SignOnOffEvent. isSignOn="+se.isSignOn(), getClass()); @@ -769,7 +796,7 @@ public class UserSession implements HttpSessionBindingListener, GenericEventList * @param sessionTimeoutInSec */ public static void setGlobalSessionTimeout(int sessionTimeoutInSec) { - UserSession.sessionTimeoutInSec = sessionTimeoutInSec; + UserSession.sessionTimeoutAuthInSec = sessionTimeoutInSec; Set<UserSession> sessionSnapShot = new HashSet<UserSession>(authUserSessions); for (UserSession session : sessionSnapShot) { try{ diff --git a/src/main/java/org/olat/core/util/WebappHelper.java b/src/main/java/org/olat/core/util/WebappHelper.java index bb873a68445526f228780df5ae295e71ea38a51d..f886ac44c862e2ecdab5f2ebd635550a16c4c8ba 100644 --- a/src/main/java/org/olat/core/util/WebappHelper.java +++ b/src/main/java/org/olat/core/util/WebappHelper.java @@ -78,8 +78,9 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA Resource res = new ClassPathResource(CoreSpringFactory.class.getCanonicalName().replaceAll("\\.", "\\/")+".class"); try { String fullPath = res.getURL().toString(); - if (fullPath.contains("/WEB-INF")) { - fullPath = fullPath.substring(fullPath.indexOf("file:")+5, fullPath.indexOf("/WEB-INF")); + + if (fullPath.contains(File.separator + "WEB-INF")) { + fullPath = fullPath.substring(fullPath.indexOf("file:")+5, fullPath.indexOf(File.separator + "WEB-INF")); } else { fullPath = servletContext.getRealPath("/"); } @@ -244,6 +245,9 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA if (!fUserData.exists()) { if (!fUserData.mkdirs()) throw new StartupException("Unable to create userdata dir '" + userDataRoot + "'. Please fix!"); } + //fxdiff: reset tmp-dir from within application to circumvent startup-params. + //do not write to system default (/var/tmp) as this leads to permission problems with multiple instances on one host! + System.setProperty("java.io.tmpdir", userDataRoot+"/tmp"); log.info("Setting userdata root to: "+userDataRoot); WebappHelper.userDataRoot = userDataRoot; } @@ -277,7 +281,8 @@ public class WebappHelper implements Initializable, Destroyable, ServletContextA * key="smtpUser" * key="smtpPwd" * key="mailSupport" - * key="mailFrom" + * key="mailReplyTo" - default from (reply-to) + * key="mailFrom" - real from address * @param string * @return */ diff --git a/src/main/java/org/olat/core/util/WorkThreadInformations.java b/src/main/java/org/olat/core/util/WorkThreadInformations.java new file mode 100644 index 0000000000000000000000000000000000000000..ddeaf925c5abfe1b55b2698935c53eca0049a200 --- /dev/null +++ b/src/main/java/org/olat/core/util/WorkThreadInformations.java @@ -0,0 +1,66 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ + +package org.olat.core.util; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * + * Description:<br> + * Only a map which logged the current work of threads + * + * <P> + * Initial Date: 6 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class WorkThreadInformations { + + private static final Map<String,String> works = new HashMap<String,String>(); + + public synchronized static void set(String message) { + String threadName = Thread.currentThread().getName(); + if(StringHelper.containsNonWhitespace(message)) { + works.put(threadName, message); + } else { + works.remove(threadName); + } + } + + public synchronized static void unset() { + String threadName = Thread.currentThread().getName(); + works.remove(threadName); + } + + public synchronized static String get(String threadName) { + return works.get(threadName); + } + + public synchronized static void currentThreadNames(List<String> threadNames) { + for(Iterator<String> threadNameIt=works.keySet().iterator(); threadNameIt.hasNext(); ) { + if(!threadNames.contains(threadNameIt.next())) { + threadNameIt.remove(); + } + } + } +} diff --git a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml index 1553ac139234e3ba1d1bc10b703d9caa5a84300e..04b85fcdf902dc4a47cda2c6d5ab10fede1d46d6 100644 --- a/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml +++ b/src/main/java/org/olat/core/util/_spring/utilCorecontext.xml @@ -7,8 +7,6 @@ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> - -<context:property-placeholder location="classpath:serviceconfig/olat.properties, classpath:olat.local.properties" /> <bean id="codeHelper" class="org.olat.core.util.CodeHelper" > <constructor-arg value="${node.id}" /> diff --git a/src/main/java/org/olat/core/util/i18n/ui/I18nConfigController.java b/src/main/java/org/olat/core/util/i18n/ui/I18nConfigController.java index 6a648a72c4dd0cd4865847e4821d304a1bee6751..68e365b8994cd88c342caa3a3f6d325c27839a3e 100644 --- a/src/main/java/org/olat/core/util/i18n/ui/I18nConfigController.java +++ b/src/main/java/org/olat/core/util/i18n/ui/I18nConfigController.java @@ -184,13 +184,16 @@ class I18nConfigController extends FormBasicController { enabledLangKeys.add(defaultLocale.toString()); showWarning("configuration.default.lang.must.be.enabed", defaultLocale.toString()); } - // Check if fallback language is still enabled - String fallbackLangKey = I18nModule.getFallbackLocale().toString(); - if (!enabledLangKeys.contains(fallbackLangKey)) { - enabledLangSelection.select(fallbackLangKey, true); - enabledLangKeys.add(fallbackLangKey); - showWarning("configuration.fallback.lang.must.be.enabed", fallbackLangKey); - } + +// fxdiff FXOLAT-40 don't force fallback language to be enabled in the GUI, +// conflict with languages with country/variant information +// // Check if fallback language is still enabled +// String fallbackLangKey = I18nModule.getFallbackLocale().toString(); +// if (!enabledLangKeys.contains(fallbackLangKey)) { +// enabledLangSelection.select(fallbackLangKey, true); +// enabledLangKeys.add(fallbackLangKey); +// showWarning("configuration.fallback.lang.must.be.enabed", fallbackLangKey); +// } I18nModule.setEnabledLanguageKeys(enabledLangKeys); diff --git a/src/main/java/org/olat/core/util/i18n/ui/TranslationToolStartCrumbController.java b/src/main/java/org/olat/core/util/i18n/ui/TranslationToolStartCrumbController.java index 27dbcc1d10af7ec809c689b3c487983038cb369c..5de1b3024484f6ce7fadf6f85515ac16bb17ade6 100644 --- a/src/main/java/org/olat/core/util/i18n/ui/TranslationToolStartCrumbController.java +++ b/src/main/java/org/olat/core/util/i18n/ui/TranslationToolStartCrumbController.java @@ -180,10 +180,17 @@ class TranslationToolStartCrumbController extends CrumbFormBasicController { ArrayHelper.sort(referencelangKeys, referenceLangValues, false, true, false); // Build css classes for reference languages String[] referenceLangCssClasses = i18nMgr.createLanguageFlagsCssClasses(referencelangKeys, "b_with_small_icon_left"); - // Preset first of the reference locales + // fxdiff: FXOLAT-213 Use first reference locale as default + referenceLocale = i18nMgr.getLocaleOrNull(referenceLangs.get(0)); + // Override with user preset Preferences guiPrefs = usess.getGuiPreferences(); String referencePrefs = (String) guiPrefs.get(I18nModule.class, I18nModule.GUI_PREFS_PREFERRED_REFERENCE_LANG, referenceLangs.get(0)); - referenceLocale = i18nMgr.getLocaleOrNull(referencePrefs); + for (String refLang : referencelangKeys) { + if (referencePrefs.equals(refLang)) { + referenceLocale = i18nMgr.getLocaleOrNull(referencePrefs); + break; + } + } referenceLangSelection = formFactory.addDropdownSingleselect("start.referenceLangSelection", formLayout, referencelangKeys, referenceLangValues, referenceLangCssClasses); referenceLangSelection.select(referenceLocale.toString(), true); diff --git a/src/main/java/org/olat/core/util/i18n/ui/_content/translationToolI18nItemEdit.html b/src/main/java/org/olat/core/util/i18n/ui/_content/translationToolI18nItemEdit.html index 3f1b982102c902601a2c445cf0e3277b8561887f..a61f7bcbde0838d3e16cc508717c81b6b96bea29 100644 --- a/src/main/java/org/olat/core/util/i18n/ui/_content/translationToolI18nItemEdit.html +++ b/src/main/java/org/olat/core/util/i18n/ui/_content/translationToolI18nItemEdit.html @@ -147,6 +147,7 @@ Ext.onReady(function() { #end area = $$('div.b_translation_edit_target textarea')[0]; area.observe('click', function(){checkClick('b_translation_edit_target', '${targetLanguageKey}');}); + area.focus(); }); /* ]]> */ </script> \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/i18n/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/util/i18n/ui/_i18n/LocalStrings_de.properties index 482d4827dde462cbae40f4db3b949a12996209f7..b8fb15c1547039e4bfa97e6a0e3f4f2f69dd0182 100644 --- a/src/main/java/org/olat/core/util/i18n/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/util/i18n/ui/_i18n/LocalStrings_de.properties @@ -12,7 +12,7 @@ configuration.deleteLangSelection=Zu l\u00F6schende Systemsprachen configuration.enabledLangSelection=Aktivierte Systemprachen configuration.enabledLangSelection.intro=W\u00E4hlen Sie aus der Liste der vorhandenen Sprachen diejenigen aus, welche den Benutzern in OLAT zur Verf\u00FCgung stehen sollen. configuration.exportLangSelection=Zu exportierende Systemsprachen -configuration.fallback.lang.must.be.enabed=Die Sprache "{"{0}"n nicht deaktiviert werden, da es sich um eine Fallback- und Referenzsprache handelt. Die Fallback- und Referenzsprachen k\u00F6nnen nur \u00FCber das Konfigurationsfile olat.local.properties ver\u00E4ndert werden. Wenden Sie sich an den Systemadministrator. +configuration.fallback.lang.must.be.enabed=Die Sprache "{0}" kann nicht deaktiviert werden, da es sich um eine Fallback- und Referenzsprache handelt. Die Fallback- und Referenzsprachen k\u00F6nnen nur \u00FCber das Konfigurationsfile olat.local.properties ver\u00E4ndert werden. Wenden Sie sich an den Systemadministrator. configuration.management=Sprachverwaltung configuration.management.create=Neue Sprache anlegen configuration.management.create.country=L\u00E4nderk\u00FCrzel diff --git a/src/main/java/org/olat/core/util/mail/_chelp/mail-admin.html b/src/main/java/org/olat/core/util/mail/_chelp/mail-admin.html new file mode 100644 index 0000000000000000000000000000000000000000..030f7c97fcc9c1e4a324a7890a8dafde99620840 --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/_chelp/mail-admin.html @@ -0,0 +1,17 @@ +<p> + $r.translate("chelp.mail.module") +</p> +<p> + <b>$r.translate("mail.admin.intern.enabled"):</b> + <ul> + <li>$r.translate("chelp.mail.module.intern")</li> + <li>$r.translate("chelp.mail.module.extern")</li> + </ul> +</p> +<p> + $r.translate("chelp.mail.module.extern.config") + <ul> + <li>$r.translate("mail.admin.intern.only")</li> + <li>$r.translate("mail.admin.intern.real.mail")</li> + </ul> +</p> diff --git a/src/main/java/org/olat/core/util/mail/_content/attachments.html b/src/main/java/org/olat/core/util/mail/_content/attachments.html new file mode 100644 index 0000000000000000000000000000000000000000..9d18a59089f7c40e2730b17185c9294062fb3d12 --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/_content/attachments.html @@ -0,0 +1,5 @@ +<ul class="b_mail_attachments""> + #foreach ($attachment in $attachments) + <li><a href="$mapperBaseURI/attachments/$attachment.key/$attachment.name" class="b_with_small_icon_left $attachment.cssClass" target="_blank"><span>$attachment.name</span></a></li> + #end +</ul> diff --git a/src/main/java/org/olat/core/util/mail/_content/mail.html b/src/main/java/org/olat/core/util/mail/_content/mail.html new file mode 100644 index 0000000000000000000000000000000000000000..90dfed2e0d5d9e8a086e33fe32439f39d4ff8805 --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/_content/mail.html @@ -0,0 +1,6 @@ +#if($r.available("back")) + $r.render("back")<br/><br/> +#end +<div class="b_mail_message"> + $r.render("mainCmp") +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/mail/_content/mails.html b/src/main/java/org/olat/core/util/mail/_content/mails.html new file mode 100644 index 0000000000000000000000000000000000000000..61f258d5bdd6d7d5adf2743293c054e776b8378a --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/_content/mails.html @@ -0,0 +1,4 @@ +#if($r.available("back")) + $r.render("back") +#end +$r.render("mainCmp") \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/mail/_content/mailsTable.html b/src/main/java/org/olat/core/util/mail/_content/mailsTable.html new file mode 100644 index 0000000000000000000000000000000000000000..197fea78d0040fe3fe9f42c7c2aae3705fe53e6d --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/_content/mailsTable.html @@ -0,0 +1,5 @@ +<h4 class="b_with_small_icon_left b_mail_icon">$title</h4> +#if($description) + <i>$description</i> +#end +$r.render("tableCmp") \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/mail/_i18n/LocalStrings_el.properties b/src/main/java/org/olat/core/util/mail/_i18n/LocalStrings_el.properties index 8ca0c591c6b952b915ef6f13b27c9ef958e9db48..c4df8d695d5d3b0947a0322b25f3f802781ef3b3 100644 --- a/src/main/java/org/olat/core/util/mail/_i18n/LocalStrings_el.properties +++ b/src/main/java/org/olat/core/util/mail/_i18n/LocalStrings_el.properties @@ -10,7 +10,7 @@ chelp.sendMail7=\u039B\u03AF\u03C3\u03C4\u03B1 \u03BC\u03B1\u03B8\u03B7\u03BC\u0 chelp.sendMail8=\u039A\u03B1\u03C4\u03AC\u03BB\u03BF\u03B3\u03BF\u03C2 \u03BC\u03B1\u03B8\u03B7\u03BC\u03AC\u03C4\u03C9\u03BD \u03C4\u03B1 \u03BF\u03C0\u03BF\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF \u03B1\u03C5\u03C4\u03AE \u03B7 \u03BF\u03BC\u03AC\u03B4\u03B1 contact.cp.from=\u0391\u03C0\u03BF\u03C3\u03C4\u03BF\u03BB\u03AE \u03B1\u03BD\u03C4\u03B9\u03B3\u03C1\u03AC\u03C6\u03BF\u03C5 \u03C3\u03C4\u03BF\u03BD \u03B1\u03C0\u03BF\u03C3\u03C4\u03BF\u03BB\u03AD\u03B1 footer.no.userdata=\n\n--- \n\u0391\u03C5\u03C4\u03CC \u03C4\u03BF \u03BC\u03AE\u03BD\u03C5\u03BC\u03B1 \u03AD\u03C7\u03B5\u03B9 \u03C3\u03C4\u03B1\u03BB\u03B5\u03AF \u03B1\u03C5\u03C4\u03CC\u03BC\u03B1\u03C4\u03B1 \u03BC\u03AD\u03C3\u03C9 \u03C4\u03BF\u03C5 OLAT.\n{0} -footer.with.userdata=\n\n--- \n{0} {1} ({2})\n{3}\n\n\u0391\u03C5\u03C4\u03CC \u03C4\u03BF \u03BC\u03AE\u03BD\u03C5\u03BC\u03B1 \u03AD\u03C7\u03B5\u03B9 \u03C3\u03C4\u03B1\u03BB\u03B5\u03AF \u03BC\u03AD\u03C3\u03C9 \u03C4\u03BF\u03C5 OLAT.\n{4} +footer.with.userdata=\n\n--- \n{2} {3} ({0})\n{4}\n\n\u0391\u03C5\u03C4\u03CC \u03C4\u03BF \u03BC\u03AE\u03BD\u03C5\u03BC\u03B1 \u03AD\u03C7\u03B5\u03B9 \u03C3\u03C4\u03B1\u03BB\u03B5\u03AF \u03BC\u03AD\u03C3\u03C9 \u03C4\u03BF\u03C5 OLAT.\n{1} help.hover.mail-templ=\u0392\u03BF\u03AE\u03B8\u03B5\u03B9\u03B1 \u03C3\u03C7\u03B5\u03C4\u03B9\u03BA\u03AC \u03BC\u03B5 \u03C4\u03B7 \u03C7\u03C1\u03AE\u03C3\u03B7 \u03C4\u03BF\u03C5 \u03C0\u03C1\u03BF\u03C4\u03CD\u03C0\u03BF\u03C5 mail mailhelper.error.attachment=\u03A4\u03BF mail \u03B4\u03B5\u03BD \u03BC\u03C0\u03CC\u03C1\u03B5\u03C3\u03B5 \u03BD\u03B1 \u03C3\u03C4\u03B1\u03BB\u03B5\u03AF \: \u03AC\u03BA\u03C5\u03C1\u03B5\u03C2 \u03B5\u03C0\u03B9\u03C3\u03C5\u03BD\u03AC\u03C8\u03B5\u03B9\u03C2. \u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE \u03B5\u03BD\u03B7\u03BC\u03B5\u03C1\u03CE\u03C3\u03C4\u03B5 \u03C4\u03BF\u03C5\u03C2 \u03C0\u03B1\u03C1\u03B1\u03BB\u03AE\u03C0\u03C4\u03B5\u03C2 \u03BC\u03B5 \u03AC\u03BB\u03BB\u03BF \u03C4\u03C1\u03CC\u03C0\u03BF. mailhelper.error.failedusers=\u03A4\u03BF mail \u03B4\u03B5\u03BD \u03BC\u03C0\u03CC\u03C1\u03B5\u03C3\u03B5 \u03BD\u03B1 \u03C3\u03C4\u03B1\u03BB\u03B5\u03AF \u03C3\u03C4\u03B1 \u03B1\u03BA\u03CC\u03BB\u03BF\u03C5\u03B8\u03B1 \u03AC\u03C4\u03BF\u03BC\u03B1. \u03A0\u03B1\u03C1\u03B1\u03BA\u03B1\u03BB\u03CE \u03B5\u03BD\u03B7\u03BC\u03B5\u03C1\u03CE\u03C3\u03C4\u03B5 \u03C4\u03BF\u03C5\u03C2 \u03C0\u03B1\u03C1\u03B1\u03BB\u03AE\u03C0\u03C4\u03B5\u03C2 \u03BC\u03B5 \u03AC\u03BB\u03BB\u03BF \u03C4\u03C1\u03CC\u03C0\u03BF \u03BA\u03B1\u03B9 \u03B5\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03B9\u03C2 \u03B4\u03B9\u03B5\u03C5\u03B8\u03CD\u03BD\u03C3\u03B5\u03B9\u03C2 \u03C4\u03C9\u03BD mail \u03C4\u03BF\u03C5\u03C2. diff --git a/src/main/java/org/olat/core/util/vfs/MergeSource.java b/src/main/java/org/olat/core/util/vfs/MergeSource.java index 97296a1f9207f7d6be36495e27431f4f7e93f230..0a83adf5ee46060576019e579ff5dc3a684b6c39 100644 --- a/src/main/java/org/olat/core/util/vfs/MergeSource.java +++ b/src/main/java/org/olat/core/util/vfs/MergeSource.java @@ -197,23 +197,43 @@ public class MergeSource extends AbstractVirtualContainer { if (path.equals("/")) return this; String childName = VFSManager.extractChild(path); + VFSItem vfsItem = null; for (Iterator iter = mergedContainers.iterator(); iter.hasNext();) { VFSContainer container = (VFSContainer) iter.next(); - if (container.getName().equals(childName)) { - VFSItem vfsItem = container.resolve(path.substring(childName.length() + 1)); + String nextPath = path.substring(childName.length() + 1); + // fxdiff FXOLAT-176 a namedContainer doesn't match with its own getName()! -> work with delegate + boolean nameMatch = container.getName().equals(childName); + if (container instanceof NamedContainerImpl && !nameMatch) { + // Special case: sometimes the path refers to the named containers + // delegate container, so try this one as well + container = ((NamedContainerImpl) container).getDelegate(); + String name = container.getName(); + if (name == null) { + // FXOLAT-195 The delegate of the named container does not + // have a name, so abort the special case and continue with + // next container + continue; + } + nameMatch = name.equals(childName); + } + if (nameMatch) { + vfsItem = container.resolve(nextPath); // set default filter on resolved file if it is a container if (vfsItem != null && vfsItem instanceof VFSContainer) { VFSContainer resolvedContainer = (VFSContainer) vfsItem; resolvedContainer.setDefaultItemFilter(defaultFilter); } return vfsItem; - } } for (Iterator iter = mergedContainersChildren.iterator(); iter.hasNext();) { VFSContainer container = (VFSContainer) iter.next(); - VFSItem vfsItem = container.resolve(path); + // fxdiff FXOLAT-176 a namedContainer doesn't match with its own getName()! -> work with delegate + if (container instanceof NamedContainerImpl) { + container = ((NamedContainerImpl) container).getDelegate(); + } + vfsItem = container.resolve(path); if (vfsItem != null) { // set default filter on resolved file if it is a container if (vfsItem instanceof VFSContainer) { @@ -225,7 +245,7 @@ public class MergeSource extends AbstractVirtualContainer { } return null; } - + /** * @see org.olat.core.util.vfs.VFSItem#getLocalSecurityCallback() */ diff --git a/src/main/java/org/olat/core/util/vfs/NamedLeaf.java b/src/main/java/org/olat/core/util/vfs/NamedLeaf.java index 604c46ebf6e304a3c5a3346b7bd5ba992a9c8a1a..3ffe226e3d4f3e0d9ff6ae435b2552d87fa799e6 100644 --- a/src/main/java/org/olat/core/util/vfs/NamedLeaf.java +++ b/src/main/java/org/olat/core/util/vfs/NamedLeaf.java @@ -48,7 +48,11 @@ public class NamedLeaf implements VFSLeaf { this.name = name; this.delegate = delegate; } - + + //fxdiff FXOLAT-125: virtual file system for CP + public VFSLeaf getDelegate() { + return delegate; + } /** * @see org.olat.core.util.vfs.VFSLeaf#getInputStream() diff --git a/src/main/java/org/olat/core/util/vfs/util/DescendingLastModifiedComparator.java b/src/main/java/org/olat/core/util/vfs/util/DescendingLastModifiedComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..426e197a1dce106c18d9cddbff7d637880c236ee --- /dev/null +++ b/src/main/java/org/olat/core/util/vfs/util/DescendingLastModifiedComparator.java @@ -0,0 +1,24 @@ +package org.olat.core.util.vfs.util; + +import java.util.Comparator; + +import org.olat.core.util.vfs.VFSItem; + +/** + * <p> + * Orders VFSItems by their last modified date descendingly + * <p> + * Initial Date: Sep 16, 2009 <br> + * + * @author gwassmann, gwassmann@frentix.com, www.frentix.com + */ +public class DescendingLastModifiedComparator implements Comparator<VFSItem> { + /** + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(VFSItem o1, VFSItem o2) { + long o1mod = o1.getLastModified(); + long o2mod = o2.getLastModified(); + return o1mod > o2mod ? -1 : 1; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/vfs/version/OrphanVersion.java b/src/main/java/org/olat/core/util/vfs/version/OrphanVersion.java new file mode 100644 index 0000000000000000000000000000000000000000..91d19705a653eaa910ed5f7df465438ed9505329 --- /dev/null +++ b/src/main/java/org/olat/core/util/vfs/version/OrphanVersion.java @@ -0,0 +1,66 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.core.util.vfs.version; + +import org.olat.core.util.vfs.LocalImpl; +import org.olat.core.util.vfs.VFSLeaf; + +/** + * + * Description:<br> + * TODO: srosse Class Description for OrphanVersion + * + * <P> + * Initial Date: 5 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-127: file versions maintenance tool +public class OrphanVersion { + + private final VFSLeaf orphan; + private final VFSLeaf versionsLeaf; + private final Versions versions; + + public OrphanVersion(VFSLeaf orphan, VFSLeaf versionsLeaf, Versions versions) { + this.orphan = orphan; + this.versionsLeaf = versionsLeaf; + this.versions = versions; + } + + public VFSLeaf getOrphan() { + return orphan; + } + + public VFSLeaf getVersionsLeaf() { + return versionsLeaf; + } + + public Versions getVersions() { + return versions; + } + + public String getOriginalFilePath() { + if(orphan instanceof LocalImpl) { + return ((LocalImpl)orphan).getBasefile().toString(); + } + return orphan.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java index e8f194803d8ad511c9b0c360c7cb43f9a950d0e3..bbce061ef5ae0d9ee8895423e1b537a4036e6666 100644 --- a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java +++ b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java @@ -39,6 +39,7 @@ import org.olat.core.configuration.Initializable; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.vfs.LocalFileImpl; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.LocalImpl; import org.olat.core.util.vfs.MergeSource; @@ -571,6 +572,84 @@ public class VersionsFileManager extends VersionsManager implements Initializabl } return rootVersionsContainer; } + + @Override + //fxdiff FXOLAT-127: file versions maintenance tool + public boolean delete(OrphanVersion orphan) { + VFSLeaf versionLeaf = orphan.getVersionsLeaf(); + + if (versionLeaf == null) return true; //already deleted + Versions versions = orphan.getVersions(); + for (VFSRevision versionToDelete : versions.getRevisions()) { + RevisionFileImpl versionImpl = (RevisionFileImpl) versionToDelete; + versionImpl.setContainer(orphan.getVersionsLeaf().getParentContainer()); + VFSLeaf fileToDelete = versionImpl.getFile(); + if (fileToDelete != null) { + fileToDelete.delete(); + } + } + versionLeaf.delete(); + return true; + } + + @Override + //fxdiff FXOLAT-127: file versions maintenance tool + public List<OrphanVersion> orphans() { + List<OrphanVersion> orphans = new ArrayList<OrphanVersion>(); + VFSContainer versionsContainer = getRootVersionsContainer(); + crawlForOrphans(versionsContainer, orphans); + return orphans; + } + //fxdiff FXOLAT-127: file versions maintenance tool + private void crawlForOrphans(VFSContainer container, List<OrphanVersion> orphans) { + List<VFSItem> children = container.getItems(); + for(VFSItem child:children) { + if(child instanceof VFSContainer) { + crawlForOrphans((VFSContainer)child, orphans); + } + if(child instanceof VFSLeaf) { + VFSLeaf versionsLeaf = (VFSLeaf)child; + if(child.getName().endsWith(".xml")) { + Versions versions = isOrphan(versionsLeaf); + if(versions == null) { + continue; + } else { + List<VFSRevision> revisions = versions.getRevisions(); + if(revisions != null) { + for(VFSRevision revision:revisions) { + if(revision instanceof RevisionFileImpl) { + ((RevisionFileImpl)revision).setContainer(container); + } + } + } + } + File originalFile = reversedOriginFile(child); + if(!originalFile.exists()) { + VFSLeaf orphan = new LocalFileImpl(originalFile); + orphans.add(new OrphanVersion(orphan, versionsLeaf, versions)); + } + } + } + } + } + //fxdiff FXOLAT-127: file versions maintenance tool + private Versions isOrphan(VFSLeaf potentialOrphan) { + try { + VersionsFileImpl versions = (VersionsFileImpl) XStreamHelper.readObject(mystream, potentialOrphan); + return versions; + } catch (Exception e) { + return null; + } + } + //fxdiff FXOLAT-127: file versions maintenance tool + private File reversedOriginFile(VFSItem versionXml) { + String path = File.separatorChar + versionXml.getName().substring(0, versionXml.getName().length() - 4); + for(VFSContainer parent=versionXml.getParentContainer(); parent != null && !parent.isSame(getRootVersionsContainer()); parent = parent.getParentContainer()) { + path = File.separatorChar + parent.getName() + path; + } + + return new File(getCanonicalRoot(), path); + } /** * diff --git a/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java b/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java index 55070e8d3666f3bac586aab3f1e9b99d6e387e65..fd4f383e32d914ca69d133435825704b4a4d6038 100644 --- a/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java +++ b/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java @@ -148,5 +148,11 @@ public abstract class VersionsManager extends BasicManager { * @return */ public abstract boolean rename(VFSItem item, String newname); + + //fxdiff FXOLAT-127: file versions maintenance tool + public abstract List<OrphanVersion> orphans(); + + //fxdiff FXOLAT-127: file versions maintenance tool + public abstract boolean delete(OrphanVersion orphan); } diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index fe2059ec194be322650270bca851d38b5d7ad853..28fd8c3b4fc7a5b453e2b08610aedc414f3861c7 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -85,6 +85,7 @@ import org.olat.core.util.xml.XStreamHelper; import org.olat.course.archiver.ScoreAccountingHelper; import org.olat.course.config.CourseConfig; import org.olat.course.config.CourseConfigManagerImpl; +import org.olat.course.config.ui.courselayout.CourseLayoutHelper; import org.olat.course.editor.EditorMainController; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.groupsandrights.PersistingCourseGroupManager; @@ -977,8 +978,7 @@ public class CourseFactory extends BasicManager { CourseConfig courseConfig = courseEnvironment.getCourseConfig(); if (courseConfig.hasCustomCourseCSS()) { // Notify the current tab that it should load a custom CSS - VFSContainer courseContainer = courseEnvironment.getCourseFolderContainer(); - customCSS = new CustomCSS(courseContainer, courseConfig.getCssLayoutRef(), usess); + return CourseLayoutHelper.getCustomCSS(usess, courseEnvironment); } return customCSS; } diff --git a/src/main/java/org/olat/course/CourseModule.java b/src/main/java/org/olat/course/CourseModule.java index 5be41e7d2f4c74c473a6e66d6f92df0aabbf1be6..aed4d445ff6d4a2669f9789e5e76b6fc726e4c82 100644 --- a/src/main/java/org/olat/course/CourseModule.java +++ b/src/main/java/org/olat/course/CourseModule.java @@ -65,7 +65,7 @@ public class CourseModule extends AbstractOLATModule { public static final String ORES_COURSE_ASSESSMENT = OresHelper.calculateTypeName(AssessmentManager.class); private static String helpCourseSoftkey; private static CoordinatorManager coordinatorManager; - private Map deployedCourses; + private Map<String, RepositoryEntry> deployedCourses; private boolean deployCoursesEnabled; private PropertyManager propertyManager; private CourseFactory courseFactory; @@ -174,7 +174,7 @@ public class CourseModule extends AbstractOLATModule { private RepositoryEntry deployCourse(DeployableCourseExport export, int access) { // let's see if we previously deployed demo courses... - RepositoryEntry re = (RepositoryEntry) getDeployedCourses().get(export.getIdentifier()); + RepositoryEntry re = getDeployedCourses().get(export.getIdentifier()); if (re != null) { logInfo("Course '" + export.getIdentifier() + "' has been previousely deployed. Skipping."); return re; @@ -189,7 +189,7 @@ public class CourseModule extends AbstractOLATModule { return null; } re = courseFactory.deployCourseFromZIP(file, access); - if (re != null) markAsDeployed(export, re, false); + if (re != null) markAsDeployed(export, re); return re; } return null; @@ -202,8 +202,19 @@ public class CourseModule extends AbstractOLATModule { * @param courseExportPath * @param re */ - private void markAsDeployed(DeployableCourseExport export, RepositoryEntry re, boolean update) { - Property prop = propertyManager.createPropertyInstance(null, null, null, "_o3_", "deployedCourses", export.getVersion(), re.getKey(), export.getIdentifier(), null); + private void markAsDeployed(DeployableCourseExport export, RepositoryEntry re) { + List<Property> props = propertyManager.findProperties(null, null, null, "_o3_", "deployedCourses"); + Property prop = null; + for (Property property : props) { + if (property.getLongValue() == re.getKey()){ + prop = property; + } + } + if (prop == null) { + prop = propertyManager.createPropertyInstance(null, null, null, "_o3_", "deployedCourses", export.getVersion(), re.getKey(), export.getIdentifier(), null); + } + prop.setFloatValue(export.getVersion()); + prop.setStringValue(export.getIdentifier()); propertyManager.saveProperty(prop); deployedCourses.put(export.getIdentifier(), re); } @@ -213,11 +224,11 @@ public class CourseModule extends AbstractOLATModule { * * @return */ - private Map getDeployedCourses() { + private Map<String, RepositoryEntry> getDeployedCourses() { if (deployedCourses != null) return deployedCourses; - List props = propertyManager.findProperties(null, null, null, "_o3_", "deployedCourses"); - deployedCourses = new HashMap(props.size()); - for (Iterator iter = props.iterator(); iter.hasNext();) { + List<?> props = propertyManager.findProperties(null, null, null, "_o3_", "deployedCourses"); + deployedCourses = new HashMap<String, RepositoryEntry>(props.size()); + for (Iterator<?> iter = props.iterator(); iter.hasNext();) { Property prop = (Property) iter.next(); Long repoKey = prop.getLongValue(); RepositoryEntry re = null; @@ -225,14 +236,27 @@ public class CourseModule extends AbstractOLATModule { if (re != null) { //props with floatValue null are old entries - delete them. if (prop.getFloatValue() == null) { - //those are courses deployed with the old mechanism, delete them and redeploy - logInfo("This course was already deployed but has old property values. Deleting it and redeploy course: "+prop.getStringValue()); - deleteCourseAndProperty(prop, re); - re = null; //do not add to deployed courses + //those are courses deployed with the old mechanism, check, if they exist and what should be done with them: + //fxdiff: no delete! + logInfo("This course was already deployed and has old property values. course: "+prop.getStringValue()); + for (DeployableCourseExport export: deployableCourseExports) { + if (export.getIdentifier().equals(prop.getStringValue())) { + logInfo("found this old course in the deployable courses list"); + if (export.isRedeploy()){ + // found in deployableCourses again and it should be redeployed, therefore delete: + logInfo("marked as to be redeployed, therefore delete first!"); + deleteCourseAndProperty(prop, re); + re = null; //do not add to deployed courses + } else { + logInfo("no redeploy! just update its version."); + markAsDeployed(export, re); + } + } + } } else { //check if latest version if course is installed for (DeployableCourseExport export: deployableCourseExports) { - if (export.getIdentifier().equals(prop.getStringValue()) && export.getVersion() > prop.getFloatValue()) { + if (export.getIdentifier().equals(prop.getStringValue()) && export.getVersion() > prop.getFloatValue() && export.isRedeploy()) { //we have a newer version - delete the old course logInfo("There is a new version for this course available. Deleting it and redeploy course: "+prop.getStringValue()); deleteCourseAndProperty(prop, re); diff --git a/src/main/java/org/olat/course/CoursefolderWebDAVProvider.java b/src/main/java/org/olat/course/CoursefolderWebDAVProvider.java index 2683cf3fba68add40b86a8453c93243f727172d4..ef86518875ca0cdf1e6629440a4245cf941ee0bf 100644 --- a/src/main/java/org/olat/course/CoursefolderWebDAVProvider.java +++ b/src/main/java/org/olat/course/CoursefolderWebDAVProvider.java @@ -21,8 +21,9 @@ package org.olat.course; -import java.util.Iterator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; @@ -31,6 +32,14 @@ import org.olat.core.util.servlets.WebDAVProvider; import org.olat.core.util.vfs.MergeSource; import org.olat.core.util.vfs.NamedContainerImpl; import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.groupsandrights.CourseRights; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupManager; +import org.olat.group.BusinessGroupManagerImpl; +import org.olat.group.context.BGContextManager; +import org.olat.group.context.BGContextManagerImpl; +import org.olat.group.right.BGRightManager; +import org.olat.group.right.BGRightManagerImpl; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; /** @@ -46,17 +55,42 @@ public class CoursefolderWebDAVProvider implements WebDAVProvider { public VFSContainer getContainer(Identity identity) { MergeSource cfRoot = new MergeSource(null, null); + + // First get all courses where user is owner in the repository RepositoryManager rm = RepositoryManager.getInstance(); - List courseEntries = rm.queryByOwner(identity, CourseModule.getCourseTypeName()); + List<RepositoryEntry> courseEntries = rm.queryByOwner(identity, CourseModule.getCourseTypeName()); + + // Second get all courses where user has author rights because he is in a + // course right group with course rights associated + // fxdiff: VCRP-15 + Set<Long> smashDuplicates = new HashSet<Long>(); + for(RepositoryEntry courseEntry:courseEntries) { + smashDuplicates.add(courseEntry.getKey()); + } + + BGContextManager bgContextManager = BGContextManagerImpl.getInstance(); + BGRightManager bgRightManager = BGRightManagerImpl.getInstance(); + BusinessGroupManager bgManager = BusinessGroupManagerImpl.getInstance(); + List<BusinessGroup> groups = bgManager.findBusinessGroupsAttendedBy(BusinessGroup.TYPE_RIGHTGROUP, identity, null); + for (BusinessGroup group:groups) { + if(bgRightManager.hasBGRight(CourseRights.RIGHT_COURSEEDITOR, identity, group.getGroupContext())) { + List<RepositoryEntry> entries = bgContextManager.findRepositoryEntriesForBGContext(group.getGroupContext()); + for(RepositoryEntry entry:entries) { + if(!smashDuplicates.contains(entry.getKey())) { + courseEntries.add(entry); + smashDuplicates.add(entry.getKey()); + } + } + } + } - for (Iterator iter = courseEntries.iterator(); iter.hasNext();) { - RepositoryEntry re = (RepositoryEntry) iter.next(); + // Add all found repo entries to merge source + for (RepositoryEntry re:courseEntries) { OLATResourceable res = re.getOlatResource(); ICourse course = CourseFactory.loadCourse(res.getResourceableId()); VFSContainer courseFolder = course.getCourseFolderContainer(); - //NamedContainerImpl cfContainer = new NamedContainerImpl(Formatter.makeStringFilesystemSave(course.getCourseTitle()), courseFolder); - NamedContainerImpl cfContainer; - cfContainer = new NamedContainerImpl(Formatter.makeStringFilesystemSave(course.getCourseTitle()), courseFolder); + String courseTitle = Formatter.makeStringFilesystemSave(course.getCourseTitle()); + NamedContainerImpl cfContainer = new NamedContainerImpl(courseTitle, courseFolder); cfRoot.addContainer(cfContainer); } return cfRoot; diff --git a/src/main/java/org/olat/course/DeployableCourseExport.java b/src/main/java/org/olat/course/DeployableCourseExport.java index 155b8bde2cc33fd4fca7b43f533b26fc4ec98dea..822c6d91a44774b4626f287bed19bcd8683ad7df 100644 --- a/src/main/java/org/olat/course/DeployableCourseExport.java +++ b/src/main/java/org/olat/course/DeployableCourseExport.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; @@ -47,6 +48,7 @@ public class DeployableCourseExport { private int access = 4; private Float version; private String identifier; + private boolean redeploy = false; //default for a demo course is false private boolean helpCourse = false; OLog log = Tracing.createLoggerFor(this.getClass()); @@ -101,19 +103,22 @@ public class DeployableCourseExport { private File downloadZipFromUrl(URL url) { try { log.info("Downloading demo course file: "+url); - HttpURLConnection uc = (HttpURLConnection) url.openConnection(); - int responseCode = uc.getResponseCode(); + // fxdiff: allow local files as demo course source + URLConnection uc = url.openConnection(); String contentType = uc.getContentType(); int contentLength = uc.getContentLength(); - if (responseCode != 200 || !contentType.startsWith("application/") || contentLength == -1) { - if (responseCode != 200) { - log.warn("Server response was not successful code: "+responseCode+" with url: " + courseUrl); - } else if (contentLength == -1) { - log.warn("File is empty!"); - } else if (!contentType.startsWith("application/")) { - log.warn("File is not a binary file! ContentType is: " + contentType + " from url:" + courseUrl); + if (uc instanceof HttpURLConnection){ + int responseCode = ((HttpURLConnection)uc).getResponseCode(); + if (responseCode != 200 || !contentType.startsWith("application/") || contentLength == -1) { + if (responseCode != 200) { + log.warn("Server response was not successful code: "+responseCode+" with url: " + courseUrl); + } else if (contentLength == -1) { + log.warn("File is empty!"); + } else if (!contentType.startsWith("application/")) { + log.warn("File is not a binary file! ContentType is: " + contentType + " from url:" + courseUrl); + } + return null; } - return null; } InputStream raw = uc.getInputStream(); InputStream in = new BufferedInputStream(raw); @@ -168,4 +173,12 @@ public class DeployableCourseExport { return identifier; } + public void setRedeploy(boolean redeploy) { + this.redeploy = redeploy; + } + + public boolean isRedeploy() { + return redeploy; + } + } diff --git a/src/main/java/org/olat/course/PersistingCourseImpl.java b/src/main/java/org/olat/course/PersistingCourseImpl.java index 7152ad400fd10725f241961cb5e9f1ed4c9c8a0d..369c3ca6014f649ec8735ef40db3630b21a8f0d2 100644 --- a/src/main/java/org/olat/course/PersistingCourseImpl.java +++ b/src/main/java/org/olat/course/PersistingCourseImpl.java @@ -300,6 +300,8 @@ public class PersistingCourseImpl implements ICourse, OLATResourceable, Serializ FileUtils.copyFileToDir(new File(fCourseBase, EDITORTREEMODEL_XML), exportDirectory, "course export exitortreemodel"); // export run structure FileUtils.copyFileToDir(new File(fCourseBase, RUNSTRUCTURE_XML), exportDirectory, "course export runstructure"); + // fxdiff: export layout-folder + FileUtils.copyDirToDir(new OlatRootFolderImpl(courseRootContainer.getRelPath() + File.separator + "layout", null).getBasefile(), exportDirectory, "course export layout folder"); // export course folder FileUtils.copyDirToDir(getIsolatedCourseFolder().getBasefile(), exportDirectory, "course export folder"); // export any node data diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml index 62dd4d075f07be33bbc33fa219f7840db9f5af2d..4b9c225a354de83bf69fd14ecf28d3d8cb6800d6 100644 --- a/src/main/java/org/olat/course/_spring/courseContext.xml +++ b/src/main/java/org/olat/course/_spring/courseContext.xml @@ -62,8 +62,6 @@ </property> - - <!-- Three Logs are available within a course: - Adminlog, logs administrative activity in the course - Userlog, logs the course user activity, where the user is identifiable diff --git a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java index 86b12e361e7283736f2bc2c44a6c35e4450683b4..b1b42f3662f7c95449116149a2cbbbbbc678d3cc 100644 --- a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java +++ b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java @@ -33,19 +33,25 @@ import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; +import org.olat.core.id.OLATResourceable; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.CourseModule; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.assessment.AssessmentManager; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.STCourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.group.BusinessGroup; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; @@ -269,10 +275,60 @@ public class ScoreAccountingHelper { firstIteration = false; rowNumber++; } + //fxdiff VCRP-4: assessment overview with max score + StringBuilder tableFooter = new StringBuilder(); + tableFooter.append("\t\n").append("\t\n").append(t.translate("legend")).append("\t\n").append("\t\n"); + Iterator iterNodes = myNodes.iterator(); + while (iterNodes.hasNext()) { + AssessableCourseNode acnode = (AssessableCourseNode) iterNodes.next(); + if (!acnode.hasScoreConfigured()) { + // only show min/max/cut legend when score configured + continue; + } + String minVal; + String maxVal; + String cutVal; + if(acnode instanceof STCourseNode || !acnode.hasScoreConfigured()) { + minVal = maxVal = cutVal = "-"; + } else { + minVal = acnode.getMinScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMinScoreConfiguration()); + maxVal = acnode.getMaxScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMaxScoreConfiguration()); + cutVal = acnode.getCutValueConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getCutValueConfiguration()); + if (acnode.hasPassedConfigured()) { + cutVal = acnode.getCutValueConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getCutValueConfiguration()); + } else { + cutVal = "-"; + } + } + + tableFooter.append('"'); + tableFooter.append(acnode.getShortTitle()); + tableFooter.append('"'); + tableFooter.append('\n'); + + tableFooter.append("\t\t"); + tableFooter.append("minValue"); + tableFooter.append('\t'); + tableFooter.append(minVal); + tableFooter.append('\n'); + + tableFooter.append("\t\t"); + tableFooter.append("maxValue"); + tableFooter.append('\t'); + tableFooter.append(maxVal); + tableFooter.append('\n'); + + tableFooter.append("\t\t"); + tableFooter.append("cutValue"); + tableFooter.append('\t'); + tableFooter.append(cutVal); + tableFooter.append('\n'); + } table.append(tableHeader1); table.append(tableHeader2); table.append(tableContent); + table.append(tableFooter); String tab = table.toString(); return tab; @@ -285,24 +341,26 @@ public class ScoreAccountingHelper { * @param courseEnv * @return The list of identities from this course */ - public static List loadUsers(CourseEnvironment courseEnv) { - List identites = new ArrayList(); + //fxdiff VCRP-1,2: access control of resources + public static List<Identity> loadUsers(CourseEnvironment courseEnv) { CourseGroupManager gm = courseEnv.getCourseGroupManager(); BaseSecurity securityManager = BaseSecurityManager.getInstance(); - List groups = gm.getAllLearningGroupsFromAllContexts(); - - Iterator iter = groups.iterator(); - while (iter.hasNext()) { - BusinessGroup group = (BusinessGroup) iter.next(); - SecurityGroup participants = group.getPartipiciantGroup(); - List ids = securityManager.getIdentitiesOfSecurityGroup(participants); - identites.addAll(ids); + List<BusinessGroup> groups = gm.getAllLearningGroupsFromAllContexts(); + + List<SecurityGroup> secGroups = new ArrayList<SecurityGroup>(); + for (BusinessGroup group: groups) { + secGroups.add(group.getPartipiciantGroup()); } - return identites; + + OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, courseEnv.getCourseResourceableId()); + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, false); + if(re != null && re.getParticipantGroup() != null) { + secGroups.add(re.getParticipantGroup()); + } + + return securityManager.getIdentitiesOfSecurityGroups(secGroups); } - - /** * Load all nodes which are assessable * diff --git a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties index b47b7bef03063da60d4a86ede59a9512154265bc..5dcc34c8957ec9b294858a84ef87ffbca9f572a8 100644 --- a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties @@ -77,6 +77,7 @@ fo=Forums help.hover.log=Beschreibung der m\u00F6glichen Logfile-Eintr\u00E4ge index.intro=Mit diesem Werkzeug k\u00F6nnen verschiedene Daten dieses OLAT-Kurses archiviert werden.<p> W\u00E4hlen Sie links im Menu einen Eintrag aus um zu beginnen. index.title=Datenarchivierungswerkzeug +legend=Legende logfilechooserform.archive=Archivieren logfilechooserform.begindate=von logfilechooserform.deletelog=nach Archivieren l\u00F6schen diff --git a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties index 74ceffcf3dd43c6b2aa2de32e815d4a5b4dedba6..95dbbe1e9252dd666b07216d9a73627c64972d8d 100644 --- a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 10:53:20 CET 2011 +#Mon May 16 17:10:05 CEST 2011 archive.dialog.notsuccessfully=This file dialog could not be archived. archive.dialog.successfully=This file dialog has been archived in your personal folder. archive.dropbox.notasks=This drop box is still empty. @@ -77,6 +77,7 @@ fo=Forums help.hover.log=Description of possible log file entries index.intro=By means of this tool you can archive various data from your OLAT course.<p> Choose a topic from the menu on the left to start. index.title=Data archiving tool +legend=Legende logfilechooserform.archive=Archive logfilechooserform.begindate=from logfilechooserform.deletelog=Delete after archiving diff --git a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java index 1d3a7f9dd78bdba008310aa3a56e859cfd220e72..12e5c96dfa46a4827519706da5476e4f2374e4f2 100644 --- a/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java +++ b/src/main/java/org/olat/course/assessment/AssessedIdentitiesTableDataModel.java @@ -26,8 +26,10 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import org.olat.admin.user.UserTableDataModel; import org.olat.core.gui.components.table.BooleanColumnDescriptor; import org.olat.core.gui.components.table.ColumnDescriptor; +import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.DefaultTableDataModel; import org.olat.core.gui.components.table.TableController; @@ -35,6 +37,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.Util; import org.olat.course.nodes.AssessableCourseNode; +import org.olat.course.nodes.STCourseNode; import org.olat.course.nodes.ta.StatusForm; import org.olat.course.nodes.ta.StatusManager; import org.olat.course.properties.CoursePropertyManager; @@ -56,29 +59,41 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel { private static final String COL_DETAILS = "details"; private static final String COL_ATTEMPTS = "attempts"; private static final String COL_SCORE = "score"; + //fxdiff VCRP-4: assessment overview with max score + private static final String COL_MINSCORE = "minScore"; + private static final String COL_MAXSCORE = "maxScore"; private static final String COL_PASSED = "passed"; private static final String COL_STATUS = "status"; - private List colMapping; + private List<String> colMapping; private List<String> userPropertyNameList; private List<UserPropertyHandler> userPropertyHandlers; private static final String usageIdentifyer = AssessedIdentitiesTableDataModel.class.getCanonicalName(); private Translator translator; + + private String identifyer; /** * @param objects List of wrapped identities (AssessedIdentityWrapper) * @param courseNode the current courseNode */ public AssessedIdentitiesTableDataModel(List objects, AssessableCourseNode courseNode, Locale locale, boolean isAdministrativeUser) { + this(objects, courseNode, locale, isAdministrativeUser, false); + } + + public AssessedIdentitiesTableDataModel(List objects, AssessableCourseNode courseNode, Locale locale, boolean isAdministrativeUser, + boolean compatibilityUserProperties) { super(objects); this.courseNode = courseNode; this.setLocale(locale); this.translator = Util.createPackageTranslator(this.getClass(), locale); - userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser); + //fxdiff FXOLAT-108: improve results table of tests + identifyer = compatibilityUserProperties ? UserTableDataModel.class.getCanonicalName() : usageIdentifyer; + userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor(identifyer, isAdministrativeUser); colCount = 0; // default - colMapping = new ArrayList(); + colMapping = new ArrayList<String>(); // store all configurable column positions in a lookup array colMapping.add(colCount++, COL_NAME); Iterator <UserPropertyHandler> propHandlerIterator = userPropertyHandlers.iterator(); @@ -97,7 +112,10 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel { colMapping.add(colCount++, COL_ATTEMPTS); } if (courseNode.hasScoreConfigured()) { - colMapping.add(colCount++, COL_SCORE); + colMapping.add(colCount++, COL_SCORE); + //fxdiff VCRP-4: assessment overview with max score + colMapping.add(colCount++, COL_MINSCORE); + colMapping.add(colCount++, COL_MAXSCORE); } if (courseNode.hasStatusConfigured()) { colMapping.add(colCount++, COL_STATUS); @@ -141,7 +159,7 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel { // lookup the column name first and // deliver value based on the column name - String colName = (String) colMapping.get(col); + String colName = colMapping.get(col); if (colName.equals(COL_NAME)) return identity.getName(); else if (userPropertyNameList.contains(colName)) return identity.getUser().getProperty(colName, getLocale()); else if (colName.equals(COL_DETAILS)) return wrappedIdentity.getDetailsListView(); @@ -149,8 +167,21 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel { else if (colName.equals(COL_SCORE)) { ScoreEvaluation scoreEval = wrappedIdentity.getUserCourseEnvironment().getScoreAccounting().evalCourseNode(courseNode); if (scoreEval == null) scoreEval = new ScoreEvaluation(null, null); - return AssessmentHelper.getRoundedScore(scoreEval.getScore()); - } else if (colName.equals(COL_STATUS)) { + //fxdiff VCRP-4: assessment overview with max score + return scoreEval.getScore(); + } else if (colName.equals(COL_MINSCORE)) { + //fxdiff VCRP-4: assessment overview with max score + if(!(courseNode instanceof STCourseNode)) { + return courseNode.getMinScoreConfiguration(); + } + return ""; + } else if (colName.equals(COL_MAXSCORE)) { + //fxdiff VCRP-4: assessment overview with max score + if(!(courseNode instanceof STCourseNode)) { + return courseNode.getMaxScoreConfiguration(); + } + return ""; + } else if (colName.equals(COL_STATUS)) { return getStatusFor(courseNode, wrappedIdentity); }else if (colName.equals(COL_PASSED)) { ScoreEvaluation scoreEval = wrappedIdentity.getUserCourseEnvironment().getScoreAccounting().evalCourseNode(courseNode); @@ -195,20 +226,30 @@ public class AssessedIdentitiesTableDataModel extends DefaultTableDataModel { userListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.name", colCount++, editCmd, getLocale())); for (int i = 0; i < userPropertyHandlers.size(); i++) { - UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i); - userListCtr.addColumnDescriptor(userPropertyHandler.getColumnDescriptor(i+1, null, getLocale())); + UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i); + //fxdiff FXOLAT-108: improve results table of tests + boolean visible = UserManager.getInstance().isMandatoryUserProperty(identifyer , userPropertyHandler); + userListCtr.addColumnDescriptor(visible, userPropertyHandler.getColumnDescriptor(i+1, null, getLocale())); colCount++; } if ( (courseNode != null) && isNodeOrGroupFocus) { - if (courseNode.hasDetails()) { - userListCtr.addColumnDescriptor((courseNode.getDetailsListViewHeaderKey() == null ? false : true), - new DefaultColumnDescriptor(courseNode.getDetailsListViewHeaderKey(), colCount++, null, getLocale())); + if (courseNode.hasDetails()) { + //fxdiff VCRP-4: assessment overview with max score + String headerKey = courseNode.getDetailsListViewHeaderKey(); + userListCtr.addColumnDescriptor((headerKey == null ? false : true), + new DefaultColumnDescriptor(headerKey == null ? "table.header.details" : headerKey, colCount++, null, getLocale())); } if (courseNode.hasAttemptsConfigured()) { userListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.attempts", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT)); } if (courseNode.hasScoreConfigured()) { - userListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.score", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT)); + userListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, + new ScoreCellRenderer())); + //fxdiff VCRP-4: assessment overview with max score + userListCtr.addColumnDescriptor(false, new CustomRenderColumnDescriptor("table.header.min", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, + new ScoreCellRenderer())); + userListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.max", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, + new ScoreCellRenderer())); } if (courseNode.hasStatusConfigured()) { userListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.status", colCount++, null, getLocale(), ColumnDescriptor.ALIGNMENT_LEFT)); diff --git a/src/main/java/org/olat/course/assessment/AssessmentEditController.java b/src/main/java/org/olat/course/assessment/AssessmentEditController.java index b864ae0c388d9eb14513ba758bf09d37484f76f0..625bdaf7485de639795adf9b03c9930a2f4ed47d 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentEditController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentEditController.java @@ -198,7 +198,11 @@ public class AssessmentEditController extends BasicController { fireEvent(ureq, Event.CHANGED_EVENT); } } else if (source == detailsEditController) { - // anything to do?? + //fxdiff FXOLAT-108: reset SCORM test + if(event == Event.CHANGED_EVENT) { + doUpdateAssessmentData(ureq.getIdentity()); + fireEvent(ureq, Event.CHANGED_EVENT); + } } else if (source == alreadyLockedDialogController) { if (event == Event.CANCELLED_EVENT || DialogBoxUIFactory.isOkEvent(event)) { //ok clicked or box closed @@ -227,14 +231,16 @@ public class AssessmentEditController extends BasicController { } if (assessmentForm.isHasScore() && assessmentForm.isScoreDirty()) { - newScore = new Float(assessmentForm.getScore()); + //fxdiff VCRP-4: assessment overview with max score + newScore = assessmentForm.getScore(); // Update properties in db later... see // courseNode.updateUserSocreAndPassed... } if (assessmentForm.isHasPassed()) { - if (assessmentForm.getCut() != null && StringHelper.containsNonWhitespace(assessmentForm.getScore())) { - newPassed = Float.parseFloat(assessmentForm.getScore()) >= assessmentForm.getCut().floatValue() + if (assessmentForm.getCut() != null && assessmentForm.getScore() != null) { + //fxdiff VCRP-4: assessment overview with max score + newPassed = assessmentForm.getScore() >= assessmentForm.getCut().floatValue() ? Boolean.TRUE : Boolean.FALSE; } else { //"passed" info was changed or not diff --git a/src/main/java/org/olat/course/assessment/AssessmentForm.java b/src/main/java/org/olat/course/assessment/AssessmentForm.java index 762a6173b8eed1474f96bd765101e408e2232874..a52de510b45f4a105a73a1f8e47bd8865ef1f074 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentForm.java +++ b/src/main/java/org/olat/course/assessment/AssessmentForm.java @@ -34,6 +34,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; @@ -126,11 +127,11 @@ public class AssessmentForm extends FormBasicController { boolean isScoreDirty() { if (!hasScore) return false; if (scoreValue == null) return !score.getValue().equals(""); - return Float.parseFloat(score.getValue()) != scoreValue.floatValue(); + return parseFloat(score) != scoreValue.floatValue(); } - String getScore() { - return score.getValue(); + Float getScore() { + return parseFloat(score); } boolean isUserCommentDirty () { @@ -160,19 +161,25 @@ public class AssessmentForm extends FormBasicController { @Override protected boolean validateFormLogic (UserRequest ureq) { if (hasScore) { + //fxdiff VCRP-4: assessment overview with max score try { - Float.parseFloat(score.getValue()); + if(parseFloat(score) == null) { + score.setErrorKey("form.error.wrongFloat", null); + return false; + } } catch (NumberFormatException e) { score.setErrorKey("form.error.wrongFloat", null); return false; } - if ((min != null && Float.parseFloat(score.getValue()) < min.floatValue()) - || (Float.parseFloat(score.getValue()) < AssessmentHelper.MIN_SCORE_SUPPORTED)) { + + Float fscore = parseFloat(score); + if ((min != null && fscore < min.floatValue()) + || fscore < AssessmentHelper.MIN_SCORE_SUPPORTED) { score.setErrorKey("form.error.scoreOutOfRange", null); return false; } - if ((max != null && Float.parseFloat(score.getValue()) > max.floatValue()) - || Float.parseFloat(score.getValue()) > AssessmentHelper.MAX_SCORE_SUPPORTED) { + if ((max != null && fscore > max.floatValue()) + || fscore > AssessmentHelper.MAX_SCORE_SUPPORTED) { score.setErrorKey("form.error.scoreOutOfRange", null); return false; } @@ -180,6 +187,19 @@ public class AssessmentForm extends FormBasicController { return true; } + //fxdiff VCRP-4: assessment overview with max score + private Float parseFloat(TextElement textEl) throws NumberFormatException { + String scoreStr = textEl.getValue(); + if(!StringHelper.containsNonWhitespace(scoreStr)) { + return null; + } + int index = scoreStr.indexOf(','); + if(index >= 0) { + scoreStr = scoreStr.replace(',', '.'); + return Float.parseFloat(scoreStr); + } + return Float.parseFloat(scoreStr); + } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { @@ -202,10 +222,12 @@ public class AssessmentForm extends FormBasicController { if (hasPassed) { cut = assessableCourseNode.getCutValueConfiguration(); } - - minVal = uifactory.addStaticTextElement("minval", "form.min", ((min == null) ? translate("form.valueUndefined") : min.toString()), formLayout); - maxVal = uifactory.addStaticTextElement("maxval", "form.max", ((max == null) ? translate("form.valueUndefined") : max.toString()), formLayout); + //fxdiff VCRP-4: assessment overview with max score + String minStr = AssessmentHelper.getRoundedScore(min); + String maxStr = AssessmentHelper.getRoundedScore(max); + minVal = uifactory.addStaticTextElement("minval", "form.min", ((min == null) ? translate("form.valueUndefined") : minStr), formLayout); + maxVal = uifactory.addStaticTextElement("maxval", "form.max", ((max == null) ? translate("form.valueUndefined") : maxStr), formLayout); // Use init variables from wrapper, already loaded from db scoreValue = scoreEval.getScore(); @@ -215,7 +237,8 @@ public class AssessmentForm extends FormBasicController { if (scoreValue != null) { score.setValue(AssessmentHelper.getRoundedScore(scoreValue)); } - score.setRegexMatchCheck("(\\d+)||(\\d+\\.\\d{1,3})", "form.error.wrongFloat"); + //fxdiff VCRP-4: assessment overview with max score + score.setRegexMatchCheck("(\\d+)||(\\d+\\.\\d{1,3})||(\\d+\\,\\d{1,3})", "form.error.wrongFloat"); } if (hasPassed) { @@ -223,7 +246,7 @@ public class AssessmentForm extends FormBasicController { // Display cut value if defined cutVal = uifactory.addStaticTextElement( "cutval","form.cut" , - ((cut == null) ? translate("form.valueUndefined") : cut.toString()), + ((cut == null) ? translate("form.valueUndefined") : AssessmentHelper.getRoundedScore(cut)), formLayout ); } diff --git a/src/main/java/org/olat/course/assessment/AssessmentHelper.java b/src/main/java/org/olat/course/assessment/AssessmentHelper.java index 435d1c0b2373a6a7995c75ca7a78f102dc7678e4..a7a74254c34614456107aeec7ff371736ed5d5bc 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentHelper.java +++ b/src/main/java/org/olat/course/assessment/AssessmentHelper.java @@ -21,16 +21,18 @@ package org.olat.course.assessment; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; import org.olat.core.logging.Tracing; -import org.olat.core.util.Formatter; import org.olat.core.util.nodes.INode; import org.olat.core.util.tree.TreeVisitor; import org.olat.core.util.tree.Visitor; @@ -67,6 +69,8 @@ public class AssessmentHelper { public static final float MAX_SCORE_SUPPORTED = 10000f; /** Lowest score value supported by OLAT * */ public static final float MIN_SCORE_SUPPORTED = -10000f; + //fxdiff VCRP-4: assessment overview with max score + private final static DecimalFormat scoreFormat = new DecimalFormat("#0.###", new DecimalFormatSymbols(Locale.ENGLISH)); /** * Wraps an identity and it's score evaluation / attempts in a wrapper object @@ -226,9 +230,15 @@ public class AssessmentHelper { * @param score The score to be rounded * @return The rounded score for GUI presentation */ + //fxdiff VCRP-4: assessment overview with max score public static String getRoundedScore(Float score) { if (score == null) return null; - return Formatter.roundToString(score.floatValue(), 3); + + //cluster_OK the formatter is not multi-thread and costly to create + synchronized(scoreFormat) { + return scoreFormat.format(score); + } + //return Formatter.roundToString(score.floatValue(), 3); } public static final String KEY_TYPE = "type"; @@ -239,9 +249,13 @@ public class AssessmentHelper { public static final String KEY_TITLE_LONG = "long.title"; public static final String KEY_PASSED = "passed"; public static final String KEY_SCORE = "score"; + public static final String KEY_SCORE_F = "fscore"; public static final String KEY_ATTEMPTS = "attempts"; public static final String KEY_DETAILS = "details"; public static final String KEY_SELECTABLE = "selectable"; + //fxdiff VCRP-4: assessment overview with max score + public static final String KEY_MIN = "minScore"; + public static final String KEY_MAX = "maxScore"; @@ -315,9 +329,19 @@ public class AssessmentHelper { hasDisplayableValuesConfigured = true; Float score = scoreEvaluation.getScore(); if (score != null) { + //fxdiff VCRP-4: assessment overview with max score nodeData.put(KEY_SCORE, AssessmentHelper.getRoundedScore(score)); + nodeData.put(KEY_SCORE_F, score); hasDisplayableUserValues = true; } + //fxdiff VCRP-4: assessment overview with max score + if(!(assessableCourseNode instanceof STCourseNode)) { + Float maxScore = assessableCourseNode.getMaxScoreConfiguration(); + nodeData.put(KEY_MAX, maxScore); + Float minScore = assessableCourseNode.getMinScoreConfiguration(); + nodeData.put(KEY_MIN, minScore); + } + } // passed if (assessableCourseNode.hasPassedConfigured()) { diff --git a/src/main/java/org/olat/course/assessment/AssessmentMainController.java b/src/main/java/org/olat/course/assessment/AssessmentMainController.java index 4446a860ee3195c444ec555b78a3e59f290d8279..9c6c5ae845be1c6fa8b67a17f167ee421a640ce2 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMainController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMainController.java @@ -23,19 +23,19 @@ package org.olat.course.assessment; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringEscapeUtils; -import org.olat.admin.securitygroup.gui.UserControllerFactory; import org.olat.admin.user.UserTableDataModel; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.SecurityGroup; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.persistence.DBFactory; -import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -86,12 +86,14 @@ import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.CourseNodeFactory; -import org.olat.course.nodes.ProjectBrokerCourseNode;// TODO:cg 04.11.2010 ProjectBroker : no assessment-tool in V1.0 , remove projectbroker completely form assessment-tool gui +import org.olat.course.nodes.ProjectBrokerCourseNode; import org.olat.course.nodes.STCourseNode; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; import org.olat.group.BusinessGroup; import org.olat.group.ui.context.BGContextTableModel; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; import org.olat.user.UserManager; /** @@ -143,6 +145,8 @@ public class AssessmentMainController extends MainLayoutBasicController implemen Map<Long, UserCourseEnvironment> localUserCourseEnvironmentCache; // package visibility for avoiding synthetic accessor method // List of groups to which the user has access rights in this course private List<BusinessGroup> coachedGroups; + //Is tutor from the security group of repository entry + private boolean repoTutor = false; // some state variables private AssessableCourseNode currentCourseNode; @@ -234,7 +238,10 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea nodeChoose = createVelocityContainer("nodechoose"); // Initialize all groups that the user is allowed to coach - coachedGroups = getAllowedGroupsFromGroupmanagement(ureq.getIdentity()); + coachedGroups = getAllowedGroupsFromGroupmanagement(ureq.getIdentity()); + + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, true); + repoTutor = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(getIdentity(), re.getTutorGroup()); // preload the assessment cache to speed up everything as background thread // the thread will terminate when finished @@ -269,7 +276,8 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea //fill the user list for the this.mode = MODE_USERFOCUS; this.identitiesList = getAllIdentitisFromGroupmanagement(); - doSimpleUserChoose(ureq, this.identitiesList); + //fxdiff FXOLAT-108: improve results table of tests + doUserChooseWithData(ureq, identitiesList, null, null); GenericTreeModel menuTreeModel = (GenericTreeModel)menuTree.getTreeModel(); TreeNode userNode = menuTreeModel.findNodeByUserObject(CMD_USERFOCUS); @@ -309,7 +317,8 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea } else if (cmd.equals(CMD_USERFOCUS)) { this.mode = MODE_USERFOCUS; this.identitiesList = getAllIdentitisFromGroupmanagement(); - doSimpleUserChoose(ureq, this.identitiesList); + //fxdiff FXOLAT-108: improve results table of tests + doUserChooseWithData(ureq, identitiesList, null, null); } else if (cmd.equals(CMD_GROUPFOCUS)) { this.mode = MODE_GROUPFOCUS; doGroupChoose(ureq); @@ -329,7 +338,15 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea } else if (source == backLinkGC){ setContent(nodeListCtr.getInitialComponent()); } else if (source == backLinkUC){ - setContent(groupChoose); + if((repoTutor && coachedGroups.isEmpty()) || (callback.mayAssessAllUsers() || callback.mayViewAllUsersAssessments())) { + if(mode == MODE_GROUPFOCUS) { + setContent(groupListCtr.getInitialComponent()); + } else { + setContent(nodeListCtr.getInitialComponent()); + } + } else { + setContent(groupChoose); + } } else if (source == showAllCourseNodesButton) { enableFilteringCourseNodes(false); } else if (source == filterCourseNodesButton) { @@ -436,7 +453,12 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea CourseNode node = course.getRunStructure().getNode((String) nodeData.get(AssessmentHelper.KEY_IDENTIFYER)); this.currentCourseNode = (AssessableCourseNode) node; // cast should be save, only assessable nodes are selectable - doGroupChoose(ureq); + if((repoTutor && coachedGroups.isEmpty()) || (callback.mayAssessAllUsers() || callback.mayViewAllUsersAssessments())) { + identitiesList = getAllIdentitisFromGroupmanagement(); + doUserChooseWithData(ureq, this.identitiesList, null, currentCourseNode); + } else { + doGroupChoose(ureq); + } } } else if (event.equals(TableController.EVENT_FILTER_SELECTED)) { this.currentCourseNode = (AssessableCourseNode) nodeListCtr.getActiveFilter(); @@ -559,13 +581,15 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea /** * @return List of all course participants */ - List<Identity> getAllIdentitisFromGroupmanagement() { + /*List<Identity> getAllIdentitisFromGroupmanagement() { List<Identity> allUsersList = new ArrayList<Identity>(); BaseSecurity secMgr = BaseSecurityManager.getInstance(); Iterator<BusinessGroup> iter = this.coachedGroups.iterator(); + List<SecurityGroup> secGroups = new ArrayList<SecurityGroup>(); while (iter.hasNext()) { BusinessGroup group = iter.next(); SecurityGroup secGroup = group.getPartipiciantGroup(); + secGroups.add(secGroup); List<Identity> identities = secMgr.getIdentitiesOfSecurityGroup(secGroup); for (Iterator<Identity> identitiyIter = identities.iterator(); identitiyIter.hasNext();) { Identity identity = identitiyIter.next(); @@ -575,6 +599,48 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea } } } + + List<Long> idKeys = secMgr.getIdentitiesOfSecurityGroups(secGroups); + System.out.println(); + + //fxdiff VCRP-1,2: access control of resources + if((repoTutor && coachedGroups.isEmpty()) || (callback.mayAssessAllUsers() || callback.mayViewAllUsersAssessments())) { + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, false); + if(re.getParticipantGroup() != null) { + for (Identity identity : secMgr.getIdentitiesOfSecurityGroup(re.getParticipantGroup())) { + if (!PersistenceHelper.listContainsObjectByKey(allUsersList, identity)) { + allUsersList.add(identity); + } + } + } + } + + return allUsersList; + }*/ + + List<Identity> getAllIdentitisFromGroupmanagement() { + List<SecurityGroup> secGroups = new ArrayList<SecurityGroup>(); + for (BusinessGroup group: coachedGroups) { + secGroups.add(group.getPartipiciantGroup()); + } + + BaseSecurity secMgr = BaseSecurityManager.getInstance(); + List<Identity> usersList = secMgr.getIdentitiesOfSecurityGroups(secGroups); + Set<Identity> smashDuplicates = new HashSet<Identity>(usersList); + List<Identity> allUsersList = new ArrayList<Identity>(usersList); + + //fxdiff VCRP-1,2: access control of resources + if((repoTutor && coachedGroups.isEmpty()) || (callback.mayAssessAllUsers() || callback.mayViewAllUsersAssessments())) { + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, false); + if(re.getParticipantGroup() != null) { + for (Identity identity : secMgr.getIdentitiesOfSecurityGroup(re.getParticipantGroup())) { + if (!smashDuplicates.contains(identity)) { + allUsersList.add(identity); + } + } + } + } + return allUsersList; } @@ -605,6 +671,8 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea removeAsListenerAndDispose(groupListCtr); TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setTableEmptyMessage(translate("groupchoose.nogroups")); + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setPreferencesOffered(true, "assessmentGroupList"); groupListCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator()); listenTo(groupListCtr); groupListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.group.name", 0, CMD_CHOOSE_GROUP, ureq.getLocale())); @@ -644,7 +712,8 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea private void doUserChooseWithData(UserRequest ureq, List<Identity> identities, BusinessGroup group, AssessableCourseNode courseNode) { ICourse course = CourseFactory.loadCourse(ores); - if (mode == MODE_GROUPFOCUS) { + //fxdiff FXOLAT-108: improve results table of tests + if (mode == MODE_GROUPFOCUS || mode == MODE_USERFOCUS) { this.nodeFilters = addAssessableNodesToList(course.getRunStructure().getRootNode(), group); if (courseNode == null && this.nodeFilters.size() > 0) { this.currentCourseNode = (AssessableCourseNode) this.nodeFilters.get(0); @@ -655,8 +724,14 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea removeAsListenerAndDispose(userListCtr); TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setTableEmptyMessage(translate("userchoose.nousers")); + if(mode == MODE_USERFOCUS) { + tableConfig.setPreferencesOffered(true, "assessmentSimpleUserList"); + } else if(mode == MODE_GROUPFOCUS){ + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setPreferencesOffered(true, "assessmentGroupUsersNode"); + } - if (mode == MODE_GROUPFOCUS) { + if (mode == MODE_GROUPFOCUS || mode == MODE_USERFOCUS) { userListCtr = new TableController(tableConfig, ureq, getWindowControl(), this.nodeFilters, courseNode, translate("nodesoverview.filter.title"), null,propertyHandlerTranslator); @@ -668,15 +743,15 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea // Wrap identities with user course environment and user score view List<AssessedIdentityWrapper> wrappedIdentities = new ArrayList<AssessedIdentityWrapper>(); for (int i = 0; i < identities.size(); i++) { - Identity identity = (Identity) identities.get(i); + Identity identity = identities.get(i); // if course node is null the wrapper will only contain the identity and no score information AssessedIdentityWrapper aiw = AssessmentHelper.wrapIdentity(identity, this.localUserCourseEnvironmentCache, course, courseNode); wrappedIdentities.add(aiw); } // Add the wrapped identities to the table data model - AssessedIdentitiesTableDataModel tdm = new AssessedIdentitiesTableDataModel(wrappedIdentities, courseNode, ureq.getLocale(), isAdministrativeUser); - tdm.addColumnDescriptors(userListCtr, CMD_CHOOSE_USER, mode == MODE_NODEFOCUS || mode == MODE_GROUPFOCUS); + AssessedIdentitiesTableDataModel tdm = new AssessedIdentitiesTableDataModel(wrappedIdentities, courseNode, ureq.getLocale(), isAdministrativeUser, mode == MODE_USERFOCUS); + tdm.addColumnDescriptors(userListCtr, CMD_CHOOSE_USER, mode == MODE_NODEFOCUS || mode == MODE_GROUPFOCUS || mode == MODE_USERFOCUS); userListCtr.setTableDataModel(tdm); @@ -703,7 +778,9 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea // set main vc to userchoose setContent(userChoose); } - + + //fxdiff FXOLAT-108: improve results table of tests + /* private void doSimpleUserChoose(UserRequest ureq, List<Identity> identities) { // Init table headers removeAsListenerAndDispose(userListCtr); @@ -721,6 +798,7 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea // set main vc to userchoose setContent(userChoose); } + */ private void doNodeChoose(UserRequest ureq) { ICourse course = CourseFactory.loadCourse(ores); @@ -730,18 +808,41 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea tableConfig.setTableEmptyMessage(translate("nodesoverview.nonodes")); tableConfig.setDownloadOffered(false); tableConfig.setColumnMovingOffered(false); - tableConfig.setSortingEnabled(false); + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setSortingEnabled(true); tableConfig.setDisplayTableHeader(true); tableConfig.setDisplayRowCount(false); tableConfig.setPageingEnabled(false); + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setPreferencesOffered(true, "assessmentNodeList"); nodeListCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator()); listenTo(nodeListCtr); + + //fxdiff VCRP-4: assessment overview with max score + final IndentedNodeRenderer nodeRenderer = new IndentedNodeRenderer() { + @Override + public boolean isIndentationEnabled() { + return nodeListCtr.getTableSortAsc() && nodeListCtr.getTableSortCol() == 0; + } + }; + // table columns - nodeListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.node", 0, - null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new IndentedNodeRenderer())); - nodeListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select", 1, - CMD_SELECT_NODE, ureq.getLocale())); + nodeListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.node", 0, + null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, nodeRenderer){ + @Override + //fxdiff VCRP-4: assessment overview with max score + public int compareTo(int rowa, int rowb) { + //the order is already ok + return rowa - rowb; + } + }); + //fxdiff VCRP-4: assessment overview with max score + nodeListCtr.addColumnDescriptor(false, new CustomRenderColumnDescriptor("table.header.min", 2, null, ureq.getLocale(), + ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + nodeListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.max", 3, null, ureq.getLocale(), + ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + nodeListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select", 1, CMD_SELECT_NODE, ureq.getLocale())); // get list of course node data and populate table data model CourseNode rootNode = course.getRunStructure().getRootNode(); @@ -811,6 +912,17 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea || assessableCourseNode.hasCommentConfigured()) { hasDisplayableValuesConfigured = true; } + + //fxdiff VCRP-4: assessment overview with max score + if(assessableCourseNode.hasScoreConfigured()) { + if(!(courseNode instanceof STCourseNode)) { + Float min = assessableCourseNode.getMinScoreConfiguration(); + nodeData.put(AssessmentHelper.KEY_MIN, min); + Float max = assessableCourseNode.getMaxScoreConfiguration(); + nodeData.put(AssessmentHelper.KEY_MAX, max); + } + } + if (assessableCourseNode.isEditableConfigured()) { // Assessable course nodes are selectable when they are aditable nodeData.put(AssessmentHelper.KEY_SELECTABLE, Boolean.TRUE); @@ -885,14 +997,16 @@ AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourcea if ( (courseNode == null) || ( group == null ) ) { return true; } - if (getGroupIdentitiesFromGroupmanagement(group).size()==0) { + BaseSecurity secMgr = BaseSecurityManager.getInstance(); + List<Identity> identities = secMgr.getIdentitiesOfSecurityGroup(group.getPartipiciantGroup(), 0, 1); + if (identities.isEmpty()) { // group has no participant, can not evalute return false; } ICourse course = CourseFactory.loadCourse(ores); // check if course node is visible for group // get first identity to use this identity for condition interpreter - Identity identity = getGroupIdentitiesFromGroupmanagement(group).get(0); + Identity identity = identities.get(0); IdentityEnvironment identityEnvironment = new IdentityEnvironment(); identityEnvironment.setIdentity(identity); UserCourseEnvironment uce = new UserCourseEnvironmentImpl(identityEnvironment, course.getCourseEnvironment()); diff --git a/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java b/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java index 3c1903793fc72bef4ec2832a6ed8b996f3fe11fe..162e0651a90205d5b145939654658b67b38ca5a7 100644 --- a/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java +++ b/src/main/java/org/olat/course/assessment/IdentityAssessmentOverviewController.java @@ -188,11 +188,14 @@ public class IdentityAssessmentOverviewController extends BasicController { TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setDownloadOffered(false); tableConfig.setColumnMovingOffered(false); - tableConfig.setSortingEnabled(false); + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setSortingEnabled(true); tableConfig.setDisplayTableHeader(true); tableConfig.setDisplayRowCount(false); tableConfig.setPageingEnabled(false); tableConfig.setTableEmptyMessage(translate("nodesoverview.emptylist")); + //fxdiff VCRP-4: assessment overview with max score + tableConfig.setPreferencesOffered(true, "assessmentIdentityNodeList"); removeAsListenerAndDispose(tableFilterCtr); if (allowTableFiltering) { @@ -204,16 +207,42 @@ public class IdentityAssessmentOverviewController extends BasicController { } listenTo(tableFilterCtr); + //fxdiff VCRP-4: assessment overview with max score + final IndentedNodeRenderer nodeRenderer = new IndentedNodeRenderer() { + @Override + public boolean isIndentationEnabled() { + return tableFilterCtr.getTableSortAsc() && tableFilterCtr.getTableSortCol() == 0; + } + }; + // table columns tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.node", 0, null, - ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new IndentedNodeRenderer())); + ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, nodeRenderer){ + @Override + //fxdiff VCRP-4: assessment overview with max score + public int compareTo(int rowa, int rowb) { + return rowa - rowb; + } + }); tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.details",1, null, ureq.getLocale())); tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.attempts", 2, null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT)); - tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.header.score", 3, null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT)); + tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.score", 3, null, ureq.getLocale(), + ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + //fxdiff VCRP-4: assessment overview with max score + tableFilterCtr.addColumnDescriptor(false, new CustomRenderColumnDescriptor("table.header.min", 6, null, ureq.getLocale(), + ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); + tableFilterCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.max", 7, null, ureq.getLocale(), + ColumnDescriptor.ALIGNMENT_RIGHT, new ScoreCellRenderer())); tableFilterCtr.addColumnDescriptor(new BooleanColumnDescriptor("table.header.passed", 4, translate("passed.true"), translate("passed.false"))); // node selection only available if configured if (nodesSelectable) { - tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select",5 ,CMD_SELECT_NODE, ureq.getLocale())); + tableFilterCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select",5 ,CMD_SELECT_NODE, ureq.getLocale()) { + @Override + //fxdiff VCRP-4: assessment overview with max score + public boolean isSortingAllowed() { + return false; + } + }); } nodesTableModel = new NodeAssessmentTableDataModel(nodesTableList, getTranslator(), nodesSelectable); tableFilterCtr.setTableDataModel(nodesTableModel); diff --git a/src/main/java/org/olat/course/assessment/IndentedNodeRenderer.java b/src/main/java/org/olat/course/assessment/IndentedNodeRenderer.java index e6517b52a11b31320a8ed2dc6474fc816b71527b..931019d3213d511d9fad7f932596c7ca5b63aed8 100644 --- a/src/main/java/org/olat/course/assessment/IndentedNodeRenderer.java +++ b/src/main/java/org/olat/course/assessment/IndentedNodeRenderer.java @@ -43,6 +43,8 @@ import org.olat.course.nodes.CourseNodeFactory; public class IndentedNodeRenderer implements CustomCellRenderer { private static final String INDENT = " "; + //fxdiff VCRP-4: assessment overview with max score + private boolean indentationEnabled = true; /** * @@ -50,6 +52,14 @@ public class IndentedNodeRenderer implements CustomCellRenderer { public IndentedNodeRenderer() { super(); } + + public boolean isIndentationEnabled() { + return indentationEnabled; + } + + public void setIndentationEnabled(boolean indentationEnabled) { + this.indentationEnabled = indentationEnabled; + } /** * @see org.olat.core.gui.components.table.CustomCellRenderer#render(org.olat.core.gui.render.StringOutput, org.olat.core.gui.render.Renderer, java.lang.Object, java.util.Locale, int, java.lang.String) @@ -63,7 +73,12 @@ public class IndentedNodeRenderer implements CustomCellRenderer { String title = (String) nodeData.get(AssessmentHelper.KEY_TITLE_SHORT); String altText = (String) nodeData.get(AssessmentHelper.KEY_TITLE_LONG); - appendIndent(sb,indent); + //fxdiff VCRP-4: assessment overview with max score + if(isIndentationEnabled()) { + Integer indentation = (Integer) nodeData.get(AssessmentHelper.KEY_INDENT); + appendIndent(sb,indentation); + } + sb.append("<span class=\"b_with_small_icon_left ").append(cssClass); if (altText != null) { sb.append("\" title= \"").append(StringEscapeUtils.escapeHtml(altText)); diff --git a/src/main/java/org/olat/course/assessment/NodeAssessmentTableDataModel.java b/src/main/java/org/olat/course/assessment/NodeAssessmentTableDataModel.java index 65073b0f4ca0dc957b888a04adb99cbdad7ef1ce..4bbb1456b24ae3147887d531e75275cc78e67a34 100644 --- a/src/main/java/org/olat/course/assessment/NodeAssessmentTableDataModel.java +++ b/src/main/java/org/olat/course/assessment/NodeAssessmentTableDataModel.java @@ -73,7 +73,8 @@ public class NodeAssessmentTableDataModel extends DefaultTableDataModel { case 2: return nodeData.get(AssessmentHelper.KEY_ATTEMPTS); case 3: - return nodeData.get(AssessmentHelper.KEY_SCORE); + //fxdiff VCRP-4: assessment overview with max score + return nodeData.get(AssessmentHelper.KEY_SCORE_F); case 4: return nodeData.get(AssessmentHelper.KEY_PASSED); case 5: @@ -81,6 +82,14 @@ public class NodeAssessmentTableDataModel extends DefaultTableDataModel { Boolean courseNodeEditable = (Boolean) nodeData.get(AssessmentHelper.KEY_SELECTABLE); if (nodesSelectable && courseNodeEditable.booleanValue()) return trans.translate("select"); else return null; + case 6: + //min score + Float minScore = (Float)nodeData.get(AssessmentHelper.KEY_MIN); + return minScore == null ? null : minScore; + case 7: + //max score + Float maxScore = (Float)nodeData.get(AssessmentHelper.KEY_MAX); + return maxScore == null ? null : maxScore; default: return "error"; } diff --git a/src/main/java/org/olat/course/assessment/NodeTableDataModel.java b/src/main/java/org/olat/course/assessment/NodeTableDataModel.java index c8accfdbd7b869078f9b06773c181339c26ce14f..cdd3bf421d12a7dcf81acd3da287bc50b923bd27 100644 --- a/src/main/java/org/olat/course/assessment/NodeTableDataModel.java +++ b/src/main/java/org/olat/course/assessment/NodeTableDataModel.java @@ -73,6 +73,13 @@ public class NodeTableDataModel extends DefaultTableDataModel { Boolean courseNodeEditable = (Boolean) nodeData.get(AssessmentHelper.KEY_SELECTABLE); if (courseNodeEditable.booleanValue()) return trans.translate("select"); else return null; + //fxdiff VCRP-4: assessment overview with max score + case 2: + //min score + return nodeData.get(AssessmentHelper.KEY_MIN); + case 3: + //min score + return nodeData.get(AssessmentHelper.KEY_MAX); default: return "error"; } diff --git a/src/main/java/org/olat/course/assessment/ScoreCellRenderer.java b/src/main/java/org/olat/course/assessment/ScoreCellRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..154c9c73bc92e5e11db9d43a5047998aa2eec654 --- /dev/null +++ b/src/main/java/org/olat/course/assessment/ScoreCellRenderer.java @@ -0,0 +1,54 @@ +/** +* 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) 2008 frentix GmbH, Switzerland<br> +* <p> +*/ + +package org.olat.course.assessment; + +import java.util.Locale; + +import org.olat.core.gui.components.table.CustomCellRenderer; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.util.StringHelper; + +/** + * + * Description:<br> + * This render apply a format to the score + * + * <P> + * Initial Date: 14 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-4: assessment overview with max score +public class ScoreCellRenderer implements CustomCellRenderer { + + @Override + public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) { + if(val instanceof Float) { + Float score = (Float)val; + String scoreStr = AssessmentHelper.getRoundedScore(score); + if(StringHelper.containsNonWhitespace(scoreStr)) { + sb.append(scoreStr); + } + } else if(val instanceof String) { + sb.append((String)val); + } + } +} diff --git a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties index a46baa732b77be16e24483d2c66bc6fbb79d53fd..53a906bd837b6df5c9866736a6b12c55da74067b 100644 --- a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_de.properties @@ -57,7 +57,7 @@ form.cut=Punkteschwelle f\u00FCr bestanden form.error.nointeger=Ung\u00FCltige Zahl. Es muss eine Zahl eingegeben werden. form.error.passedUndefined=Bestanden muss auf Ja oder Nein gestellt werden form.error.scoreOutOfRange=Punkte nicht zwischen Punkteminimum/-maximum oder nicht zwischen 10000/-10000 -form.error.wrongFloat=Falsches Zahlenformat. Beispiele\: 15.0, 5.5, 10 +form.error.wrongFloat=Falsches Zahlenformat. Beispiele\: 15.0, 5.5, 10, 15,0 form.max=Punktemaximum form.min=Punkteminimum form.passed=Bestanden @@ -132,6 +132,8 @@ table.header.delete=L\u00F6schen table.header.details=Details table.header.details.ta=Aufgabe table.header.launchcourse=Kurs starten +table.header.min=Min. +table.header.max=Max. table.header.name=Benutzername table.header.node=Kursbaustein table.header.passed=Bestanden diff --git a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_en.properties index be360f41ab21a2c227d8a451dc6087de539d0c97..787b552c0d1af90a53d0a8fdf4f05464a99aae94 100644 --- a/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/assessment/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 11:02:45 CET 2011 +#Mon May 16 17:10:14 CEST 2011 action.choose=Select artefact.title=Evidence of achievement for course {0} assessmentLock=This assessment is currently modified by user {0} ({1}) and is therefore locked. Please try again later. @@ -140,6 +140,8 @@ table.header.delete=Delete table.header.details=Details table.header.details.ta=Task table.header.launchcourse=Start course +table.header.max=Max. +table.header.min=Min. table.header.name=User name table.header.node=Course element table.header.passed=Passed diff --git a/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java new file mode 100644 index 0000000000000000000000000000000000000000..92792defc6607c116545c774cb5bc20523c642fd --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java @@ -0,0 +1,434 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.image.ImageComponent; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.translator.PackageTranslator; +import org.olat.core.logging.AssertException; +import org.olat.core.util.ArrayHelper; +import org.olat.core.util.FileUtils; +import org.olat.core.util.IImageHelper; +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.VFSItemSuffixFilter; +import org.olat.course.config.CourseConfig; +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SpecialAttributeFormItemHandler; +import org.olat.course.config.ui.courselayout.elements.AbstractLayoutElement; +import org.olat.course.run.environment.CourseEnvironment; + +/** + * Description:<br> + * Present different templates for course-layouts and let user generate his own. + * + * <P> + * Initial Date: 01.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class CourseLayoutGeneratorController extends FormBasicController { + + private static final String ELEMENT_ATTRIBUTE_DELIM = "::"; + private static final String PREVIEW_IMAGE_NAME = "preview.png"; + private CourseConfig courseConfig; + private SingleSelection styleSel; + private FileElement logoUpl; + private FormLayoutContainer previewImgFlc; + private CourseEnvironment courseEnvironment; + private FormLayoutContainer styleFlc; + private CustomConfigManager customCMgr; + private LinkedHashMap<String, Map<String, FormItem>> guiWrapper; + private Map<String, Map<String, Object>> persistedCustomConfig; + private FormLayoutContainer logoImgFlc; + private FormLink logoDel; + private boolean elWithErrorExists = false; + + public CourseLayoutGeneratorController(UserRequest ureq, WindowControl wControl, CourseConfig courseConfig, + CourseEnvironment courseEnvironment) { + super(ureq, wControl); + + this.courseConfig = courseConfig; + this.courseEnvironment = courseEnvironment; + customCMgr = (CustomConfigManager) CoreSpringFactory.getBean("courseConfigManager"); + // stack the translator to get attribs/elements + PackageTranslator pt = new PackageTranslator(AbstractLayoutAttribute.class.getPackage().getName(), ureq.getLocale(), getTranslator()); + pt = new PackageTranslator(AbstractLayoutElement.class.getPackage().getName(), ureq.getLocale(), pt); + setTranslator(pt); + persistedCustomConfig = customCMgr.getCustomConfig(courseEnvironment); + 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) + */ + @SuppressWarnings("unused") + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("tab.layout.title"); + + ArrayList<String> keys = new ArrayList<String>(); + ArrayList<String> vals = new ArrayList<String>(); + ArrayList<String> csss = new ArrayList<String>(); + + String actualCSSSettings = courseConfig.getCssLayoutRef(); + + // add a default option + keys.add(CourseLayoutHelper.CONFIG_KEY_DEFAULT); + vals.add(translate("course.layout.default")); + csss.add(""); + + // check for old legacy template, only available if yet one set + if(actualCSSSettings.startsWith("/") && actualCSSSettings.lastIndexOf("/") == 0) { + keys.add(actualCSSSettings); + vals.add(translate("course.layout.legacy", actualCSSSettings)); + csss.add(""); + } + + // add css from hidden coursecss-folder + VFSContainer coursecssCont = (VFSContainer) courseEnvironment.getCourseFolderContainer().resolve(CourseLayoutHelper.COURSEFOLDER_CSS_BASE); + if (coursecssCont != null) { + coursecssCont.setDefaultItemFilter(new VFSItemSuffixFilter(new String[]{"css"})); + List<VFSItem> coursecssStyles = coursecssCont.getItems(); + if (coursecssStyles != null) { + for (VFSItem vfsItem : coursecssStyles) { + keys.add(CourseLayoutHelper.COURSEFOLDER_CSS_BASE + "/" + vfsItem.getName()); + vals.add(translate("course.layout.legacy", vfsItem.getName())); + csss.add(""); + } + } + } + + // get the olat-wide templates + List<VFSItem> templates = CourseLayoutHelper.getCourseThemeTemplates(); + if (templates != null) { + for (VFSItem vfsItem : templates) { + if (CourseLayoutHelper.isCourseThemeFolderValid((VFSContainer) vfsItem)){ + keys.add(CourseLayoutHelper.CONFIG_KEY_TEMPLATE + vfsItem.getName()); + String name = translate("course.layout.template", vfsItem.getName()); + vals.add(name); + csss.add(""); + } + } + } + + // get the predefined template for this course if any + VFSItem predefCont = courseEnvironment.getCourseBaseContainer().resolve(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER + "/" + CourseLayoutHelper.CONFIG_KEY_PREDEFINED); + if (predefCont != null && CourseLayoutHelper.isCourseThemeFolderValid((VFSContainer) predefCont)) { + keys.add(CourseLayoutHelper.CONFIG_KEY_PREDEFINED); + vals.add(translate("course.layout.predefined")); + csss.add(""); + } + + // add option for customizing + keys.add(CourseLayoutHelper.CONFIG_KEY_CUSTOM); + vals.add(translate("course.layout.custom")); + csss.add(""); + + String[] theKeys = ArrayHelper.toArray(keys); + String[] theValues = ArrayHelper.toArray(vals); + String[] theCssClasses = ArrayHelper.toArray(csss); + + styleSel = uifactory.addDropdownSingleselect("course.layout.selector", formLayout, theKeys, theValues, theCssClasses); + styleSel.addActionListener(this, FormEvent.ONCHANGE); + if (keys.contains(actualCSSSettings)){ + styleSel.select(actualCSSSettings, true); + } else { + styleSel.select(CourseLayoutHelper.CONFIG_KEY_DEFAULT, true); + } + + previewImgFlc = FormLayoutContainer.createCustomFormLayout("preview.image", getTranslator(), velocity_root + "/image.html"); + formLayout.add(previewImgFlc); + previewImgFlc.setLabel("preview.image.label", null); + refreshPreviewImage(actualCSSSettings); + + logoImgFlc = FormLayoutContainer.createCustomFormLayout("logo.image", getTranslator(), velocity_root + "/image.html"); + formLayout.add(logoImgFlc); + logoImgFlc.setLabel("logo.image.label", null); + refreshLogoImage(); + + // offer upload for 2nd logo + logoUpl = uifactory.addFileElement("upload.second.logo", formLayout); + logoUpl.addActionListener(this, FormEvent.ONCHANGE); + Set<String> mimeTypes = new HashSet<String>(); + mimeTypes.add("image/*"); + logoUpl.limitToMimeType(mimeTypes, "logo.file.type.error", null); + logoUpl.setMaxUploadSizeKB(2048, "logo.size.error", null); + + // prepare the custom layouter + styleFlc = FormLayoutContainer.createCustomFormLayout("style", getTranslator(), velocity_root + "/style.html"); + formLayout.add(styleFlc); + styleFlc.setLabel(null, null); + enableDisableCustom(CourseLayoutHelper.CONFIG_KEY_CUSTOM.equals(actualCSSSettings)); + + uifactory.addFormSubmitButton("course.layout.save", formLayout); + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest, org.olat.core.gui.components.form.flexible.FormItem, org.olat.core.gui.components.form.flexible.impl.FormEvent) + */ + @Override + protected void formInnerEvent(@SuppressWarnings("unused") UserRequest ureq, FormItem source, FormEvent event) { + if (source == styleSel) { + String selection = styleSel.getSelectedKey(); + if (CourseLayoutHelper.CONFIG_KEY_CUSTOM.equals(selection)) { + enableDisableCustom(true); + } else { + enableDisableCustom(false); + } + refreshPreviewImage(selection); // in any case! + } else if (source == logoUpl && event.wasTriggerdBy(FormEvent.ONCHANGE)) { + if (logoUpl.isUploadSuccess()) { + File newFile = logoUpl.getUploadFile(); + String newFilename = logoUpl.getUploadFileName(); + boolean isValidFileType = newFilename.toLowerCase().matches(".*[.](png|jpg|jpeg|gif)"); + if (!isValidFileType) { + logoUpl.setErrorKey("logo.file.type.error", null); + } else { + logoUpl.clearError(); + } + + if (processUploadedImage(newFile)){ + logoUpl.reset(); + showInfo("logo.upload.success"); + refreshLogoImage(); + } else { + showError("logo.upload.error"); + } + } + } else if (source.getName().contains(ELEMENT_ATTRIBUTE_DELIM)){ + // some selections changed, refresh to get new preview + prepareStyleEditor(compileCustomConfigFromGuiWrapper()); + } else if (source == logoDel){ + VFSItem logo = (VFSItem) logoDel.getUserObject(); + logo.delete(); + refreshLogoImage(); + } + } + + private void enableDisableCustom(boolean onOff){ + if (onOff) prepareStyleEditor(persistedCustomConfig); + styleFlc.setVisible(onOff); + logoUpl.setVisible(onOff); + logoImgFlc.setVisible(onOff); + } + + // process uploaded file according to image size and persist in <course>/layout/logo.xy + private boolean processUploadedImage(File image){ + int height = 0; + int width = 0; + String size[] = customCMgr.getImageSize(image); + if (size != null) { + width = Integer.parseInt(size[0]); + height = Integer.parseInt(size[1]); + } else return false; + // target file: + String fileType = logoUpl.getUploadFileName().substring(logoUpl.getUploadFileName().lastIndexOf(".")); + VFSContainer base = (VFSContainer) courseEnvironment.getCourseBaseContainer().resolve(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER); + if (base == null) { + base = courseEnvironment.getCourseBaseContainer().createChildContainer(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER); + } + VFSContainer customBase = (VFSContainer) base.resolve("/" + CourseLayoutHelper.CONFIG_KEY_CUSTOM); + if (customBase==null) { + customBase = base.createChildContainer(CourseLayoutHelper.CONFIG_KEY_CUSTOM); + } + if (customBase.resolve("logo" + fileType) != null) customBase.resolve("logo" + fileType).delete(); + VFSLeaf targetFile = customBase.createChildLeaf("logo" + fileType); + int maxHeight = CourseLayoutHelper.getLogoMaxHeight(); + int maxWidth = CourseLayoutHelper.getLogoMaxWidth(); + if (height > maxHeight || width > maxWidth){ + // scale image + try { + IImageHelper helper = CourseLayoutHelper.getImageHelperToUse(); + helper.scaleImage(new FileInputStream(image), targetFile, maxWidth, maxHeight); + } catch (FileNotFoundException e) { + logError("could not find to be scaled image", e); + return false; + } + } else { + // only persist without scaling + try { + FileUtils.copy(new FileInputStream(image), targetFile.getOutputStream(false)); + } catch (FileNotFoundException e) { + logError("Problem reading uploaded image to copy", e); + return false; + } + } + return true; + } + + + private void refreshPreviewImage(String template) { + VFSContainer baseFolder = CourseLayoutHelper.getThemeBaseFolder(courseEnvironment, template); + if (baseFolder != null) { + VFSItem preview = baseFolder.resolve("/" + PREVIEW_IMAGE_NAME); + if (preview != null) { + ImageComponent image = new ImageComponent("preview"); + previewImgFlc.setVisible(true); + previewImgFlc.put("preview", image); + VFSMediaResource prevImage = new VFSMediaResource((VFSLeaf) preview); + image.setMediaResource(prevImage); + image.setMaxWithAndHeightToFitWithin(300, 300); + return; + } + } + previewImgFlc.setVisible(false); + previewImgFlc.remove(previewImgFlc.getComponent("preview")); + } + + private void refreshLogoImage(){ + VFSContainer baseFolder = CourseLayoutHelper.getThemeBaseFolder(courseEnvironment, CourseLayoutHelper.CONFIG_KEY_CUSTOM); + VFSItem logo = customCMgr.getLogoItem(baseFolder); + if (logo != null) { + ImageComponent image = new ImageComponent("preview"); + logoImgFlc.setVisible(true); + logoImgFlc.put("preview", image); + VFSMediaResource prevImage = new VFSMediaResource((VFSLeaf) logo); + image.setMediaResource(prevImage); + image.setMaxWithAndHeightToFitWithin(300, 300); + logoDel = uifactory.addFormLink("logo.delete", logoImgFlc, Link.BUTTON_XSMALL); + logoDel.setUserObject(logo); + return; + } + logoImgFlc.setVisible(false); + logoImgFlc.remove(logoImgFlc.getComponent("preview")); + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) + */ + @Override + protected void formOK(UserRequest ureq) { + String selection = styleSel.getSelectedKey(); + courseConfig.setCssLayoutRef(selection); + + if(CourseLayoutHelper.CONFIG_KEY_CUSTOM.equals(selection)){ + Map<String, Map<String, Object>> customConfig = compileCustomConfigFromGuiWrapper(); + customCMgr.saveCustomConfigAndCompileCSS(customConfig, courseEnvironment); + persistedCustomConfig = customConfig; + if (!elWithErrorExists) prepareStyleEditor(customConfig); + } + + // inform course-settings-dialog about changes: + fireEvent(ureq, Event.CHANGED_EVENT); + } + + private Map<String, Map<String, Object>> compileCustomConfigFromGuiWrapper(){ + // get config from wrapper-object + elWithErrorExists = false; + Map<String, Map<String, Object>> customConfig = new HashMap<String, Map<String, Object>>(); + for (Iterator<Entry<String, Map<String, FormItem>>> iterator = guiWrapper.entrySet().iterator(); iterator.hasNext();) { + Entry<String, Map<String, FormItem>> type = iterator.next(); + String cIdent = type.getKey(); + Map<String, Object> elementConfig = new HashMap<String, Object>(); + Map<String, FormItem> element = type.getValue(); + for (Entry<String, FormItem> entry : element.entrySet()) { + String attribName = entry.getKey(); + if (!attribName.equals(PreviewLA.IDENTIFIER)){ // exclude preview + FormItem foItem = entry.getValue(); + String value = ""; + if (foItem instanceof SingleSelection) { + value = ((SingleSelection)foItem).isOneSelected() ? ((SingleSelection)foItem).getSelectedKey() : ""; + } else if (foItem.getUserObject() != null && foItem.getUserObject() instanceof SpecialAttributeFormItemHandler) { + // enclosed item + SpecialAttributeFormItemHandler specHandler = (SpecialAttributeFormItemHandler) foItem.getUserObject(); + value = specHandler.getValue(); + if (specHandler.hasError()) { + elWithErrorExists = true; + } + } else { + throw new AssertException("implement a getValue for this FormItem to get back a processable value."); + } + elementConfig.put(attribName, value); + } + } + customConfig.put(cIdent, elementConfig); + } + return customConfig; + } + + + private void prepareStyleEditor(Map<String, Map<String, Object>> customConfig){ + guiWrapper = new LinkedHashMap<String, Map<String, FormItem>>(); //keep config order + + List<AbstractLayoutElement> allElements = customCMgr.getAllAvailableElements(); + List<AbstractLayoutAttribute> allAttribs = customCMgr.getAllAvailableAttributes(); + styleFlc.contextPut("allAttribs", allAttribs); + styleFlc.setUserObject(this); // needed reference to get listener back. + + for (AbstractLayoutElement abstractLayoutElement : allElements) { + String elementType = abstractLayoutElement.getLayoutElementTypeName(); + Map<String, Object> elConf = customConfig.get(elementType); + AbstractLayoutElement concreteElmt = abstractLayoutElement.createInstance(elConf); + + HashMap<String, FormItem> elAttribGui = new HashMap<String, FormItem>(); + + List<AbstractLayoutAttribute> attributes = concreteElmt.getAvailableAttributes(); + for (AbstractLayoutAttribute attrib : attributes) { + String compName = elementType + ELEMENT_ATTRIBUTE_DELIM + attrib.getLayoutAttributeTypeName(); + FormItem fi = attrib.getFormItem(compName, styleFlc); + fi.addActionListener(this, FormEvent.ONCHANGE); + elAttribGui.put(attrib.getLayoutAttributeTypeName(), fi); + } + guiWrapper.put(elementType, elAttribGui); + } + styleFlc.contextPut("guiWrapper", guiWrapper); + } + + + /** + * @see org.olat.core.gui.control.DefaultController#doDispose() + */ + @Override + protected void doDispose() { + // nothing to dispose + } + + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutHelper.java b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..d130f82ab42fff9a444cab6b2893d9b73698895b --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutHelper.java @@ -0,0 +1,271 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.olat.core.gui.components.htmlheader.jscss.CustomCSS; +import org.olat.core.helpers.Settings; +import org.olat.core.util.IImageHelper; +import org.olat.core.util.StringHelper; +import org.olat.core.util.UserSession; +import org.olat.core.util.WebappHelper; +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.filters.VFSItemFilter; +import org.olat.course.config.CourseConfig; +import org.olat.course.run.environment.CourseEnvironment; + +/** + * Description:<br> + * some static helpers for the course-layout-generator + * + * <P> + * Initial Date: 01.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class CourseLayoutHelper { + + public static final String COURSEFOLDER_CSS_BASE = "/courseCSS"; + private static int logoMaxHeight; + private static int logoMaxWidth; + private static IImageHelper imageHelperToUse; + + public static final String CONFIG_KEY_LEGACY = "legacy"; + public static final String CONFIG_KEY_DEFAULT = CourseConfig.VALUE_EMPTY_CSS_FILEREF; + public static final String CONFIG_KEY_TEMPLATE = "template::"; + public static final String CONFIG_KEY_PREDEFINED = "predefined"; + public static final String CONFIG_KEY_CUSTOM = "custom"; + + public static final String LAYOUT_COURSE_SUBFOLDER = "/layout"; + private static final String IFRAME_CSS = "/iframe.css"; + private static final String MAIN_CSS = "/main.css"; + + /** + * Array holding the folder-names of the blacklisted course-layouts + * (set by spring) + * + */ + private static List<String> layoutBlacklist; + + CourseLayoutHelper(){ + // + } + +/** + * get configured path for this courseEnvironment + * @param courseEnvironment + * @return + */ + public static VFSContainer getCSSBaseFolder(CourseEnvironment courseEnvironment) { + CourseConfig courseConfig = courseEnvironment.getCourseConfig(); + String cssSet = courseConfig.getCssLayoutRef(); + return getThemeBaseFolder(courseEnvironment, cssSet); + } + + /** + * get path according to type of theme + * @param courseEnvironment + * @param cssSet + * @return + */ + public static VFSContainer getThemeBaseFolder(CourseEnvironment courseEnvironment, String cssSet) { + VFSContainer courseBase = courseEnvironment.getCourseBaseContainer(); + + // the hidden-dir CSS in "/coursecss/xy.css" + if (StringHelper.containsNonWhitespace(cssSet) && cssSet.contains(COURSEFOLDER_CSS_BASE) && cssSet.startsWith("/") && cssSet.lastIndexOf("/") > 0){ + if (courseEnvironment.getCourseFolderContainer().resolve(COURSEFOLDER_CSS_BASE) != null) { + return courseEnvironment.getCourseFolderContainer(); + } else return null; + } else if (!StringHelper.containsNonWhitespace(cssSet) || cssSet.startsWith("/")) { + // the old legacy format "/blibla.css" + return (VFSContainer) courseBase.resolve("coursefolder"); + } else if (cssSet.equals(CONFIG_KEY_CUSTOM)) { + return (VFSContainer) courseBase.resolve(LAYOUT_COURSE_SUBFOLDER + "/custom"); + } else if (CONFIG_KEY_PREDEFINED.equals(cssSet)) { + return (VFSContainer) courseBase.resolve(LAYOUT_COURSE_SUBFOLDER + "/predefined"); + } else if (cssSet.startsWith(CONFIG_KEY_TEMPLATE)) { + String selTheme = cssSet.substring(CONFIG_KEY_TEMPLATE.length()); + + // 1. check if it's a layout from the OLAT-theme + VFSContainer themeContainer = getOLATThemeCourseLayoutFolder(); + if(themeContainer!=null){ + themeContainer = (VFSContainer) themeContainer.resolve("/"+selTheme); + return themeContainer; + } + + // 2. check if it's system-default + String staticAbsPath = WebappHelper.getContextRoot() + "/static/coursethemes/"; + File themesFile = new File(staticAbsPath + selTheme); + if(themesFile.exists() && themesFile.isDirectory()) + return new LocalFolderImpl(themesFile); + } + return null; // default was set + } + + /** + * get CustomCSS preconfigured according to choosen course theme + * @param usess + * @param courseEnvironment + * @return + */ + public static CustomCSS getCustomCSS(UserSession usess, CourseEnvironment courseEnvironment) { + VFSContainer courseContainer = getCSSBaseFolder(courseEnvironment); + CustomCSS customCSS = null; + // check for existing main.css and iframe.css + if (isCourseThemeFolderValid(courseContainer)) { + customCSS = new CustomCSS(courseContainer, MAIN_CSS, IFRAME_CSS, usess); + } else if (courseContainer!=null && courseContainer.resolve(courseEnvironment.getCourseConfig().getCssLayoutRef()) != null){ // legacy fallback + customCSS = new CustomCSS(courseContainer, courseEnvironment.getCourseConfig().getCssLayoutRef(), usess); + } + return customCSS; + } + + /** + * get a list of system wide course theme templates + * they need to be in webapp/static/coursethemes/XY + * @return + */ + public static List<VFSItem> getCourseThemeTemplates(){ + List<VFSItem> courseThemes = new ArrayList<VFSItem>(); + + // 1. add the system-defaults + String staticAbsPath = WebappHelper.getContextRoot() + "/static"; + File themesFile = new File(staticAbsPath); + VFSContainer cThemeCont = new LocalFolderImpl(themesFile); + cThemeCont = (VFSContainer) cThemeCont.resolve("/coursethemes"); + if (cThemeCont != null) { + courseThemes = cThemeCont.getItems(new VFSItemFilter() { + @Override + public boolean accept(VFSItem it) { + if (!(it instanceof VFSContainer)) return false; + if (it.getName().equals("CVS") || it.getName().equals("cvs")) return false; + return !(layoutBlacklist.contains(it.getName())); + } + }); + } + + // 2. now add the additional Templates from the current Theme + VFSContainer addThCont = CourseLayoutHelper.getOLATThemeCourseLayoutFolder(); + if (addThCont != null) { + List<VFSItem> additionalThemes = addThCont.getItems(new VFSItemFilter() { + @Override + public boolean accept(VFSItem it) { + if (!(it instanceof VFSContainer)) return false; + return (!it.getName().equals("CVS") && !it.getName().equals("cvs")); + } + }); + courseThemes.addAll(additionalThemes); + } + return courseThemes; + } + + /** + * returns the Folder with the additional courselayouts from the current + * OLAT-Theme this is e.g. : /static/themes/frentix/courselayouts/<br /> + * If no courselayouts exist in the current OLAT-Theme, null is returned! + * + * @return the courselayouts-folder or null + */ + public static VFSContainer getOLATThemeCourseLayoutFolder() { + String staticThemesPath = WebappHelper.getContextRoot() + "/static/themes/"; + File tmpDir = new File(staticThemesPath); + VFSContainer addThCont = new LocalFolderImpl(tmpDir); + return (VFSContainer) addThCont.resolve("/" + Settings.getGuiThemeIdentifyer() + "/courselayouts"); + } + + /** + * checks if the given theme base contains the needed css-files + * - main.css + * - iframe.css + * @param courseThemeBase + * @return + */ + public static boolean isCourseThemeFolderValid(VFSContainer courseThemeBase){ + if (courseThemeBase==null) return false; + return courseThemeBase.resolve(MAIN_CSS) != null && courseThemeBase.resolve(IFRAME_CSS) != null; + } + + /** + * [spring] + * @param logoMaxHeight The logoMaxHeight to set. + */ + public void setLogoMaxHeight(int logoMaxHeight) { + CourseLayoutHelper.logoMaxHeight = logoMaxHeight; + } + + /** + * [spring] + * + * @param layouts comma-separated list of folder-names (e.g. + * purple,green,blue) + */ + public void setLayoutBlacklist(String layouts){ + layoutBlacklist = Arrays.asList(layouts.split(",")); + } + + /** + * @return Returns the logoMaxHeight. + */ + public static int getLogoMaxHeight() { + return logoMaxHeight; + } + + /** + * [spring] + * @param logoMaxWidth The logoMaxWidth to set. + */ + public void setLogoMaxWidth(int logoMaxWidth) { + CourseLayoutHelper.logoMaxWidth = logoMaxWidth; + } + + /** + * @return Returns the logoMaxWidth. + */ + public static int getLogoMaxWidth() { + return logoMaxWidth; + } + + /** + * @return Returns the imageHelperToUse. + */ + public static IImageHelper getImageHelperToUse() { + return imageHelperToUse; + } + + /** + * allows to exchange the java implementation of Imagehelper. + * to use i.e. an imageMagick-instance to get better image-scaling (transparency, etc.) + * [spring] + * @param imageHelperToUse The imageHelperToUse to set. + */ + public void setImageHelperToUse(IImageHelper imageHelperToUse) { + CourseLayoutHelper.imageHelperToUse = imageHelperToUse; + } + + + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/CustomConfigManager.java b/src/main/java/org/olat/course/config/ui/courselayout/CustomConfigManager.java new file mode 100644 index 0000000000000000000000000000000000000000..08c2ce824ab45ba8b345226c8e0b0ae457bb0a6d --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/CustomConfigManager.java @@ -0,0 +1,226 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.imageio.ImageIO; + +import org.olat.core.manager.BasicManager; +import org.olat.core.util.FileUtils; +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.filters.VFSItemSuffixFilter; +import org.olat.core.util.xml.XStreamHelper; +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.elements.AbstractLayoutElement; +import org.olat.course.run.environment.CourseEnvironment; + +import com.thoughtworks.xstream.XStream; + +/** + * + * Description:<br> + * used to load and persist the custom config + * + * <P> + * Initial Date: 04.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class CustomConfigManager extends BasicManager { + + private static final String IFRAME_CSS = "iframe.css"; + private static final String MAIN_CSS = "main.css"; + private static final String CUSTOM_CONFIG_XML = "config.xml"; + private List<AbstractLayoutElement> availableLayoutElements; + private List<AbstractLayoutAttribute> availableLayoutAttributes; + + + CustomConfigManager(){ + // + } + + /** + * save the custom css configuration in a reprocessable format (map with inner map) + * also generates the two needed css-files (main / iframe) + * @param customConfig + * @param courseEnvironment + */ + public void saveCustomConfigAndCompileCSS(Map<String, Map<String, Object>> customConfig, CourseEnvironment courseEnvironment){ + VFSContainer themeBase = null; + VFSContainer base = null; + base = (VFSContainer) courseEnvironment.getCourseBaseContainer().resolve(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER); + if (base == null) { + base = courseEnvironment.getCourseBaseContainer().createChildContainer(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER); + } + themeBase = (VFSContainer) base.resolve("/" + CourseLayoutHelper.CONFIG_KEY_CUSTOM); + if (themeBase == null) { + themeBase = base.createChildContainer(CourseLayoutHelper.CONFIG_KEY_CUSTOM); + } + VFSLeaf configTarget = (VFSLeaf) themeBase.resolve(CUSTOM_CONFIG_XML); + if (configTarget == null) { + configTarget = themeBase.createChildLeaf(CUSTOM_CONFIG_XML); + } + + XStream xStream = XStreamHelper.createXStreamInstance(); + xStream.toXML(customConfig, configTarget.getOutputStream(false)); + + // compile the css-files + StringBuffer sbMain = new StringBuffer(); + StringBuffer sbIFrame = new StringBuffer(); + for (Entry<String, Map<String, Object>> iterator : customConfig.entrySet()) { + String type = iterator.getKey(); + Map<String, Object> elementConfig = iterator.getValue(); + AbstractLayoutElement configuredLayEl = createLayoutElementByType(type, elementConfig); + sbIFrame.append(configuredLayEl.getCSSForIFrame()); + sbMain.append(configuredLayEl.getCSSForMain()); + } + + // attach line for logo, if there is any to cssForMain: + appendLogoPart(sbMain, themeBase); + + VFSLeaf mainFile = (VFSLeaf) themeBase.resolve(MAIN_CSS); + if (mainFile == null) mainFile = themeBase.createChildLeaf(MAIN_CSS); + VFSLeaf iFrameFile = (VFSLeaf) themeBase.resolve(IFRAME_CSS); + if (iFrameFile == null) iFrameFile = themeBase.createChildLeaf(IFRAME_CSS); + FileUtils.save(mainFile.getOutputStream(false), sbMain.toString(), "utf-8"); + FileUtils.save(iFrameFile.getOutputStream(false), sbIFrame.toString(), "utf-8"); + } + + private void appendLogoPart(StringBuffer sb, VFSContainer themeBase) { + VFSItem vfsItem = getLogoItem(themeBase); + if (vfsItem != null) { + sb.append("#b_right_logo {\n\tbackground-image: url(").append(vfsItem.getName()).append("); \n"); + sb.append("\tbackground-position: left top; \n"); + sb.append("\tbackground-repeat: no-repeat; \n"); + LocalFileImpl leaf = (LocalFileImpl) vfsItem; + String size[] = getImageSize(leaf.getBasefile()); + sb.append("\twidth: ").append(size[0]).append("px; \n"); + sb.append("\theight: ").append(size[1]).append("px; \n"); + sb.append("\tfloat: left; \n}\n"); + sb.append("#b_logo { \n\t float: left; \n}"); + } + } + + public VFSItem getLogoItem(VFSContainer themeBase){ + if (themeBase == null) return null; + List<VFSItem> images = themeBase.getItems(new VFSItemSuffixFilter(new String[] { "gif", "jpg", "png" })); + for (VFSItem vfsItem : images) { + if (vfsItem.getName().indexOf("logo")!=-1) { + return vfsItem; + } + } return null; + } + + /** + * calculate the real size of an image + * @param image + * @return array[width, height] + */ + public String[] getImageSize(File image){ + int height = 0; + int width = 0; + try { + BufferedImage imageSrc = ImageIO.read(image); + height = imageSrc.getHeight(); + width = imageSrc.getWidth(); + } catch (IOException e) { + logError("Problem reading uploaded image", e); + return null; + } + return new String[] { String.valueOf(width), String.valueOf(height) }; + } + + /** + * load the persisted customConfig + * this is the custom.xml file in bcroot/course/<courseID>/layout/custom + * @param courseEnvironment + * @return + */ + @SuppressWarnings("unchecked") + public Map<String, Map<String, Object>> getCustomConfig(CourseEnvironment courseEnvironment){ + Map<String, Map<String, Object>> defaultConf = new HashMap<String, Map<String, Object>>(); + VFSContainer base = (VFSContainer) courseEnvironment.getCourseBaseContainer().resolve(CourseLayoutHelper.LAYOUT_COURSE_SUBFOLDER); + if (base == null) { + return defaultConf; + } + VFSContainer themeBase = (VFSContainer) base.resolve("/" + CourseLayoutHelper.CONFIG_KEY_CUSTOM); + if (themeBase == null) { + return defaultConf; + } + VFSLeaf configTarget = (VFSLeaf) themeBase.resolve(CUSTOM_CONFIG_XML); + if (configTarget == null) { + return defaultConf; + } + XStream xStream = XStreamHelper.createXStreamInstance(); + return (Map<String, Map<String, Object>>) xStream.fromXML(configTarget.getInputStream()); + } + + /** + * get all possible layout elements. the real instances need to be created with the factory method! + * @return + */ + public List<AbstractLayoutElement> getAllAvailableElements(){ + return availableLayoutElements; + } + + // get a list of configured attributes + public List<AbstractLayoutAttribute> getAllAvailableAttributes(){ + return availableLayoutAttributes; + } + + // creates an instance of the given type with the factory-method and sets config for its attributes + public AbstractLayoutElement createLayoutElementByType(String type, Map<String, Object> config){ + List<AbstractLayoutElement> allElements = getAllAvailableElements(); + for (AbstractLayoutElement abstractLayoutElement : allElements) { + if(abstractLayoutElement.getLayoutElementTypeName().equals(type)) { + return abstractLayoutElement.createInstance(config); + } + } + return null; + } + + + /** + * [spring] + * @param availableLayoutElements The availableLayoutElements to set. + */ + public void setAvailableLayoutElements(List<AbstractLayoutElement> availableLayoutElements) { + this.availableLayoutElements = availableLayoutElements; + } + + /** + * [spring] + * @param availableLayoutAttributes The availableLayoutAttributes to set. + */ + public void setAvailableLayoutAttributes(List<AbstractLayoutAttribute> availableLayoutAttributes) { + this.availableLayoutAttributes = availableLayoutAttributes; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/_content/image.html b/src/main/java/org/olat/course/config/ui/courselayout/_content/image.html new file mode 100644 index 0000000000000000000000000000000000000000..fdd3f09b049443501f004aed2eca359d02c0f23c --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/_content/image.html @@ -0,0 +1,2 @@ +#if ($r.available("preview")) $r.render("preview") #end +#if ($r.available("logo.delete")) <br/> $r.render("logo.delete") #end \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/_content/style.html b/src/main/java/org/olat/course/config/ui/courselayout/_content/style.html new file mode 100644 index 0000000000000000000000000000000000000000..85e8c50508ab37d41db0514b88fbf5e1df35b891 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/_content/style.html @@ -0,0 +1,31 @@ +<br/> +<table> + <tr> + <th> </th> + #foreach( $attrib in $allAttribs ) + #set($attribName = $attrib.getLayoutAttributeTypeName()) + #set($rowheader = "attribute.title.$attribName") + <th>$r.translate($rowheader)</th> + #end + </tr> + +#foreach( $element in $guiWrapper.entrySet() ) + #set($elementName = $element.getKey() ) + #set($column = "element.title.$elementName") + <tr> + <td>$r.translate($column)</td> + + #set($elAttribs = $element.getValue()) + #foreach( $attrib in $allAttribs ) + <td> + #set($attribName = $attrib.getLayoutAttributeTypeName()) + #if ($elAttribs.get($attribName)) + $r.render($elAttribs.get($attribName).getName()) + #else + + #end + </td> + #end + </tr> + #end +</table> diff --git a/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..4433a37b5df06fee15d09b58d31d564c234681db --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_de.properties @@ -0,0 +1,16 @@ +tab.layout.title=Einstellungen +course.layout.selector=Layoutvorlage für Kurs wählen +course.layout.legacy=Aus Kursablageordner {0} +course.layout.default=Standard +course.layout.custom=Benutzerdefiniert... +course.layout.predefined=Kursvorlage +course.layout.template=Systemvorlage {0} +upload.second.logo=Logo hochladen +course.layout.save=Speichern +preview.image.label=Vorschau +logo.image.label=Logo für Header +logo.delete=Löschen +logo.file.type.error=Bitte eine Bilddatei als Logo hochladen (z.B. png, gif, jpg)! +logo.size.error=Die hochgeladene Datei ist zu gross, bitte verwenden Sie ein kleineres Logo. +logo.upload.success=Das Bild wurde erfolgreich gespeichert. +logo.upload.error=Das Bild konnte nicht gespeichert werden, bitte probieren Sie es mit einem anderen Bild. \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..410efc960ef6d0991b47aa0dcf61c788bcf39f52 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/_i18n/LocalStrings_en.properties @@ -0,0 +1,16 @@ +tab.layout.title=Settings +course.layout.selector=Choose layout for this course +course.layout.legacy=from course folder {0} +course.layout.default=Default +course.layout.custom=Customize... +course.layout.predefined=Course template +course.layout.template=System template {0} +upload.second.logo=Upload a logo +course.layout.save=Save +preview.image.label=Preview +logo.image.label=Logo in header +logo.delete=Delete +logo.file.type.error=Please choose a valid image as logo (i.e. png, gif, jpg)! +logo.size.error=The uploaded file is too large, please use a smaller one. +logo.upload.success=Your logo has been saved successfully. +logo.upload.error=This logo could not be processed, please try another image. \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/_spring/courseLayoutContext.xml b/src/main/java/org/olat/course/config/ui/courselayout/_spring/courseLayoutContext.xml new file mode 100644 index 0000000000000000000000000000000000000000..408ebf7f4abd1e59935b6c18511f9d267c08b2ff --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/_spring/courseLayoutContext.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + +<context:property-placeholder location="classpath:serviceconfig/olat.properties, classpath:olat.local.properties" /> + + <bean id="courseLayoutHelper" class="org.olat.course.config.ui.courselayout.CourseLayoutHelper" depends-on="courseModule"> + <property name="logoMaxHeight" value="100"/> + <property name="logoMaxWidth" value="300"/> + <property name="imageHelperToUse" ref="imageHelper"/> + <!-- OLAT will add course-layouts from the currently used OLAT-theme --> + <property name="layoutBlacklist" value="${layout.coursetemplates.blacklist}" /> + </bean> + + <bean id="courseConfigManager" class="org.olat.course.config.ui.courselayout.CustomConfigManager" depends-on="courseModule"> + <property name="availableLayoutElements"> + <list> + <bean class="org.olat.course.config.ui.courselayout.elements.TextLE" lazy-init="true"/> + <bean class="org.olat.course.config.ui.courselayout.elements.HeaderLE" lazy-init="true"/> + <bean class="org.olat.course.config.ui.courselayout.elements.LinkLE" lazy-init="true"/> + <bean class="org.olat.course.config.ui.courselayout.elements.MenuLE" lazy-init="true"/> + <bean class="org.olat.course.config.ui.courselayout.elements.ToolboxLE" lazy-init="true"/> + </list> + </property> + <property name="availableLayoutAttributes"> + <list> + <bean class="org.olat.course.config.ui.courselayout.attribs.FontLA"/> + <bean class="org.olat.course.config.ui.courselayout.attribs.SizeLA"/> + <bean class="org.olat.course.config.ui.courselayout.attribs.ColorLA"/> + <bean class="org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA"/> + <!-- keep this at the end, works only after other attributes are initialized! --> + <bean class="org.olat.course.config.ui.courselayout.attribs.PreviewLA"/> + </list> + </property> + </bean> + + <bean id="imageHelper" class="org.olat.core.util.ImageHelper" /> + +</beans> diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/AbstractLayoutAttribute.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/AbstractLayoutAttribute.java new file mode 100644 index 0000000000000000000000000000000000000000..6127df5349ee00be0a779c1573419bbf6c6cc511 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/AbstractLayoutAttribute.java @@ -0,0 +1,154 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +import java.util.Arrays; +import java.util.List; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.util.ArrayHelper; +import org.olat.core.util.StringHelper; + + +/** + * Description:<br> + * contains common stuff for an css-elements attribute + * give the attribute its name by attributeKey, set all available keys & values + * if used by elements children, the relative keys must be set + * getFormItem can be overwritten to return another component than a drop-down + * + * <P> + * Initial Date: 03.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public abstract class AbstractLayoutAttribute { + + private String attributeKey; + private String attributeValue = null; + private String[] relativeKeys; + private String[] availKeys; + private String[] availValues; + private String[] availCSS; + + /** + * @param availKeys The availKeys to set. + */ + protected void setAvailKeys(String[] availKeys) { + this.availKeys = ArrayHelper.addToArray(availKeys, "", false); + } + + /** + * @param availValues The availValues to set. + */ + protected void setAvailValues(String[] availValues) { + this.availValues = ArrayHelper.addToArray(availValues, "--", false); + } + + /** + * @param availCSS The availCSS to set. + */ + protected void setAvailCSS(String[] availCSS) { + this.availCSS = ArrayHelper.addToArray(availCSS, "", false); + } + + /** + * @param relativeKeys The relativeKeys to set. + */ + protected void setRelativeKeys(String[] relativeKeys) { + this.relativeKeys = relativeKeys; + } + + // as per default attaches a dropdown and selects what is given + public FormItem getFormItem(String compName, FormItemContainer formLayout){ + FormUIFactory uifact = FormUIFactory.getInstance(); + + SingleSelection fi = uifact.addDropdownSingleselect(compName, null, formLayout, availKeys, availValues, availCSS); + if (attributeValue!=null && Arrays.asList(availKeys).contains(attributeValue)){ + fi.select(attributeValue, true); + fi.showLabel(false); + } + return fi; + } + + + public String getRelativeCompiledAttribute(int rel){ + if (StringHelper.containsNonWhitespace(getAttributeKey())) { + return getAttributeKey() + ": " + getRelativeValue(getAttributeValue(), rel) + "; \n"; + } else { + return ""; + } + } + + /** + * @param attributeKey The attributeKey to set. + */ + public void setAttributeKey(String attributeKey) { + this.attributeKey = attributeKey; + } + + /** + * @return Returns the attributeKey. + */ + public String getAttributeKey() { + return attributeKey; + } + + /** + * @param attributeValue The attributeValue to set. + */ + public void setAttributeValue(String attributeValue) { + this.attributeValue = attributeValue; + } + + /** + * @return Returns the attributeValue. + */ + public String getAttributeValue() { + return attributeValue; + } + + public abstract String getLayoutAttributeTypeName(); + + /** + * get value relative to choosen attribute value. + * @param value + * @param rel + * @return the <rel> higher or lower value if any were set in attribute config + */ + public String getRelativeValue(String value, int rel){ + List<String> keyL = Arrays.asList(availKeys); + if(keyL.contains(value) && relativeKeys != null){ + int pos = keyL.indexOf(value); + int relpos = (pos + rel); + if (relpos >= 0 && relpos < relativeKeys.length) { + return relativeKeys[relpos]; + } else if (relpos < 0){ + return relativeKeys[0]; + } else if (relpos == relativeKeys.length){ + return relativeKeys[relativeKeys.length-1]; + } + } + return value; + } +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/BackgroundColorLA.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/BackgroundColorLA.java new file mode 100644 index 0000000000000000000000000000000000000000..31f989034e2480078d5a41248f85a112a1b6c10a --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/BackgroundColorLA.java @@ -0,0 +1,50 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + + +/** + * + * Description:<br> + * attribute: background-color + * needs some dummy classes in olat.css + * colored-dropdowns are not supported by all browsers! + * + * <P> + * Initial Date: 08.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class BackgroundColorLA extends ColorLA { + + public static final String IDENTIFIER = "backgroundColor"; + + public BackgroundColorLA() { + super(); + setAttributeKey("background-color"); + + } + + @Override + public String getLayoutAttributeTypeName() { + return IDENTIFIER; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorLA.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorLA.java new file mode 100644 index 0000000000000000000000000000000000000000..4db9b161e633e67d63444eb3ee19f626c649b380 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorLA.java @@ -0,0 +1,136 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; + +/** + * + * Description:<br> + * attribute: color + * needs some dummy classes in olat.css + * colored-dropdowns are not supported by all browsers! + * + * <P> + * Initial Date: 07.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class ColorLA extends AbstractLayoutAttribute { + + public static final String IDENTIFIER = "color"; + + public ColorLA() { + setAttributeKey("color"); + + String[] availKeys = new String[] { "Black", "Navy", "DarkBlue", "MediumBlue", "Blue", "DarkGreen", "Green", "Teal", "DarkCyan", + "DeepSkyBlue", "DarkTurquoise", "MediumSpringGreen", "Lime", "SpringGreen", "Aqua", "Cyan", "MidnightBlue", "DodgerBlue", + "LightSeaGreen", "ForestGreen", "SeaGreen", "DarkSlateGray", "DarkSlateGrey", "LimeGreen", "MediumSeaGreen", "Turquoise", + "RoyalBlue", "SteelBlue", "DarkSlateBlue", "MediumTurquoise", "Indigo ", "DarkOliveGreen", "CadetBlue", "CornflowerBlue", + "MediumAquaMarine", "DimGray", "DimGrey", "SlateBlue", "OliveDrab", "SlateGray", "SlateGrey", "LightSlateGray", "LightSlateGrey", + "MediumSlateBlue", "LawnGreen", "Chartreuse", "Aquamarine", "Maroon", "Purple", "Olive", "Gray", "Grey", "SkyBlue", "LightSkyBlue", + "BlueViolet", "DarkRed", "DarkMagenta", "SaddleBrown", "DarkSeaGreen", "LightGreen", "MediumPurple", "DarkViolet", "PaleGreen", + "DarkOrchid", "YellowGreen", "Sienna", "Brown", "DarkGray", "DarkGrey", "LightBlue", "GreenYellow", "PaleTurquoise", + "LightSteelBlue", "PowderBlue", "FireBrick", "DarkGoldenRod", "MediumOrchid", "RosyBrown", "DarkKhaki", "Silver", + "MediumVioletRed", "IndianRed ", "Peru", "Chocolate", "Tan", "LightGray", "LightGrey", "PaleVioletRed", "Thistle", "Orchid", + "GoldenRod", "Crimson", "Gainsboro", "Plum", "BurlyWood", "LightCyan", "Lavender", "DarkSalmon", "Violet", "PaleGoldenRod", + "LightCoral", "Khaki", "AliceBlue", "HoneyDew", "Azure", "SandyBrown", "Wheat", "Beige", "WhiteSmoke", "MintCream", "GhostWhite", + "Salmon", "AntiqueWhite", "Linen", "LightGoldenRodYellow", "OldLace", "Red", "Fuchsia", "Magenta", "DeepPink", "OrangeRed", + "Tomato", "HotPink", "Coral", "Darkorange", "LightSalmon", "Orange", "LightPink", "Pink", "Gold", "PeachPuff", "NavajoWhite", + "Moccasin", "Bisque", "MistyRose", "BlanchedAlmond", "PapayaWhip", "LavenderBlush", "SeaShell", "Cornsilk", "LemonChiffon", + "FloralWhite", "Snow", "Yellow", "LightYellow", "Ivory", "White" }; + setAvailKeys(availKeys); + String[] availValues = new String[] { "Black", "Navy", "DarkBlue", "MediumBlue", "Blue", "DarkGreen", "Green", "Teal", "DarkCyan", + "DeepSkyBlue", "DarkTurquoise", "MediumSpringGreen", "Lime", "SpringGreen", "Aqua", "Cyan", "MidnightBlue", "DodgerBlue", + "LightSeaGreen", "ForestGreen", "SeaGreen", "DarkSlateGray", "DarkSlateGrey", "LimeGreen", "MediumSeaGreen", "Turquoise", + "RoyalBlue", "SteelBlue", "DarkSlateBlue", "MediumTurquoise", "Indigo ", "DarkOliveGreen", "CadetBlue", "CornflowerBlue", + "MediumAquaMarine", "DimGray", "DimGrey", "SlateBlue", "OliveDrab", "SlateGray", "SlateGrey", "LightSlateGray", "LightSlateGrey", + "MediumSlateBlue", "LawnGreen", "Chartreuse", "Aquamarine", "Maroon", "Purple", "Olive", "Gray", "Grey", "SkyBlue", "LightSkyBlue", + "BlueViolet", "DarkRed", "DarkMagenta", "SaddleBrown", "DarkSeaGreen", "LightGreen", "MediumPurple", "DarkViolet", "PaleGreen", + "DarkOrchid", "YellowGreen", "Sienna", "Brown", "DarkGray", "DarkGrey", "LightBlue", "GreenYellow", "PaleTurquoise", + "LightSteelBlue", "PowderBlue", "FireBrick", "DarkGoldenRod", "MediumOrchid", "RosyBrown", "DarkKhaki", "Silver", + "MediumVioletRed", "IndianRed ", "Peru", "Chocolate", "Tan", "LightGray", "LightGrey", "PaleVioletRed", "Thistle", "Orchid", + "GoldenRod", "Crimson", "Gainsboro", "Plum", "BurlyWood", "LightCyan", "Lavender", "DarkSalmon", "Violet", "PaleGoldenRod", + "LightCoral", "Khaki", "AliceBlue", "HoneyDew", "Azure", "SandyBrown", "Wheat", "Beige", "WhiteSmoke", "MintCream", "GhostWhite", + "Salmon", "AntiqueWhite", "Linen", "LightGoldenRodYellow", "OldLace", "Red", "Fuchsia", "Magenta", "DeepPink", "OrangeRed", + "Tomato", "HotPink", "Coral", "Darkorange", "LightSalmon", "Orange", "LightPink", "Pink", "Gold", "PeachPuff", "NavajoWhite", + "Moccasin", "Bisque", "MistyRose", "BlanchedAlmond", "PapayaWhip", "LavenderBlush", "SeaShell", "Cornsilk", "LemonChiffon", + "FloralWhite", "Snow", "Yellow", "LightYellow", "Ivory", "White" }; + setAvailValues(availValues); + String[] availCSS = new String[] { "Black", "Navy", "DarkBlue", "MediumBlue", "Blue", "DarkGreen", "Green", "Teal", "DarkCyan", + "DeepSkyBlue", "DarkTurquoise", "MediumSpringGreen", "Lime", "SpringGreen", "Aqua", "Cyan", "MidnightBlue", "DodgerBlue", + "LightSeaGreen", "ForestGreen", "SeaGreen", "DarkSlateGray", "DarkSlateGrey", "LimeGreen", "MediumSeaGreen", "Turquoise", + "RoyalBlue", "SteelBlue", "DarkSlateBlue", "MediumTurquoise", "Indigo ", "DarkOliveGreen", "CadetBlue", "CornflowerBlue", + "MediumAquaMarine", "DimGray", "DimGrey", "SlateBlue", "OliveDrab", "SlateGray", "SlateGrey", "LightSlateGray", "LightSlateGrey", + "MediumSlateBlue", "LawnGreen", "Chartreuse", "Aquamarine", "Maroon", "Purple", "Olive", "Gray", "Grey", "SkyBlue", "LightSkyBlue", + "BlueViolet", "DarkRed", "DarkMagenta", "SaddleBrown", "DarkSeaGreen", "LightGreen", "MediumPurple", "DarkViolet", "PaleGreen", + "DarkOrchid", "YellowGreen", "Sienna", "Brown", "DarkGray", "DarkGrey", "LightBlue", "GreenYellow", "PaleTurquoise", + "LightSteelBlue", "PowderBlue", "FireBrick", "DarkGoldenRod", "MediumOrchid", "RosyBrown", "DarkKhaki", "Silver", + "MediumVioletRed", "IndianRed ", "Peru", "Chocolate", "Tan", "LightGray", "LightGrey", "PaleVioletRed", "Thistle", "Orchid", + "GoldenRod", "Crimson", "Gainsboro", "Plum", "BurlyWood", "LightCyan", "Lavender", "DarkSalmon", "Violet", "PaleGoldenRod", + "LightCoral", "Khaki", "AliceBlue", "HoneyDew", "Azure", "SandyBrown", "Wheat", "Beige", "WhiteSmoke", "MintCream", "GhostWhite", + "Salmon", "AntiqueWhite", "Linen", "LightGoldenRodYellow", "OldLace", "Red", "Fuchsia", "Magenta", "DeepPink", "OrangeRed", + "Tomato", "HotPink", "Coral", "Darkorange", "LightSalmon", "Orange", "LightPink", "Pink", "Gold", "PeachPuff", "NavajoWhite", + "Moccasin", "Bisque", "MistyRose", "BlanchedAlmond", "PapayaWhip", "LavenderBlush", "SeaShell", "Cornsilk", "LemonChiffon", + "FloralWhite", "Snow", "Yellow", "LightYellow", "Ivory", "White" }; + setAvailCSS(availCSS); + } + + @Override + public String getLayoutAttributeTypeName() { + return IDENTIFIER; + } + + + + /** + * @see org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute#getFormItem(java.lang.String, org.olat.core.gui.components.form.flexible.FormItemContainer) + * get a dropdown and an input field wrapped in a FormLayoutContainer + */ + @Override + public FormItem getFormItem(String compName, FormItemContainer formLayout) { + FormUIFactory uifact = FormUIFactory.getInstance(); + FormLayoutContainer colorFLC = FormLayoutContainer.createVerticalFormLayout(compName, formLayout.getTranslator()); + formLayout.add(compName, colorFLC); + FormItem dropDown = super.getFormItem(compName + "sel", formLayout); + dropDown.addActionListener((Controller) formLayout.getUserObject(), FormEvent.ONCHANGE); + colorFLC.add(dropDown); + + String inputValue = ""; + if (getAttributeValue()!=null && !((SingleSelection)dropDown).isOneSelected()){ + inputValue = getAttributeValue(); + } + + TextElement inputEl = uifact.addTextElement(compName + "value", null, 7, inputValue, colorFLC); + inputEl.setDisplaySize(7); + colorFLC.setUserObject(new ColorSpecialHandler(colorFLC)); + return colorFLC; + } + +} + diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorSpecialHandler.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorSpecialHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..fc32f283e1d8cba6abd22ad96856e50470f8758c --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/ColorSpecialHandler.java @@ -0,0 +1,95 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.util.StringHelper; + + +/** + * + * Description:<br> + * get and validate values from a color attribute (ColorLA) with two formItems + * + * <P> + * Initial Date: 17.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +class ColorSpecialHandler extends SpecialAttributeFormItemHandler { + + private static final String HEX_PATTERN = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"; + private boolean hasError = false; + + ColorSpecialHandler(FormItem item) { + super (item); + } + + @Override + public String getValue(){ + FormLayoutContainer innerFLC = (FormLayoutContainer) getFormItem(); + Map<String, FormItem> items = innerFLC.getFormComponents(); + String ddValue = ""; + String inputValue = ""; + FormItem inputItem = null; + for (Entry<String, FormItem> fiEntry : items.entrySet()) { + String compName = fiEntry.getKey(); + FormItem fi = fiEntry.getValue(); + if (compName.endsWith("sel") && fi instanceof SingleSelection){ + ddValue = ((SingleSelection)fi).isOneSelected() ? ((SingleSelection)fi).getSelectedKey() : ""; + } + if (compName.endsWith("value") && fi instanceof TextElement) { + inputItem = fi; + inputValue = ((TextElement)fi).getValue(); + } + } + if (ddValue.equals("") && StringHelper.containsNonWhitespace(inputValue)){ + // use input-value if valid + Pattern pattern = Pattern.compile(HEX_PATTERN); + Matcher matcher = pattern.matcher(inputValue); + if (matcher.matches()) { + hasError = false; + return inputValue; + } else { + hasError = true; + inputItem.setErrorKey("color.hex.error", null); + return ""; + } + } + if (!ddValue.equals("") && StringHelper.containsNonWhitespace(inputValue)){ + inputItem.setErrorKey("color.double.error", null); + } + return ddValue; + } + + @Override + public boolean hasError() { + return hasError; + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/FontLA.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/FontLA.java new file mode 100644 index 0000000000000000000000000000000000000000..864b6acf010f68e32a6bea9433bdeb637dec9f36 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/FontLA.java @@ -0,0 +1,57 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +/** + * Description:<br> + * attribute: font + * IMPORTANT: needs corresponding css-classes in olat.css to get + * a valid preview in the dropdown (not supported by every browser)! + * + * <P> + * Initial Date: 03.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class FontLA extends AbstractLayoutAttribute { + + public static final String IDENTIFIER = "font"; + + public FontLA() { + setAttributeKey("font-family"); + String[] availKeys = new String[] { "arial,helvetica,sans-serif", "arial black,avant garde", "comic sans ms,sans-serif", + "courier new,courier", "georgia,serif", "impact,chicago", "lucida console,monaco,monospace", + "palatino linotype,book antiqua,palatino,serif", "times new roman,times", "verdana,geneva,sans-serif", "wingdings,zapf dingbats" }; + setAvailKeys(availKeys); + String[] availValues = new String[] { "Arial normal", "Arial black", "Comic Sans MS", "Courier", "Georgia", "Impact", "Lucida Console", + "Palatino", "Times", "Verdana", "Wingdings" }; + setAvailValues(availValues); + String[] availCSS = new String[] { "clgen_font_arial", "clgen_font_arial_black", "clgen_font_comic", "clgen_font_courier", + "clgen_font_georgia", "clgen_font_impact", "clgen_font_lucida", "clgen_font_palatino", "clgen_font_times", "clgen_font_verdana", "" }; + setAvailCSS(availCSS); + } + + @Override + public String getLayoutAttributeTypeName() { + return IDENTIFIER; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/PreviewLA.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/PreviewLA.java new file mode 100644 index 0000000000000000000000000000000000000000..dbad6ced08ed0e596ef93125dc37f0cb2cbfae33 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/PreviewLA.java @@ -0,0 +1,76 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +/** + * + * Description:<br> + * attribute: preview, gets the config of all other attributes from AbstractLayoutElement, and shows it as preview + * + * <P> + * Initial Date: 08.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class PreviewLA extends AbstractLayoutAttribute { + + public static final String IDENTIFIER = "preview"; + + public PreviewLA() { + setAttributeKey(""); + String[] availKeys = new String[] { "" }; + setAvailKeys(availKeys); + String[] availValues = new String[] { "" }; + setAvailValues(availValues); + String[] availCSS = new String[] { "" }; + setAvailCSS(availCSS); + } + + @Override + public String getLayoutAttributeTypeName() { + return IDENTIFIER; + } + + /** + * @see org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute#getFormItem(java.lang.String, org.olat.core.gui.components.form.flexible.FormItemContainer) + */ + @Override + public FormItem getFormItem(String compName, FormItemContainer formLayout) { + FormUIFactory uifact = FormUIFactory.getInstance(); + + String textEl = new String("<span style=\"" + getAttributeValue() + "\">"+ formLayout.getTranslator().translate("preview.sample") + "</span>"); + StaticTextElement fi = uifact.addStaticTextElement(compName, null, textEl, formLayout); + return fi; + } + + + /** + * @see org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute#getRelativeValue(java.lang.String, int) + */ + @Override + public String getRelativeValue(String value, int rel) { + return ""; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/SizeLA.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/SizeLA.java new file mode 100644 index 0000000000000000000000000000000000000000..52d40d3b0c563b358eca0b0f023ae3db57b6beb1 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/SizeLA.java @@ -0,0 +1,58 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +/** + * Description:<br> + * attribute: size + * + * <P> + * Initial Date: 04.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class SizeLA extends AbstractLayoutAttribute { + + public static final String IDENTIFIER = "size"; + + /** + * + */ + public SizeLA() { + setAttributeKey("font-size"); + String[] availKeys = new String[] { "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" }; + setAvailKeys(availKeys); + String[] relativeKeys = new String[]{ "0.3em", "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "3em" }; + setRelativeKeys(relativeKeys); + String[] availValues = new String[] { "1 (xx-small)", "2 (x-small)", "3 (small)", "4 (medium)", "5 (large)", "6 (x-large)", "7 (xx-large)" }; + setAvailValues(availValues); + String[] availCSS = new String[] { "clgen_font_xxsmall", "b_xsmall", "b_small", "", "b_large", "b_xlarge", "clgen_font_xxlarge" }; + setAvailCSS(availCSS); + } + + /** + * @see org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute#getLayoutAttributeTypeName() + */ + @Override + public String getLayoutAttributeTypeName() { + return IDENTIFIER; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/SpecialAttributeFormItemHandler.java b/src/main/java/org/olat/course/config/ui/courselayout/attribs/SpecialAttributeFormItemHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d1211e5e03e0ba9e984e61fa6fbb4d09fd94b515 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/SpecialAttributeFormItemHandler.java @@ -0,0 +1,52 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.attribs; + +import org.olat.core.gui.components.form.flexible.FormItem; + +/** + * + * Description:<br> + * Interface to use when an Attribute has a complex getValue() + * + * <P> + * Initial Date: 17.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public abstract class SpecialAttributeFormItemHandler { + + private FormItem formItem; + + protected SpecialAttributeFormItemHandler(FormItem formItem){ + this.formItem = formItem; + } + + public abstract String getValue(); + + protected FormItem getFormItem(){ + return formItem; + } + + public boolean hasError(){ + return formItem.hasError(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..2fb4b0df91c6c551f082dfbd3c5a71d0d2840f29 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_de.properties @@ -0,0 +1,8 @@ +attribute.title.font=Schriftart +attribute.title.size=Schriftgrösse +attribute.title.color=Farbe +attribute.title.backgroundColor=Hintergrund-Farbe +attribute.title.preview=Vorschau +preview.sample=Ein kurzer Vorschau-Text +color.hex.error=Dieser Hex-Farbwert ist ungültig! Start mit #. +color.double.error=Bitte entweder Farbe auswählen oder Hex-Farbe angeben, nicht beides! \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..f989cd74a681e4b3556682ec5b9bdd3615c7f4ea --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/attribs/_i18n/LocalStrings_en.properties @@ -0,0 +1,8 @@ +attribute.title.font=Font family +attribute.title.size=Font size +attribute.title.color=Color +attribute.title.backgroundColor=Background color +attribute.title.preview=Preview +preview.sample=A short preview text +color.hex.error=This is an invalid hex value! Start with #. +color.double.error=Please either choose a color or enter a hex value, not both! \ No newline at end of file diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/AbstractLayoutElement.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/AbstractLayoutElement.java new file mode 100644 index 0000000000000000000000000000000000000000..0174d5b9d7dea58b102f41aa4ab0a1bbd69e3e9f --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/AbstractLayoutElement.java @@ -0,0 +1,139 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.olat.core.util.StringHelper; +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; + +/** + * Description:<br> + * does all common stuff for a css-element. + * extend this type; to get a concrete element implement createInstance! + * there needs to be set three things (by setters): + * - iFrameRelativeChildren -> all possible child elements with relative position to choosen value, for iframe.css + * - mainRelativeChilds -> same as above, but for main-css + * - availableAttributes -> all possible attributes, that can be used for this element + * + * using getCSSForMain / getCSSForIFrame returns all attributes as compiled String + * <P> + * Initial Date: 03.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public abstract class AbstractLayoutElement { + + private List<AbstractLayoutAttribute> availableAttributes; + private Map<String, Object> config; + private HashMap<String, Integer> iFrameRelativeChildren; + private HashMap<String, Integer> mainRelativeChilds; + + protected AbstractLayoutElement(){ + // + } + + protected AbstractLayoutElement(Map<String, Object> config) { + this.config = config; + } + + public String getCSSForMain(){ + return loopChildren(mainRelativeChilds); + } + + public String getCSSForIFrame(){ + return loopChildren(iFrameRelativeChildren); + } + /** + * @return + */ + private String loopChildren(HashMap<String, Integer> relativeChilds) { + StringBuffer sb = new StringBuffer(); + for (Entry<String, Integer> relChild : relativeChilds.entrySet()) { + sb.append(relChild.getKey()).append(" { \n" ); + int rel = relChild.getValue(); + prepareAttributePart(rel, sb); + sb.append("}\n"); + } + return sb.toString(); + } + + protected void prepareAttributePart(int rel, StringBuffer sb){ + for (AbstractLayoutAttribute attrib : availableAttributes) { + if (StringHelper.containsNonWhitespace(attrib.getAttributeValue())){ + sb.append("\t"); + sb.append(attrib.getRelativeCompiledAttribute(rel)); + } + } + } + + protected void setIframeRelativeChildren(HashMap<String, Integer> iFrameRelativeChildren) { + this.iFrameRelativeChildren = iFrameRelativeChildren; + } + + protected void setMainRelativeChildren(HashMap<String, Integer> mainRelativeChilds) { + this.mainRelativeChilds = mainRelativeChilds; + } + + public void setAvailableAttributes(List<AbstractLayoutAttribute> availableAttributes) { + this.availableAttributes = availableAttributes; + } + + /** + * @return Returns the availableAttributes. + */ + public List<AbstractLayoutAttribute> getAvailableAttributes() { + return availableAttributes; + } + + public abstract String getLayoutElementTypeName(); + + // factory method must be implemented to create a new instance of given type + public abstract AbstractLayoutElement createInstance(Map<String, Object> elConfig); + + protected void initAttributeConfig() { + if (getConfig() != null) { + List<AbstractLayoutAttribute> avAttribs = getAvailableAttributes(); + for (AbstractLayoutAttribute abstractLayoutAttribute : avAttribs) { + String type = abstractLayoutAttribute.getLayoutAttributeTypeName(); + if (type.equals(PreviewLA.IDENTIFIER)){ + StringBuffer sbPreviewStyle = new StringBuffer(); + prepareAttributePart(0, sbPreviewStyle); + abstractLayoutAttribute.setAttributeValue(sbPreviewStyle.toString()); + } else { + abstractLayoutAttribute.setAttributeValue((String) getConfig().get(type)); + } + } + } + } + + /** + * @return Returns the config. + */ + public Map<String, Object> getConfig() { + return config; + } + + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/HeaderLE.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/HeaderLE.java new file mode 100644 index 0000000000000000000000000000000000000000..f2b363657e0e7517e7c8e41c900f9761446ee684 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/HeaderLE.java @@ -0,0 +1,91 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA; +import org.olat.course.config.ui.courselayout.attribs.ColorLA; +import org.olat.course.config.ui.courselayout.attribs.FontLA; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SizeLA; + +/** + * Description:<br> + * element: headers with different sizes + * + * <P> + * Initial Date: 03.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class HeaderLE extends AbstractLayoutElement { + + public static final String IDENTIFIER = "header"; + + public HeaderLE() { + // + } + + public HeaderLE(Map<String, Object> config) { + super(config); + + HashMap<String, Integer> iFrameRelativeChildren = new HashMap<String, Integer>(); + iFrameRelativeChildren.put("h1", +2); + iFrameRelativeChildren.put("h2", +1); + iFrameRelativeChildren.put("h3", 0); + iFrameRelativeChildren.put("h4", -1); + iFrameRelativeChildren.put("h5", -2); + setIframeRelativeChildren(iFrameRelativeChildren); + + HashMap<String, Integer> mainRelativeChildren = new HashMap<String, Integer>(); + mainRelativeChildren.put("#b_main h1", +2); + mainRelativeChildren.put("#b_main h2", +1); + mainRelativeChildren.put("#b_main h3", 0); + mainRelativeChildren.put("#b_main h4", -1); + mainRelativeChildren.put("#b_main h5", -2); + setMainRelativeChildren(mainRelativeChildren); + + ArrayList<AbstractLayoutAttribute> avAttribs = new ArrayList<AbstractLayoutAttribute>(); + avAttribs.add(new FontLA()); + avAttribs.add(new SizeLA()); + avAttribs.add(new ColorLA()); + avAttribs.add(new BackgroundColorLA()); + avAttribs.add(new PreviewLA()); + + setAvailableAttributes(avAttribs); + initAttributeConfig(); + } + + @Override + public String getLayoutElementTypeName() { + return IDENTIFIER; + } + + @Override + public AbstractLayoutElement createInstance(Map<String, Object> elConfig) { + return new HeaderLE(elConfig); + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/LinkLE.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/LinkLE.java new file mode 100644 index 0000000000000000000000000000000000000000..d6201e0f93e33bc8e8742e44dce6e904fc1d7aa5 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/LinkLE.java @@ -0,0 +1,83 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA; +import org.olat.course.config.ui.courselayout.attribs.ColorLA; +import org.olat.course.config.ui.courselayout.attribs.FontLA; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SizeLA; + +/** + * + * Description:<br> + * element: links + * + * <P> + * Initial Date: 07.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class LinkLE extends AbstractLayoutElement { + + public static final String IDENTIFIER = "link"; + + public LinkLE() { + // + } + + public LinkLE(Map<String, Object> config) { + super(config); + HashMap<String, Integer> iFrameRelativeChildren = new HashMap<String, Integer>(); + iFrameRelativeChildren.put("a", 0); + setIframeRelativeChildren(iFrameRelativeChildren); + + HashMap<String, Integer> mainRelativeChildren = new HashMap<String, Integer>(); + mainRelativeChildren.put("#b_main a", 0); + setMainRelativeChildren(mainRelativeChildren); + + ArrayList<AbstractLayoutAttribute> avAttribs = new ArrayList<AbstractLayoutAttribute>(); + avAttribs.add(new FontLA()); + avAttribs.add(new SizeLA()); + avAttribs.add(new ColorLA()); + avAttribs.add(new BackgroundColorLA()); + avAttribs.add(new PreviewLA()); + + setAvailableAttributes(avAttribs); + initAttributeConfig(); + } + + @Override + public String getLayoutElementTypeName() { + return IDENTIFIER; + } + + @Override + public AbstractLayoutElement createInstance(Map<String, Object> config) { + return new LinkLE(config); + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/MenuLE.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/MenuLE.java new file mode 100644 index 0000000000000000000000000000000000000000..460f2623340679e5aba948b4c55018c8e67c9950 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/MenuLE.java @@ -0,0 +1,82 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA; +import org.olat.course.config.ui.courselayout.attribs.ColorLA; +import org.olat.course.config.ui.courselayout.attribs.FontLA; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SizeLA; + +/** + * + * Description:<br> + * element: menu (row1) + * + * <P> + * Initial Date: 08.02.2011 <br> + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class MenuLE extends AbstractLayoutElement { + + public static final String IDENTIFIER = "menu"; + + public MenuLE() { + // + } + + public MenuLE(Map<String, Object> config) { + super(config); + + HashMap<String, Integer> iFrameRelativeChildren = new HashMap<String, Integer>(); + setIframeRelativeChildren(iFrameRelativeChildren); + + HashMap<String, Integer> mainRelativeChildren = new HashMap<String, Integer>(); + mainRelativeChildren.put("#b_main .b_tree a", 0); + setMainRelativeChildren(mainRelativeChildren); + + ArrayList<AbstractLayoutAttribute> avAttribs = new ArrayList<AbstractLayoutAttribute>(); + avAttribs.add(new FontLA()); + avAttribs.add(new SizeLA()); + avAttribs.add(new ColorLA()); + avAttribs.add(new BackgroundColorLA()); + avAttribs.add(new PreviewLA()); + + setAvailableAttributes(avAttribs); + initAttributeConfig(); + } + + @Override + public String getLayoutElementTypeName() { + return IDENTIFIER; + } + + @Override + public AbstractLayoutElement createInstance(Map<String, Object> config) { + return new MenuLE(config); + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/TextLE.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/TextLE.java new file mode 100644 index 0000000000000000000000000000000000000000..373dd84b572329c488f82eb239aea120d9f67b88 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/TextLE.java @@ -0,0 +1,86 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA; +import org.olat.course.config.ui.courselayout.attribs.ColorLA; +import org.olat.course.config.ui.courselayout.attribs.FontLA; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SizeLA; + +/** + * Description:<br> + * element: text with children + * + * <P> + * Initial Date: 03.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class TextLE extends AbstractLayoutElement { + + public static final String IDENTIFIER = "text"; + + public TextLE() { + // + } + + public TextLE(Map<String, Object> config) { + super(config); + + HashMap<String, Integer> iFrameRelativeChildren = new HashMap<String, Integer>(); + iFrameRelativeChildren.put("body", 0); + iFrameRelativeChildren.put("table", 0); + iFrameRelativeChildren.put("ol, ul, li", 0); + setIframeRelativeChildren(iFrameRelativeChildren); + + HashMap<String, Integer> mainRelativeChildren = new HashMap<String, Integer>(); + mainRelativeChildren.put("#b_main", 0); + iFrameRelativeChildren.put("#b_main table", 0); + iFrameRelativeChildren.put("#b_main ol, #b_main ul, #b_main li", 0); + setMainRelativeChildren(mainRelativeChildren); + + ArrayList<AbstractLayoutAttribute> avAttribs = new ArrayList<AbstractLayoutAttribute>(); + avAttribs.add(new FontLA()); + avAttribs.add(new SizeLA()); + avAttribs.add(new ColorLA()); + avAttribs.add(new BackgroundColorLA()); + avAttribs.add(new PreviewLA()); + + setAvailableAttributes(avAttribs); + initAttributeConfig(); + } + + public TextLE createInstance(Map<String, Object> config) { + return new TextLE(config); + } + + @Override + public String getLayoutElementTypeName() { + return IDENTIFIER; + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/ToolboxLE.java b/src/main/java/org/olat/course/config/ui/courselayout/elements/ToolboxLE.java new file mode 100644 index 0000000000000000000000000000000000000000..80f68e9127d9054fb0a6bb183ea18c796a24f691 --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/ToolboxLE.java @@ -0,0 +1,84 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.course.config.ui.courselayout.elements; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.course.config.ui.courselayout.attribs.AbstractLayoutAttribute; +import org.olat.course.config.ui.courselayout.attribs.BackgroundColorLA; +import org.olat.course.config.ui.courselayout.attribs.ColorLA; +import org.olat.course.config.ui.courselayout.attribs.FontLA; +import org.olat.course.config.ui.courselayout.attribs.PreviewLA; +import org.olat.course.config.ui.courselayout.attribs.SizeLA; + +/** + * + * Description:<br> + * element: toolbox (right panel/row2) + * + * <P> + * Initial Date: 07.02.2011 <br> + * + * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com + */ +public class ToolboxLE extends AbstractLayoutElement { + + public static final String IDENTIFIER = "toolbox"; + + public ToolboxLE() { + // + } + + public ToolboxLE(Map<String, Object> config) { + super(config); + + HashMap<String, Integer> iFrameRelativeChildren = new HashMap<String, Integer>(); + setIframeRelativeChildren(iFrameRelativeChildren); + + HashMap<String, Integer> mainRelativeChildren = new HashMap<String, Integer>(); +// mainRelativeChildren.put("#b_main .b_toolbox", 0); + mainRelativeChildren.put("#b_main .b_toolbox a", 0); + setMainRelativeChildren(mainRelativeChildren); + + ArrayList<AbstractLayoutAttribute> avAttribs = new ArrayList<AbstractLayoutAttribute>(); + avAttribs.add(new FontLA()); + avAttribs.add(new SizeLA()); + avAttribs.add(new ColorLA()); + avAttribs.add(new BackgroundColorLA()); + avAttribs.add(new PreviewLA()); + + setAvailableAttributes(avAttribs); + initAttributeConfig(); + } + + @Override + public String getLayoutElementTypeName() { + return IDENTIFIER; + } + + @Override + public AbstractLayoutElement createInstance(Map<String, Object> config) { + return new ToolboxLE(config); + } + +} diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..9fdc488f9fb5a503ab5fe3708cb34cd188c6fece --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_de.properties @@ -0,0 +1,5 @@ +element.title.text=Text +element.title.link=Links +element.title.menu=Menue +element.title.toolbox=Toolbox +element.title.header=Überschriften diff --git a/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..a7a652dbc1b7346ff20729201220dfd7b28b658c --- /dev/null +++ b/src/main/java/org/olat/course/config/ui/courselayout/elements/_i18n/LocalStrings_en.properties @@ -0,0 +1,5 @@ +element.title.text=Text +element.title.link=Links +element.title.menu=Menue +element.title.toolbox=Toolbox +element.title.header=Headers diff --git a/src/main/java/org/olat/course/editor/EditorMainController.java b/src/main/java/org/olat/course/editor/EditorMainController.java index 212270fa082a386f24d76dbf3fb82d1856e9fdf3..97c9cd7029333075f5ebc5a2bff62fb3f4073054 100644 --- a/src/main/java/org/olat/course/editor/EditorMainController.java +++ b/src/main/java/org/olat/course/editor/EditorMainController.java @@ -22,6 +22,7 @@ package org.olat.course.editor; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -31,8 +32,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import javax.servlet.http.HttpServletRequest; - import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.modules.bc.FolderRunController; @@ -41,11 +40,13 @@ import org.olat.core.dispatcher.mapper.MapperRegistry; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.htmlheader.HtmlHeaderComponent; +import org.olat.core.gui.components.htmlheader.jscss.CustomCSS; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.tabbedpane.TabbedPane; import org.olat.core.gui.components.tree.MenuTree; import org.olat.core.gui.components.tree.SelectionTree; +import org.olat.core.gui.components.tree.TreeDropEvent; import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -65,8 +66,6 @@ import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.gui.control.winmgr.JSCommand; -import org.olat.core.gui.media.MediaResource; -import org.olat.core.gui.media.NotFoundMediaResource; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; @@ -86,13 +85,11 @@ import org.olat.core.util.tree.TreeVisitor; import org.olat.core.util.tree.Visitor; import org.olat.core.util.vfs.NamedContainerImpl; 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.VFSMediaResource; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.config.CourseConfig; +import org.olat.course.config.ui.courselayout.CourseLayoutHelper; +import org.olat.course.editor.PublishStepCatalog.CategoryLabel; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.CourseNodeConfiguration; @@ -135,6 +132,7 @@ public class EditorMainController extends MainLayoutBasicController implements G private static final String CMD_KEEPOPEN_ERROR = "keep.open.error"; private static final String CMD_KEEPCLOSED_WARNING = "keep.closed.warning"; private static final String CMD_KEEPOPEN_WARNING = "keep.open.warning"; + private static final String CMD_MULTI_SP = "cmp.multi.sp"; // NLS support @@ -159,6 +157,8 @@ public class EditorMainController extends MainLayoutBasicController implements G private static final String NLS_MOVECOPYNODE_ERROR_ROOTNODE = "movecopynode.error.rootnode"; private static final String NLS_COURSEFOLDER_NAME = "coursefolder.name"; private static final String NLS_COURSEFOLDER_CLOSE = "coursefolder.close"; + private static final String NLS_ADMIN_HEADER = "command.admin.header"; + private static final String NLS_MULTI_SPS = "command.multi.sps"; private Boolean errorIsOpen = Boolean.TRUE; private Boolean warningIsOpen = Boolean.FALSE; @@ -194,6 +194,8 @@ public class EditorMainController extends MainLayoutBasicController implements G private Link keepClosedWarningButton; private Link keepOpenWarningButton; private CloseableModalController cmc; + + private MultiSPController multiSPChooserCtr; private OLATResourceable ores; @@ -238,6 +240,10 @@ public class EditorMainController extends MainLayoutBasicController implements G enableCustomCss(ureq); menuTree = new MenuTree("luTree"); + menuTree.setExpandSelectedNode(false); + //fxdiff VCRP-9: drag and drop in menu tree + menuTree.setDragAndDropEnabled(true); + menuTree.setDragAndDropGroup("courseEditorGroup"); /* @@ -266,6 +272,7 @@ public class EditorMainController extends MainLayoutBasicController implements G getWindowControl().setInfo(translate(NLS_PUBLISHED_LATEST, Formatter.getInstance(ureq.getLocale()).formatDateAndTime(d))); } menuTree.setTreeModel(cetm); + menuTree.setOpenNodeIds(Collections.singleton(cetm.getRootNode().getIdent())); menuTree.addListener(this); selTree = new SelectionTree("selection", getTranslator()); @@ -296,6 +303,9 @@ public class EditorMainController extends MainLayoutBasicController implements G log.error("Error while trying to add a course buildingblock of type \""+courseNodeAlias +"\" to the editor", e); } } + + toolC.addHeader(translate(NLS_ADMIN_HEADER)); + toolC.addLink(CMD_MULTI_SP, translate(NLS_MULTI_SPS), CMD_MULTI_SP, "b_toolbox_copy"); toolC.addHeader(translate(NLS_COMMAND_DELETENODE_HEADER)); toolC.addLink(CMD_DELNODE, translate(NLS_COMMAND_DELETENODE), CMD_DELNODE, "b_toolbox_delete"); @@ -336,6 +346,10 @@ public class EditorMainController extends MainLayoutBasicController implements G TreeEvent te = (TreeEvent) event; String nodeId = te.getNodeId(); updateViewForSelectedNodeId(ureq, nodeId); + //fxdiff VCRP-9: drag and drop in menu tree + } else if(event.getCommand().equals(MenuTree.COMMAND_TREENODE_DROP)) { + TreeDropEvent te = (TreeDropEvent) event; + dropNodeAsChild(ureq, course, te.getDroppedNodeId(), te.getTargetNodeId(), te.isAsChild(), te.isAtTheEnd()); } } else if (source == main) { if (event.getCommand().startsWith(NLS_START_HELP_WIZARD)) { @@ -587,6 +601,14 @@ public class EditorMainController extends MainLayoutBasicController implements G publishManager.changeGeneralAccess(ureq1, newAccess, membersOnly); hasChanges = true; } + + //fxdiff VCRP-3: add catalog entry in publish wizard + if (runContext.containsKey("catalogChoice")) { + String choice = (String) runContext.get("catalogChoice"); + List<CategoryLabel> categories = (List<CategoryLabel>)runContext.get("categories"); + PublishProcess publishManager = (PublishProcess) runContext.get("publishProcess"); + publishManager.publishToCatalog(choice, categories); + } // signal correct completion and tell if changes were made or not. return hasChanges ? StepsMainRunController.DONE_MODIFIED : StepsMainRunController.DONE_UNCHANGED; @@ -606,13 +628,24 @@ public class EditorMainController extends MainLayoutBasicController implements G // Folder for course with custom link model to jump to course nodes VFSContainer namedCourseFolder = new NamedContainerImpl(translate(NLS_COURSEFOLDER_NAME), course.getCourseFolderContainer()); CustomLinkTreeModel customLinkTreeModel = new CourseInternalLinkTreeModel(course.getEditorTreeModel()); - FolderRunController bcrun = new FolderRunController(namedCourseFolder, true, true, ureq, getWindowControl(), null, customLinkTreeModel); + FolderRunController bcrun = new FolderRunController(namedCourseFolder, true, true, true, ureq, getWindowControl(), null, customLinkTreeModel); bcrun.addLoggingResourceable(LoggingResourceable.wrap(course)); Component folderComponent = bcrun.getInitialComponent(); CloseableModalController clc = new CloseableModalController(getWindowControl(), translate(NLS_COURSEFOLDER_CLOSE), folderComponent); clc.activate(); + } else if (event.getCommand().equals(CMD_MULTI_SP)) { + removeAsListenerAndDispose(multiSPChooserCtr); + VFSContainer rootContainer = course.getCourseEnvironment().getCourseFolderContainer(); + CourseEditorTreeNode selectedNode = (CourseEditorTreeNode)menuTree.getSelectedNode(); + multiSPChooserCtr = new MultiSPController(ureq, getWindowControl(), rootContainer, ores, selectedNode); + listenTo(multiSPChooserCtr); + + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), translate("close"), multiSPChooserCtr.getInitialComponent()); + listenTo(cmc); + cmc.activate(); } } else if (source == nodeEditCntrllr) { // event from the tabbed pane (any tab) @@ -731,6 +764,17 @@ public class EditorMainController extends MainLayoutBasicController implements G } else { tabbedNodeConfig.setVisible(true); } + } else if (source == multiSPChooserCtr) { + cmc.deactivate(); + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(multiSPChooserCtr); + + if(event == Event.CHANGED_EVENT) { + menuTree.setDirty(true); + euce.getCourseEditorEnv().validateCourse(); + StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus(); + updateCourseStatusMessages(ureq.getLocale(), courseStatus); + } } } catch (RuntimeException e) { log.warn(RELEASE_LOCK_AT_CATCH_EXCEPTION+" [in event(UserRequest,Controller,Event)]", e); @@ -738,6 +782,78 @@ public class EditorMainController extends MainLayoutBasicController implements G throw e; } } + + //fxdiff VCRP-9: drag and drop in menu tree + private void dropNodeAsChild(UserRequest ureq, ICourse course, String droppedNodeId, String targetNodeId, boolean asChild, boolean atTheEnd) { + menuTree.setDirty(true); // setDirty when moving + CourseNode droppedNode = cetm.getCourseNode(droppedNodeId); + + int position; + CourseEditorTreeNode insertParent; + if(asChild) { + insertParent = cetm.getCourseEditorNodeById(targetNodeId); + position = atTheEnd ? -1 : 0; + } else { + CourseEditorTreeNode selectedNode = cetm.getCourseEditorNodeById(targetNodeId); + if(selectedNode.getParent() == null) { + //root node + insertParent = selectedNode; + position = 0; + } else { + insertParent = course.getEditorTreeModel().getCourseEditorNodeById(selectedNode.getParent().getIdent()); + position = 0; + for(position=insertParent.getChildCount(); position-->0; ) { + if(insertParent.getChildAt(position).getIdent().equals(selectedNode.getIdent())) { + position++; + break; + } + } + } + } + + CourseEditorTreeNode moveFrom = course.getEditorTreeModel().getCourseEditorNodeById(droppedNode.getIdent()); + //check if an ancestor is not dropped on a child + if (course.getEditorTreeModel().checkIfIsChild(insertParent, moveFrom)) { + showError("movecopynode.error.overlap"); + fireEvent(ureq, Event.CANCELLED_EVENT); + return; + } + + //don't generate red screen for that. If the position is too high -> add the node at the end + if(position >= insertParent.getChildCount()) { + position = -1; + } + + try { + if(position >= 0) { + insertParent.insert(moveFrom, position); + } else { + insertParent.addChild(moveFrom); + } + } catch (IndexOutOfBoundsException e) { + logError("", e); + //reattach the node as security, if not, the node is lost + insertParent.addChild(moveFrom); + } + + moveFrom.setDirty(true); + //mark subtree as dirty + TreeVisitor tv = new TreeVisitor( new Visitor() { + public void visit(INode node) { + CourseEditorTreeNode cetn = (CourseEditorTreeNode)node; + cetn.setDirty(true); + } + }, moveFrom, true); + tv.visitAll(); + + CourseFactory.saveCourseEditorTreeModel(course.getResourceableId()); + showInfo("movecopynode.info.condmoved"); + ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_EDITOR_NODE_MOVED, getClass()); + + euce.getCourseEditorEnv().validateCourse(); + StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus(); + updateCourseStatusMessages(ureq.getLocale(), courseStatus); + } /* * FIXME:pb:b never used... @@ -934,42 +1050,14 @@ public class EditorMainController extends MainLayoutBasicController implements G final ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId()); CourseConfig cc = course.getCourseEnvironment().getCourseConfig(); if (cc.hasCustomCourseCSS()) { - cssFileRef = cc.getCssLayoutRef(); - mapreg = MapperRegistry.getInstanceFor(ureq.getUserSession()); - if (cssUriMapper != null) { - // deregister old mapper - mapreg.deregister(cssUriMapper); - } - cssUriMapper = new Mapper() { - final VFSContainer courseFolder = course.getCourseFolderContainer(); - - @SuppressWarnings("unused") - public MediaResource handle(String relPath, HttpServletRequest request) { - VFSItem vfsItem = courseFolder.resolve(relPath); - MediaResource mr; - if (vfsItem == null || !(vfsItem instanceof VFSLeaf)) mr = new NotFoundMediaResource(relPath); - else mr = new VFSMediaResource((VFSLeaf) vfsItem); - return mr; - } - }; - - String uri; - // Register mapper as cacheable - String mapperID = VFSManager.getRealPath(course.getCourseFolderContainer()); - if (mapperID == null) { - // Can't cache mapper, no cacheable context available - uri = mapreg.register(cssUriMapper); - } else { - // Add classname to the file path to remove conflicts with other - // usages of the same file path - mapperID = this.getClass().getSimpleName() + ":" + mapperID; - uri = mapreg.registerCacheable(mapperID, cssUriMapper); + CustomCSS localCustomCSS = CourseLayoutHelper.getCustomCSS(ureq.getUserSession(), course.getCourseEnvironment()); + if (localCustomCSS != null) { + String fulluri = localCustomCSS.getCSSURL(); + // path + hc = new HtmlHeaderComponent("custom-css", null, "<link rel=\"StyleSheet\" href=\"" + fulluri + + "\" type=\"text/css\" media=\"screen\"/>"); + main.put("css-inset2", hc); } - final String fulluri = uri + cssFileRef; // the stylesheet's relative - // path - hc = new HtmlHeaderComponent("custom-css", null, "<link rel=\"StyleSheet\" href=\"" + fulluri - + "\" type=\"text/css\" media=\"screen\"/>"); - main.put("css-inset2", hc); } } diff --git a/src/main/java/org/olat/course/editor/MoveCopySubtreeController.java b/src/main/java/org/olat/course/editor/MoveCopySubtreeController.java index 2b49a14dfe236badc06c48b9db7e75c1cf53b3db..fff90b007ccc8573da8ad77525d0b9160319b6fc 100644 --- a/src/main/java/org/olat/course/editor/MoveCopySubtreeController.java +++ b/src/main/java/org/olat/course/editor/MoveCopySubtreeController.java @@ -109,7 +109,7 @@ public class MoveCopySubtreeController extends BasicController { CourseEditorTreeNode insertParent = course.getEditorTreeModel().getCourseEditorNodeById(selectedNode.getIdent()); // check if insert position is within the to-be-copied tree - if (checkIfIsChild(insertParent, moveCopyFrom)) { + if (course.getEditorTreeModel().checkIfIsChild(insertParent, moveCopyFrom)) { this.showError("movecopynode.error.overlap"); fireEvent(ureq, Event.CANCELLED_EVENT); return; @@ -163,28 +163,6 @@ public class MoveCopySubtreeController extends BasicController { } } - /** - * Check if prospectChild is a child of sourceTree. - * - * @param prospectChild - * @param sourceTree - * @return - */ - private boolean checkIfIsChild(CourseEditorTreeNode prospectChild, CourseEditorTreeNode sourceTree) { - // FIXME:ms:b would it be simpler to check the parents? - // INode par; - // for (par = prospectChild.getParent(); par != null && par != sourceTree; - // par = par.getParent()); - // return (par == sourceTree); - ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId()); - if (sourceTree.getIdent().equals(prospectChild.getIdent())) return true; - for (int i = 0; i < sourceTree.getChildCount(); i++) { - INode child = sourceTree.getChildAt(i); - if (checkIfIsChild(prospectChild, course.getEditorTreeModel().getCourseEditorNodeById(child.getIdent()))) return true; - } - return false; - } - protected void doDispose() { // nothing to dispose } diff --git a/src/main/java/org/olat/course/editor/MultiSPController.java b/src/main/java/org/olat/course/editor/MultiSPController.java new file mode 100644 index 0000000000000000000000000000000000000000..c051ba158e9d97cd673b2dedb1723bc4cfc9a564 --- /dev/null +++ b/src/main/java/org/olat/course/editor/MultiSPController.java @@ -0,0 +1,371 @@ +/** + * 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com + * <p> + */ + +package org.olat.course.editor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.vfs.MergeSource; +import org.olat.core.util.vfs.NamedContainerImpl; +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.filters.VFSItemFilter; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.CourseNodeConfiguration; +import org.olat.course.nodes.CourseNodeFactory; +import org.olat.course.nodes.sp.SPEditController; +import org.olat.course.tree.CourseEditorTreeNode; +import org.olat.modules.ModuleConfiguration; + +/** + * + * Description:<br> + * Select elements from the course directory and inject these files + * as single page in the course. + * VCRP-11 + * + * <P> + * Initial Date: 21 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class MultiSPController extends FormBasicController { + + private FormLink selectAll; + private FormLink deselectAll; + private FormLink sameLevel; + private FormLink asChild; + private MultipleSelectionElement rootSelection; + private final Map<String,MultipleSelectionElement> identToSelectionMap = new HashMap<String,MultipleSelectionElement>(); + private final List<MultipleSelectionElement> nodeSelections = new ArrayList<MultipleSelectionElement>(); + + private int position; + private CourseEditorTreeNode selectedNode; + private final OLATResourceable ores; + private final VFSContainer rootContainer; + + public MultiSPController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, + OLATResourceable ores, CourseEditorTreeNode selectedNode) { + super(ureq, wControl, "choosesps"); + + this.ores = ores; + this.selectedNode = selectedNode; + + if(rootContainer instanceof MergeSource) { + //we cannot link to files from course elements or groups folders without single page BB update + VFSContainer realContainer = ((MergeSource)rootContainer).getRootWriteContainer(); + VFSContainer namedRoot = new NamedContainerImpl(rootContainer.getName(), realContainer); + this.rootContainer = namedRoot; + } else { + this.rootContainer = rootContainer; + } + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("multi.sps.title"); + setFormDescription("multi.sps.desc"); + + if(formLayout instanceof FormLayoutContainer) { + FormLayoutContainer layoutContainer = (FormLayoutContainer)formLayout; + rootSelection = initTreeRec(0, rootContainer, layoutContainer); + layoutContainer.contextPut("nodeSelections", nodeSelections); + } + + selectAll = uifactory.addFormLink("checkall", "form.checkall", null, formLayout, Link.LINK); + deselectAll = uifactory.addFormLink("uncheckall", "form.uncheckall", null, formLayout, Link.LINK); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("ok-cancel", getTranslator()); + formLayout.add(buttonLayout); + buttonLayout.setRootForm(mainForm); + + if(selectedNode.getParent() != null) { + sameLevel = uifactory.addFormLink("multi.sps.sameLevel", buttonLayout, Link.BUTTON); + } + asChild = uifactory.addFormLink("multi.sps.asChild", buttonLayout, Link.BUTTON); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + private MultipleSelectionElement initTreeRec(int level, VFSItem item, FormLayoutContainer layoutcont) { + SelectNodeObject node = new SelectNodeObject(item, UUID.randomUUID().toString(), level); + + String[] singleKey = new String[]{ node.getId() }; + String[] singleValue = new String[]{ node.getName() }; + String[] css = new String[]{ "b_with_small_icon_left " + node.getIconCssClass() }; + MultipleSelectionElement nodeSelection = uifactory.addCheckboxesVertical("print.node.list." + nodeSelections.size(), layoutcont, singleKey, singleValue, css, 1); + nodeSelection.setLabel("multi.sps.file", null); + + nodeSelection.setUserObject(node); + nodeSelection.addActionListener(this, FormEvent.ONCLICK); + nodeSelections.add(nodeSelection); + identToSelectionMap.put(node.getId(), nodeSelection); + layoutcont.add(nodeSelection.getComponent().getComponentName(), nodeSelection); + + if(item instanceof VFSContainer) { + VFSContainer container = (VFSContainer)item; + for(VFSItem subItem:container.getItems(new MultiSPVFSItemFilter())) { + MultipleSelectionElement sel = initTreeRec(level + 1, subItem, layoutcont); + node.getChildren().add(sel); + } + } + + return nodeSelection; + } + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(nodeSelections.contains(source)) { + MultipleSelectionElement nodeSelection = (MultipleSelectionElement)source; + if(nodeSelection.isMultiselect()) { + selectRec(nodeSelection, nodeSelection.isSelected(0)); + } + } else if (source == selectAll) { + for(MultipleSelectionElement nodeSelection:nodeSelections) { + if(nodeSelection.isMultiselect() && !nodeSelection.isSelected(0)) { + SelectNodeObject treeNode = (SelectNodeObject)nodeSelection.getUserObject(); + String id = treeNode.getId(); + nodeSelection.select(id, true); + } + } + } else if (source == deselectAll) { + for(MultipleSelectionElement nodeSelection:nodeSelections) { + if(nodeSelection.isMultiselect() && nodeSelection.isSelected(0)) { + SelectNodeObject treeNode = (SelectNodeObject)nodeSelection.getUserObject(); + String id = treeNode.getId(); + nodeSelection.select(id, false); + } + } + } else if (source == asChild) { + position = -1; + ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId()); + create(rootSelection, course, selectedNode.getCourseNode()); + fireEvent(ureq, Event.CHANGED_EVENT); + } else if (source == sameLevel) { + ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId()); + CourseEditorTreeNode parentNode = (CourseEditorTreeNode)selectedNode.getParent(); + position = 0; + for(position = parentNode.getChildCount(); position-->0; ) { + if(selectedNode.getIdent().equals(parentNode.getChildAt(position).getIdent())) { + position++; + break; + } + } + create(rootSelection, course, parentNode.getCourseNode()); + fireEvent(ureq, Event.CHANGED_EVENT); + } else { + super.formInnerEvent(ureq, source, event); + } + } + + private void create(MultipleSelectionElement selection, ICourse course, CourseNode parentNode) { + SelectNodeObject node = (SelectNodeObject)selection.getUserObject(); + if(selection.isMultiselect() && selection.isSelected(0)) { + VFSItem item = node.getItem(); + + CourseNode newNode = null; + if(item instanceof VFSLeaf) { + //create node + newNode = createCourseNode(item, "sp"); + ModuleConfiguration moduleConfig = newNode.getModuleConfiguration(); + String path = getRelativePath(item); + moduleConfig.set(SPEditController.CONFIG_KEY_FILE, path); + moduleConfig.setBooleanEntry(SPEditController.CONFIG_KEY_ALLOW_RELATIVE_LINKS, true); + } else if (item instanceof VFSContainer) { + //add structure + newNode = createCourseNode(item, "st"); + } + + int pos = -1; + if(position >= 0 && selectedNode.getCourseNode().getIdent().equals(parentNode.getIdent())) { + pos = position++; + } + + if(pos < 0 || pos >= parentNode.getChildCount()) { + course.getEditorTreeModel().addCourseNode(newNode, parentNode); + } else { + course.getEditorTreeModel().insertCourseNodeAt(newNode, parentNode, pos); + } + + if (item instanceof VFSContainer) { + parentNode = newNode; + } + } + + //recurse + for(MultipleSelectionElement childElement:node.getChildren()) { + create(childElement, course, parentNode); + } + } + + private CourseNode createCourseNode(VFSItem item, String type) { + CourseNodeConfiguration newNodeConfig = CourseNodeFactory.getInstance().getCourseNodeConfiguration(type); + CourseNode newNode = newNodeConfig.getInstance(); + newNode.setShortTitle(item.getName()); + newNode.setLongTitle(item.getName()); + newNode.setLearningObjectives(item.getName()); + newNode.setNoAccessExplanation("You don't have access"); + return newNode; + } + + private String getRelativePath(VFSItem item) { + String path = ""; + while(item != null && !isSameAsRootContainer(item)) { + path = "/" + item.getName() + path; + item = item.getParentContainer(); + } + return path; + } + + private boolean isSameAsRootContainer(VFSItem item) { + if(item instanceof VFSContainer) { + VFSContainer blocker = rootContainer; + if(blocker instanceof MergeSource) { + blocker = ((MergeSource)blocker).getRootWriteContainer(); + } + return blocker.isSame(item); + } + return false; + } + + /** + * @param nodeSelection The node that should be selected recursively + * @param select true: select the node and its children; false: deselect the node and its children + */ + private void selectRec(MultipleSelectionElement nodeSelection, boolean select) { + SelectNodeObject userObject = (SelectNodeObject)nodeSelection.getUserObject(); + String id = userObject.getId(); + if(nodeSelection.isMultiselect()) { + nodeSelection.select(id, select); + } + + for(MultipleSelectionElement childSelection:userObject.getChildren()) { + selectRec(childSelection, select); + } + } + + public class SelectNodeObject { + private final int indentation; + private final VFSItem item; + private final String id; + private final List<MultipleSelectionElement> children = new ArrayList<MultipleSelectionElement>(); + + public SelectNodeObject(VFSItem item, String id, int indentation) { + this.id = id; + this.item = item; + this.indentation = indentation; + } + + public String getIndentation() { + return Integer.toString(indentation); + } + + public VFSItem getItem() { + return item; + } + + public String getName() { + return item.getName(); + } + + public String getId() { + return id; + } + + public List<MultipleSelectionElement> getChildren() { + return children; + } + + public String getIconCssClass() { + if(item instanceof VFSContainer) { + return "b_filetype_folder"; + } + return CSSHelper.createFiletypeIconCssClassFor(item.getName()); + } + } + + public class MultiFileEvents extends Event { + private final String currentPath; + private final VFSContainer currentContainer; + private final List<String> selectedFiles; + + public MultiFileEvents(VFSContainer currentContainer, String currentPath, List<String> selectedFiles) { + super("multi"); + this.currentContainer = currentContainer; + this.currentPath = currentPath; + this.selectedFiles = selectedFiles; + } + + public String getCurrentPath() { + return currentPath; + } + + public VFSContainer getCurrentContainer() { + return currentContainer; + } + + public List<String> getSelectedFiles() { + return selectedFiles; + } + } + + public class MultiSPVFSItemFilter implements VFSItemFilter { + @Override + public boolean accept(VFSItem vfsItem) { + String name = vfsItem.getName(); + return !name.startsWith("."); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/editor/PublishProcess.java b/src/main/java/org/olat/course/editor/PublishProcess.java index 71b5f8787cd6ebca0e5cd38577b9796597fa5c6c..5695850228273cc57e0a914701271a43bd11ac7e 100644 --- a/src/main/java/org/olat/course/editor/PublishProcess.java +++ b/src/main/java/org/olat/course/editor/PublishProcess.java @@ -28,6 +28,9 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.catalog.CatalogEntry; +import org.olat.catalog.CatalogManager; import org.olat.core.gui.UserRequest; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; @@ -47,11 +50,14 @@ import org.olat.core.util.xml.XStreamHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.Structure; +import org.olat.course.editor.PublishStepCatalog.CategoryLabel; import org.olat.course.nodes.CourseNode; +import org.olat.course.properties.CoursePropertyManager; import org.olat.course.run.RunMainController; import org.olat.course.tree.CourseEditorTreeModel; import org.olat.course.tree.CourseEditorTreeNode; import org.olat.course.tree.PublishTreeModel; +import org.olat.properties.Property; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.controllers.EntryChangedEvent; @@ -512,6 +518,61 @@ public class PublishProcess { } } +//VCRP-3: add catalog entry in publish wizard + protected void publishToCatalog(String choiceValue, List<CategoryLabel> labels) { + + CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); + CourseNode rootNode = course.getRunStructure().getRootNode(); + Property prop = cpm.findCourseNodeProperty(rootNode, null, null, "catalog-choice"); + if(prop == null) { + prop = cpm.createCourseNodePropertyInstance(rootNode, null, null, "catalog-choice", null, null, choiceValue, null); + cpm.saveProperty(prop); + } else { + prop.setStringValue(choiceValue); + cpm.updateProperty(prop); + } + + if("yes".equals(choiceValue) && labels != null) { + CatalogManager cm = CatalogManager.getInstance(); + List<CatalogEntry> refParentCategories = cm.getCatalogCategoriesFor(repositoryEntry); + + a_a: + for(CategoryLabel label:labels) { + CatalogEntry category = label.getCategory(); + CatalogEntry parentCategory = label.getParentCategory(); + if(label.isDeleted()) { + //test + if(category.getKey() != null) { + List<CatalogEntry> children = cm.getChildrenOf(category); + for(CatalogEntry child:children) { + if(child.getRepositoryEntry() != null && child.getRepositoryEntry().equalsByPersistableKey(repositoryEntry)) { + cm.deleteCatalogEntry(child); + } + } + } + } else if(category.getKey() == null) { + //it's a new entry -> check if not already in catalog at this position + for(Iterator<CatalogEntry> refIt=refParentCategories.iterator(); refIt.hasNext(); ) { + CatalogEntry refParentCategory = refIt.next(); + if(refParentCategory.equalsByPersistableKey(parentCategory)) { + refIt.remove(); + break a_a; + } + } + + category.setOwnerGroup(BaseSecurityManager.getInstance().createAndPersistSecurityGroup()); + cm.addCatalogEntry(parentCategory, category); + } else { + for(Iterator<CatalogEntry> refIt=refParentCategories.iterator(); refIt.hasNext(); ) { + CatalogEntry refParentCategory = refIt.next(); + if(refParentCategory.equalsByPersistableKey(category)) { + refIt.remove(); + } + } + } + } + } + } void clearPublishSet() { resultingCourseRun = null; diff --git a/src/main/java/org/olat/course/editor/PublishStep00.java b/src/main/java/org/olat/course/editor/PublishStep00.java index 09a91e09a7f46993d84f25aeef19e2e60be68f28..1ca7f7fedaaf6cff163af616d4901e61416b50cd 100644 --- a/src/main/java/org/olat/course/editor/PublishStep00.java +++ b/src/main/java/org/olat/course/editor/PublishStep00.java @@ -24,6 +24,8 @@ package org.olat.course.editor; import java.util.ArrayList; import java.util.List; +import org.olat.catalog.CatalogEntry; +import org.olat.catalog.CatalogManager; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -42,9 +44,14 @@ import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.id.OLATResourceable; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.CourseModule; import org.olat.course.ICourse; +import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.StatusDescriptionHelper; +import org.olat.course.properties.CoursePropertyManager; import org.olat.course.tree.CourseEditorTreeModel; +import org.olat.properties.Property; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; @@ -60,14 +67,34 @@ class PublishStep00 extends BasicStep { private PublishProcess publishProcess; private OLATResourceable ores; + + private final boolean hasCatalog; + private final boolean hasPublishableChanges; public PublishStep00(UserRequest ureq, CourseEditorTreeModel cetm, ICourse course) { super(ureq); this.ores = course; publishProcess = PublishProcess.getInstance(course, cetm, ureq.getLocale()); + + //VCRP-3: add catalog entry in publish wizard + CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); + CourseNode rootNode = course.getRunStructure().getRootNode(); + + Property prop = cpm.findCourseNodeProperty(rootNode, null, null, "catalog-choice"); + if(prop == null) { + hasCatalog = false; + } else if("no".equals(prop.getStringValue())) { + hasCatalog = true; + } else { + RepositoryEntry repositoryEntry = RepositoryManager.getInstance().lookupRepositoryEntry(ores, true); + hasCatalog = !CatalogManager.getInstance().getCatalogCategoriesFor(repositoryEntry).isEmpty(); + } + + hasPublishableChanges = publishProcess.hasPublishableChanges(); + setI18nTitleAndDescr("publish.header", null); // proceed with direct access as next step. - setNextStep(new PublishStep01(ureq, publishProcess.hasPublishableChanges())); + setNextStep(new PublishStep01(ureq, course, hasPublishableChanges, hasCatalog)); } /** @@ -75,7 +102,8 @@ class PublishStep00 extends BasicStep { */ public PrevNextFinishConfig getInitialPrevNextFinishConfig() { // in any case allow next or finish - if(publishProcess.hasPublishableChanges()){ + //VCRP-3: add catalog entry in publish wizard + if(hasPublishableChanges || !hasCatalog){ //this means we have possible steps 00a (error messages) and 00b (warning messages) return PrevNextFinishConfig.NEXT; }else{ @@ -98,7 +126,12 @@ class PublishStep00 extends BasicStep { * prepares all data needed for next step(s) */ runContext.put("publishProcess", publishProcess); - runContext.put("selectedCourseAccess",String.valueOf(repoEntry.getAccess())); + //fxdiff VCRP-1,2: access control of resources + if(repoEntry.isMembersOnly()) { + runContext.put("selectedCourseAccess", RepositoryEntry.MEMBERS_ONLY); + } else { + runContext.put("selectedCourseAccess",String.valueOf(repoEntry.getAccess())); + } return new PublishStep00Form(ureq, wControl, form, publishProcess, runContext); } diff --git a/src/main/java/org/olat/course/editor/PublishStep01.java b/src/main/java/org/olat/course/editor/PublishStep01.java index 477902a53a2213b71c08afa203a7f3285f54b937..9b4d601298d88c8bd72fec2147dacaba04e56fa8 100644 --- a/src/main/java/org/olat/course/editor/PublishStep01.java +++ b/src/main/java/org/olat/course/editor/PublishStep01.java @@ -29,13 +29,13 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.BasicStep; import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; -import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.util.Util; +import org.olat.course.ICourse; import org.olat.repository.PropPupForm; import org.olat.repository.RepositoryEntry; @@ -52,17 +52,17 @@ class PublishStep01 extends BasicStep { private PrevNextFinishConfig prevNextConfig; private boolean hasPublishableChanges; - public PublishStep01(UserRequest ureq, boolean hasPublishableChanges) { + public PublishStep01(UserRequest ureq, ICourse course, boolean hasPublishableChanges, boolean hasCatalog) { super(ureq); setI18nTitleAndDescr("publish.access.header", null); + //VCRP-3: add catalog entry in publish wizard this.hasPublishableChanges = hasPublishableChanges; - if(hasPublishableChanges){ - setNextStep(new PublishStep00a(ureq)); + setNextStep(new PublishStepCatalog(ureq, course, hasPublishableChanges)); + if(hasCatalog){ prevNextConfig = PrevNextFinishConfig.BACK_NEXT_FINISH; }else{ - setNextStep(Step.NOSTEP); - prevNextConfig = PrevNextFinishConfig.BACK_FINISH; + prevNextConfig = PrevNextFinishConfig.BACK_NEXT; } } @@ -87,11 +87,9 @@ class PublishStep01 extends BasicStep { private SingleSelection accessSelbox; private String selectedAccess; - private boolean hasPublishableChanges2; PublishStep01AccessForm(UserRequest ureq, WindowControl control, Form rootForm, StepsRunContext runContext, boolean hasPublishableChanges2) { super(ureq, control, rootForm, runContext, LAYOUT_VERTICAL, null); - this.hasPublishableChanges2 = hasPublishableChanges2; selectedAccess = (String) getFromRunContext("selectedCourseAccess"); initForm(ureq); } @@ -108,12 +106,8 @@ class PublishStep01 extends BasicStep { //only change if access was changed addToRunContext("changedaccess", newAccess); } - if(hasPublishableChanges2){ - fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); - }else{ - fireEvent(ureq, StepsEvent.INFORM_FINISHED); - } - + + fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); } @Override @@ -127,13 +121,15 @@ class PublishStep01 extends BasicStep { "" + RepositoryEntry.ACC_OWNERS, "" + RepositoryEntry.ACC_OWNERS_AUTHORS, "" + RepositoryEntry.ACC_USERS, - "" + RepositoryEntry.ACC_USERS_GUESTS + "" + RepositoryEntry.ACC_USERS_GUESTS, + RepositoryEntry.MEMBERS_ONLY//fxdiff VCRP-1,2: access control of resources }; String[] values = new String[] { pt.translate("cif.access.owners"), pt.translate("cif.access.owners_authors"), pt.translate("cif.access.users"), pt.translate("cif.access.users_guests"), + pt.translate("cif.access.membersonly"),//fxdiff VCRP-1,2: access control of resources }; //use the addDropDownSingleselect method with null as label i18n - key, because there is no label to set. OLAT-3682 accessSelbox = uifactory.addDropdownSingleselect("accessBox",null, fic, keys, values, null); diff --git a/src/main/java/org/olat/course/editor/PublishStepCatalog.java b/src/main/java/org/olat/course/editor/PublishStepCatalog.java new file mode 100644 index 0000000000000000000000000000000000000000..2d435992a2cb9baebf47e8886bfa880b6dec6f75 --- /dev/null +++ b/src/main/java/org/olat/course/editor/PublishStepCatalog.java @@ -0,0 +1,534 @@ +package org.olat.course.editor; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.olat.catalog.CatalogEntry; +import org.olat.catalog.CatalogManager; +import org.olat.catalog.ui.CatalogAjaxAddController; +import org.olat.catalog.ui.CatalogEntryAddController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.Form; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.tree.SelectionTree; +import org.olat.core.gui.components.tree.TreeEvent; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.ajax.tree.TreeNodeClickedEvent; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.wizard.BasicStep; +import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; +import org.olat.core.gui.control.generic.wizard.Step; +import org.olat.core.gui.control.generic.wizard.StepFormBasicController; +import org.olat.core.gui.control.generic.wizard.StepFormController; +import org.olat.core.gui.control.generic.wizard.StepsEvent; +import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.CourseModule; +import org.olat.course.ICourse; +import org.olat.course.nodes.CourseNode; +import org.olat.course.properties.CoursePropertyManager; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.properties.Property; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; + +/** + * + * Description:<br> + * Step to set the course in the catalog + * + * <P> + * Initial Date: 16 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://wwww.frentix.com + */ +class PublishStepCatalog extends BasicStep { + + private final PrevNextFinishConfig prevNextConfig; + private final CourseEnvironment courseEnv; + private final CourseNode rootNode; + + public PublishStepCatalog(UserRequest ureq, ICourse course, boolean hasPublishableChanges) { + super(ureq); + + this.courseEnv = course.getCourseEnvironment(); + this.rootNode = course.getRunStructure().getRootNode(); + setI18nTitleAndDescr("publish.catalog.header", null); + + if(hasPublishableChanges) { + setNextStep(new PublishStep00a(ureq)); + prevNextConfig = PrevNextFinishConfig.BACK_NEXT_FINISH; + } else { + setNextStep(Step.NOSTEP); + prevNextConfig = PrevNextFinishConfig.BACK_FINISH; + } + } + + @Override + public PrevNextFinishConfig getInitialPrevNextFinishConfig() { + return prevNextConfig; + } + + @Override + public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext stepsRunContext, Form form) { + return new PublishStepCatalogForm(ureq, wControl, form, stepsRunContext, courseEnv, rootNode); + } + + class PublishStepCatalogForm extends StepFormBasicController { + + private FormLink addToCatalog; + private SingleSelection catalogBox; + private CloseableModalController cmc; + private Controller catalogAddController; + private final List<FormLink> deleteLinks = new ArrayList<FormLink>();; + + private final RepositoryEntry repositoryEntry; + private final CatalogManager catalogManager; + private final CourseEnvironment courseEnv; + private final CourseNode rootNode; + + public PublishStepCatalogForm(UserRequest ureq, WindowControl control, Form rootForm, StepsRunContext runContext, + CourseEnvironment courseEnv, CourseNode rootNode) { + super(ureq, control, rootForm, runContext, LAYOUT_CUSTOM, "publish_catalog"); + + OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, courseEnv.getCourseResourceableId()); + repositoryEntry = RepositoryManager.getInstance().lookupRepositoryEntry(ores, true); + catalogManager = CatalogManager.getInstance(); + this.courseEnv = courseEnv; + this.rootNode = rootNode; + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + CoursePropertyManager cpm = courseEnv.getCoursePropertyManager(); + + Property prop = cpm.findCourseNodeProperty(rootNode, null, null, "catalog-choice"); + String value = prop == null ? null : prop.getStringValue(); + + FormItemContainer fc = FormLayoutContainer.createDefaultFormLayout("catalogSettings", getTranslator()); + fc.setRootForm(mainForm); + formLayout.add("catalogSettings", fc); + + final String[] keys = new String[]{"yes","no"}; + final String[] values = new String[] { + translate("yes"), + translate("no") + }; + catalogBox = uifactory.addDropdownSingleselect("catalogBox", "publish.catalog.box", fc, keys, values, null); + catalogBox.addActionListener(this, FormEvent.ONCHANGE); + if(!StringHelper.containsNonWhitespace(value)) { + value = "yes"; + } + catalogBox.select(value, true); + flc.contextPut("choice", value); + boolean activate = "yes".equals(value); + addToCatalog = uifactory.addFormLink("publish.catalog.add", flc, Link.BUTTON_SMALL); + addToCatalog.setVisible(activate); + if(activate) { + List<CatalogEntry> catalogEntries = catalogManager.getCatalogCategoriesFor(repositoryEntry); + for(CatalogEntry entry:catalogEntries) { + CategoryLabel label = new CategoryLabel(entry, entry.getParent(), getPath(entry)); + FormLink link = uifactory.addFormLink(label.getCategoryUUID(), "delete", null, flc, Link.LINK); + link.setUserObject(label); + deleteLinks.add(link); + } + } + flc.contextPut("categories", deleteLinks); + } + + private String getPath(CatalogEntry entry) { + String path = ""; + CatalogEntry tempEntry = entry; + while (tempEntry != null) { + path = "/" + tempEntry.getName() + path; + tempEntry = tempEntry.getParent(); + } + return path; + } + + private void deleteCategory(CategoryLabel category) { + for(FormLink deleteLink:deleteLinks) { + CategoryLabel label = (CategoryLabel)deleteLink.getUserObject(); + if(category.equals(label)) { + label.setDeleted(true); + deleteLink.setVisible(false); + flc.setDirty(true); + } + } + } + + private void doAddCatalog(UserRequest ureq) { + List<CategoryLabel> categories = new ArrayList<CategoryLabel>(); + for(FormLink deleteLink:deleteLinks) { + CategoryLabel label = (CategoryLabel)deleteLink.getUserObject(); + categories.add(label); + } + + removeAsListenerAndDispose(catalogAddController); + if (getWindowControl().getWindowBackOffice().getWindowManager().isAjaxEnabled()) { + catalogAddController = new SpecialCatalogAjaxAddController(ureq, getWindowControl(), repositoryEntry, categories); + } else { + catalogAddController = new SpecialCatalogEntryAddController(ureq, getWindowControl(), repositoryEntry, categories); + } + + listenTo(catalogAddController); + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), "close", catalogAddController.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + catalogBox.clearError(); + if(catalogBox.isOneSelected() && catalogBox.isSelected(0) && isDeleteLinksEmpty()) { + catalogBox.setErrorKey("publish.catalog.error", null); + allOk &= false; + } + return allOk && super.validateFormLogic(ureq); + } + + /** + * Show if there is active delete links (which is the same has having active categories) + * @return + */ + private boolean isDeleteLinksEmpty() { + if(deleteLinks == null || deleteLinks.isEmpty()) return true; + + boolean visible = false; + for(FormLink deleteLink:deleteLinks) { + visible |= deleteLink.isVisible(); + } + return !visible; + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == cmc) { + removeAsListenerAndDispose(catalogAddController); + removeAsListenerAndDispose(cmc); + catalogAddController = null; + cmc = null; + } else if (catalogAddController == source) { + if(event instanceof AddToCategoryEvent) { + AddToCategoryEvent e = (AddToCategoryEvent)event; + CatalogEntry category = e.getCategory(); + CatalogEntry parentCategory = e.getParentCategory(); + CategoryLabel label = new CategoryLabel(category, parentCategory, getPath(parentCategory)); + FormLink link = uifactory.addFormLink(label.getCategoryUUID(), "delete", null, flc, Link.LINK); + link.setUserObject(label); + deleteLinks.add(link); + catalogBox.clearError(); + } else if (event instanceof UndoCategoryEvent) { + UndoCategoryEvent e = (UndoCategoryEvent)event; + CategoryLabel undelete = e.getUndelete(); + for(FormLink deleteLink:deleteLinks) { + CategoryLabel label = (CategoryLabel)deleteLink.getUserObject(); + if(label.equals(undelete)) { + label.setDeleted(false); + deleteLink.setVisible(true); + break; + } + } + } + cmc.deactivate(); + removeAsListenerAndDispose(catalogAddController); + removeAsListenerAndDispose(cmc); + catalogAddController = null; + cmc = null; + } else { + super.event(ureq, source, event); + } + } + + @Override + protected void formOK(UserRequest ureq) { + if(catalogBox.isOneSelected()) { + String val = catalogBox.getSelectedKey(); + addToRunContext("catalogChoice", val); + + List<CategoryLabel> categories = new ArrayList<CategoryLabel>(); + for(FormLink deletedLink:deleteLinks) { + CategoryLabel cat = (CategoryLabel)deletedLink.getUserObject(); + categories.add(cat); + } + addToRunContext("categories", categories); + } + + fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(catalogBox == source) { + //updateCategories(); + catalogBox.clearError(); + if(catalogBox.isOneSelected() && catalogBox.isSelected(0)) { + addToCatalog.setVisible(true); + flc.contextPut("choice", "yes"); + if(deleteLinks == null || deleteLinks.isEmpty()) { + doAddCatalog(ureq); + } + } else { + addToCatalog.setVisible(false); + flc.contextPut("choice", "no"); + } + } else if (source == addToCatalog) { + doAddCatalog(ureq); + } else if (deleteLinks.contains(source)) { + CategoryLabel label = (CategoryLabel)source.getUserObject(); + deleteCategory(label); + //do by delete updateCategories(); + } else { + super.formInnerEvent(ureq, source, event); + } + } + } + + public class SpecialCatalogAjaxAddController extends CatalogAjaxAddController { + + private CategoryLabel undoDelete; + private List<CategoryLabel> categories; + + public SpecialCatalogAjaxAddController(UserRequest ureq, WindowControl wControl, RepositoryEntry toBeAddedEntry, + List<CategoryLabel> categories) { + super(ureq, wControl, toBeAddedEntry); + this.categories = categories; + } + + @Override + protected VelocityContainer createVelocityContainer(String page) { + setTranslator(Util.createPackageTranslator(CatalogAjaxAddController.class, getLocale())); + velocity_root = Util.getPackageVelocityRoot(CatalogAjaxAddController.class); + return super.createVelocityContainer(page); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == treeCtr) { + if (event instanceof TreeNodeClickedEvent) { + TreeNodeClickedEvent clickedEvent = (TreeNodeClickedEvent) event; + // build new entry for this catalog level + CatalogManager cm = CatalogManager.getInstance(); + String nodeId = clickedEvent.getNodeId(); + Long newParentId = Long.parseLong(nodeId); + CatalogEntry newParent = cm.loadCatalogEntry(newParentId); + // check first if this repo entry is already attached to this new parent + for (CategoryLabel label:categories) { + CatalogEntry category = label.getCategory(); + if(category.getKey() == null) { + category = label.getParentCategory(); + } + + if(category.equalsByPersistableKey(newParent)) { + if(label.isDeleted()) { + undoDelete = label; + } else { + showError("catalog.tree.add.already.exists", toBeAddedEntry.getDisplayname()); + return; + } + } + } + // don't create entry right away, user must select submit button first + selectedParent = newParent; + // enable link, set dirty button class and trigger redrawing + selectLink.setEnabled(true); + selectLink.setCustomEnabledLinkCSS("b_button b_button_dirty"); + selectLink.setDirty(true); + } + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if (source == selectLink) { + if(undoDelete != null) { + fireEvent(ureq, new UndoCategoryEvent(undoDelete)); + } else if (selectedParent != null) { + CatalogManager cm = CatalogManager.getInstance(); + CatalogEntry newEntry = cm.createCatalogEntry(); + newEntry.setRepositoryEntry(toBeAddedEntry); + newEntry.setName(toBeAddedEntry.getDisplayname()); + newEntry.setDescription(toBeAddedEntry.getDescription()); + newEntry.setType(CatalogEntry.TYPE_LEAF); + newEntry.setParent(selectedParent); + fireEvent(ureq, new AddToCategoryEvent(newEntry, selectedParent)); + } + } else { + super.event(ureq, source, event); + } + } + } + + public class SpecialCatalogEntryAddController extends CatalogEntryAddController { + + private final RepositoryEntry toBeAddedEntry; + private final List<CategoryLabel> categories; + + public SpecialCatalogEntryAddController(UserRequest ureq, WindowControl wControl, RepositoryEntry toBeAddedEntry, + List<CategoryLabel> categories) { + super(ureq, wControl, toBeAddedEntry); + + this.toBeAddedEntry = toBeAddedEntry; + this.categories = categories; + } + + @Override + protected VelocityContainer createVelocityContainer(String page) { + setTranslator(Util.createPackageTranslator(CatalogAjaxAddController.class, getLocale())); + velocity_root = Util.getPackageVelocityRoot(CatalogAjaxAddController.class); + return super.createVelocityContainer(page); + } + + @Override + public void event(UserRequest ureq, Component source, Event event) { + if (source instanceof SelectionTree) { + TreeEvent te = (TreeEvent) event; + if (te.getCommand().equals(TreeEvent.COMMAND_TREENODE_CLICKED)) { + CatalogManager cm = CatalogManager.getInstance(); + Long newParentId = Long.parseLong(te.getNodeId()); + CatalogEntry newParent = cm.loadCatalogEntry(newParentId); + // check first if this repo entry is already attached to this new parent + for (CategoryLabel label:categories) { + CatalogEntry category = label.getCategory(); + if(category.getKey() == null) { + category = label.getParentCategory(); + } + + if(category.equalsByPersistableKey(newParent)) { + if(label.isDeleted()) { + fireEvent(ureq, new UndoCategoryEvent(label)); + } else { + showError("catalog.tree.add.already.exists", toBeAddedEntry.getDisplayname()); + } + return; + } + } + + CatalogEntry newEntry = cm.createCatalogEntry(); + newEntry.setRepositoryEntry(toBeAddedEntry); + newEntry.setName(toBeAddedEntry.getDisplayname()); + newEntry.setDescription(toBeAddedEntry.getDescription()); + newEntry.setType(CatalogEntry.TYPE_LEAF); + newEntry.setParent(newParent); + fireEvent(ureq, new AddToCategoryEvent(newEntry, newParent)); + + } else if (te.getCommand().equals(TreeEvent.COMMAND_CANCELLED)) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + } + + } + } + + public class UndoCategoryEvent extends Event { + private final CategoryLabel undelete; + + public UndoCategoryEvent(CategoryLabel undelete) { + super("undelete"); + this.undelete = undelete; + } + + public CategoryLabel getUndelete() { + return undelete; + } + } + + public class AddToCategoryEvent extends Event { + + private final CatalogEntry category; + private final CatalogEntry parentCategory; + + public AddToCategoryEvent(CatalogEntry category, CatalogEntry parentCategory) { + super("add-to-catalog"); + this.category = category; + this.parentCategory = parentCategory; + } + + public CatalogEntry getCategory() { + return category; + } + + public CatalogEntry getParentCategory() { + return parentCategory; + } + } + + public class CategoryLabel { + private final CatalogEntry category; + private final CatalogEntry parentCategory; + private final String path; + private final String uuid; + + private boolean deleted = false; + + public CategoryLabel(CatalogEntry category, CatalogEntry parentCategory, String path) { + this.category = category; + this.parentCategory = parentCategory; + this.path = path; + uuid = UUID.randomUUID().toString().replace("-", ""); + } + + public String getCategoryUUID() { + return uuid; + } + + public String getPath() { + return path; + } + + public CatalogEntry getCategory() { + return category; + } + + public CatalogEntry getParentCategory() { + return parentCategory; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if (obj instanceof CategoryLabel) { + CategoryLabel label = (CategoryLabel)obj; + return uuid.equals(label.uuid); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/editor/_chelp/ced-expert-expl.html b/src/main/java/org/olat/course/editor/_chelp/ced-expert-expl.html index eb634cbd26db9519221400af801150460cdd03b9..eda4b898abf81d77cdf4f40dc98732292253cfe3 100644 --- a/src/main/java/org/olat/course/editor/_chelp/ced-expert-expl.html +++ b/src/main/java/org/olat/course/editor/_chelp/ced-expert-expl.html @@ -61,6 +61,10 @@ $r.contextHelpRelativeLink("ced-expert-eg.html") <td><i>$r.translate("chelp.funcCoach")</i></td> <td>$r.translate("chelp.signCoach")</td> </tr> + <tr> + <td><i>$r.translate("chelp.funcParticipant")</i></td> + <td>$r.translate("chelp.signParticipant")</td> + </tr> <tr> <td><i>$r.translate("chelp.funcGuest")</i></td> <td>$r.translate("chelp.signGuest")</td> diff --git a/src/main/java/org/olat/course/editor/_content/choosesps.html b/src/main/java/org/olat/course/editor/_content/choosesps.html new file mode 100644 index 0000000000000000000000000000000000000000..f9bae8c89d314be64759d8f4d7bc178e5c5949ad --- /dev/null +++ b/src/main/java/org/olat/course/editor/_content/choosesps.html @@ -0,0 +1,25 @@ +<div class="b_form #if ($off_css_class) $off_css_class #end b_clearfix"> +#if ($off_title) <fieldset><legend>$off_title</legend> #end +#if ($off_chelp_package) $r.contextHelpWithWrapper("$off_chelp_package","$off_chelp_page","$off_chelp_hover") #end +#if ($off_desc) <div class="b_form_desc">$off_desc</div> #end + + #foreach($nodeSelection in $nodeSelections) + <div style="margin-left:${nodeSelection.getUserObject().getIndentation()}0px;"> + $r.render($nodeSelection.getComponent().getComponentName()) + </div> + #end + + <br/> + <div class="b_togglecheck"> + <input type="checkbox" checked="checked" disabled="disabled" />$r.render("checkall") + <input type="checkbox" disabled="disabled" />$r.render("uncheckall") + </div> + + <div class="b_form_element_wrapper"> + <div class="b_form_element"> + $r.render("ok-cancel") + </div> + </div> + +#if ($off_title) </fieldset> #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/editor/_content/publish_catalog.html b/src/main/java/org/olat/course/editor/_content/publish_catalog.html new file mode 100644 index 0000000000000000000000000000000000000000..7bc1478c8c17c0169aab1202bf89fc8122304d8e --- /dev/null +++ b/src/main/java/org/olat/course/editor/_content/publish_catalog.html @@ -0,0 +1,31 @@ +<div class="o_course_editor_publish"> + <h4>$r.translate("publish.catalog.header")</h4> + <div class='b_info'> + <p>$r.translate("publish.catalog.reminder1")</p> + <p>$r.translate("publish.catalog.reminder2")</p> + </div> + <div class="b_clearfix"> + $r.render("catalogSettings") + </div> + <div class="b_clearfix"> + <div class="b_float_right"> + $r.render("publish.catalog.add") + </div> + </div> + #if($choice == 'yes') + <div class="b_clearfix o_course_editor_categories"> + #foreach($category in $categories) + #if(!$category.getUserObject().isDeleted()) + <div class="b_clearfix"> + <div class="b_c75l"> + $category.getUserObject().getPath() + </div> + <div class="b_c25r"> + $r.render($category.getComponent().getComponentName()) + </div> + </div> + #end + #end + </div> + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/editor/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/editor/_i18n/LocalStrings_de.properties index 73f29f45aba81ebf2574d727fe5149267d40b68a..b068606c1286ff2b8b84cb8f0c48ec0e7e7e1072 100644 --- a/src/main/java/org/olat/course/editor/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/editor/_i18n/LocalStrings_de.properties @@ -206,6 +206,7 @@ chelp.funcArea=<i>inLearningArea("</i>$\:chelp.string<i>")</i> chelp.funcAtte=<i>getAttempts("</i>$\:chelp.int<i>")</i> chelp.funcAuth=isGlobalAuthor(0) chelp.funcCoach=isCourseCoach(0) +chelp.funcParticipant=isCourseParticipant(0) chelp.funcCourPass=<i>getPassedWithCourseId("</i>$\:chelp.int1<i>","</i>$\:chelp.int2<i>")</i> chelp.funcCourScor=<i>getScoreWithCourseId("</i>$\:chelp.int1<i>","</i>$\:chelp.int2<i>")</i> chelp.funcDate=<i>date("</i>$\:chelp.date<i>")</i> @@ -252,7 +253,8 @@ chelp.signAnd=Logisches UND chelp.signArea=Gibt $\:chelp.wordTrue f\u00FCr alle Mitglieder der Gruppen im Lernbereich $\:chelp.string chelp.signAtte=Gibt vom Kursbaustein mit spezifizierter ID die Anzahl abgeschlossener Versuche zur\u00FCck. Kann auf Kursbausteine vom Typ $\:chelp.iqtest, $\:chelp.iqself, $\:chelp.iqsurv (m\u00F6gliche R\u00FCckgabewerte 0 oder 1) und $\:chelp.ta (R\u00FCckgabewert \= Anzahl abgegebener Dateien) angewendet werden. chelp.signAuth=Gibt $\:chelp.wordTrue f\u00FCr alle Mitglieder der OLAT-Autorengruppe -chelp.signCoach=Gibt $\:chelp.wordTrue f\u00FCr alle Benutzer, die eine Lerngruppe betreuen +chelp.signCoach=Gibt $\:chelp.wordTrue f\u00FCr alle Benutzer, die eine Lerngruppe oder den gesamten Kurs betreuen +chelp.signParticipant=Gibt $\:chelp.wordTrue f\u00FCr alle Teilnehmer des Kurses chelp.signCourPass=Gibt vom Kursbaustein mit ID\=$\:chelp.int2 des Kurses mit ID\=$\:chelp.int1 den Boolean $\:chelp.wordTrue (\=Bestanden) oder $\:chelp.wordFalse (\=Nicht bestanden) zur\u00FCck chelp.signCourScor=Gibt vom Kursbaustein mit ID\=$\:chelp.int2 des Kurses mit ID\=$\:chelp.int1 die Anzahl Punkte zur\u00FCck chelp.signDate=Datum abfragen @@ -335,6 +337,13 @@ command.deletenode=L\u00F6schen command.deletenode.header=Kursbaustein \u00E4ndern command.movenode=Verschieben command.publish=Publizieren +command.admin.header=Sammelfunktion +command.multi.sps=Mehrere Einzelseiten +multi.sps.title= W\u00E4hlen Sie die Dateien. +multi.sps.desc=W\u00E4hlen Sie eine oder mehrere Dateien aus um diese als Einzelseiten im Kurs anzulegen. +multi.sps.sameLevel=Nach dem aktuellen Kursbaustein einf\u00FCgen +multi.sps.asChild=Als Kind des aktuellen Kursbausteins einf\u00FCgen +multi.sps.file=Datei condition.noAccessExplanation.title=Information wenn sichtbar und kein Zugang condition.visibility.title=Sichtbarkeit copy.course.element.title=Kursbaustein kopieren @@ -429,6 +438,12 @@ pbl.nothing=Es gibt keine \u00C4nderungen, die publiziert werden k\u00F6nnen. De pbl.nothing.button=Zur\u00FCck zum Editor pbl.success=Die gew\u00E4hlten \u00C4nderungen sind publiziert. publish.access.header=\u00C4nderung des Kurszugriffs +publish.catalog.header=Katalogeintrag erstellen +publish.catalog.box=Kurs in Katalog hinzuf\u00FCgen +publish.catalog.add= In Katalog hinzuf\u00FCgen +publish.catalog.error=Sie m\u00FCssen mindestens eine Katalogeintrag erstellen. +publish.catalog.reminder1=Ohne Katalogeintrag sind Kurse f\u00FCr Studierende schlecht auffindbar. W\u00E4hlen Sie daher ein oder mehrere Bereiche aus dem Katalog aus, in denen dieser Kurs aufgelistet sein soll. Sie k\u00F6nnen jederzeit weitere Katalogeintr\u00E4ge hinzuf\u00FCgen oder bestehende wieder entfernen. +publish.catalog.reminder2=Hinweis: Ein Kurs wird im Katalog erst sichtbar, wenn im vorherigen Schritt "\u00C4nderung des Kurszugriffs" die Option "Alle registrierten OLAT Benutzer" gew\u00E4hlt wurde. W\u00E4hlen Sie "Zur\u00FCck" um die Zugriffsrechte zu \u00E4ndern. publish.header=Publizieren der \u00C4nderungen von Kursbausteinen publish.notpossible.setincomplete=Die Kombination der zu publizierenden Bausteine ergibt einen ung\u00FCltigen Kurs. \u00DCberpr\u00FCfen Sie die folgende Abh\u00E4ngigkeitsliste, um die Probleme zu beheben. publish.nowarnings=Keine Probleme gefunden diff --git a/src/main/java/org/olat/course/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/editor/_i18n/LocalStrings_en.properties index d5dd3fe93bed10621715090c72418022cbf34dff..f43df4f17a34ad83914e4056ce38a7948e89fe91 100644 --- a/src/main/java/org/olat/course/editor/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/editor/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Wed Jan 26 19:00:02 CET 2011 +#Thu May 26 10:56:34 CEST 2011 access.form.label=Access to entire course have access.legend=Modify access to entire course apply=OK @@ -202,6 +202,7 @@ chelp.funcInit=<i>getInitialEnrollmentDate("</i>$\:chelp.int<i>")</i> chelp.funcInitCourse=<i>getInitialCourseLaunchDate(0)</i> chelp.funcLearn=<i>inLearningGroup("</i>$\:chelp.string<i>")</i> chelp.funcLearningGroupFull=<i>isLearningGroupFull("</i>$\:chelp.string<i>")</i> +chelp.funcParticipant=isCourseParticipant(0) chelp.funcPass=<i>getPassed("</i>$\:chelp.int<i>")</i> chelp.funcRece=<i>getRecentEnrollmentDate("</i>$\:chelp.int<i>")</i> chelp.funcReceCourse=<i>getRecentCourseLaunchDate(0)</i> @@ -265,6 +266,7 @@ chelp.signO7=Division chelp.signO8=Addition chelp.signO9=Subtraction chelp.signOR=Logical OR +chelp.signParticipant=Returns $\:chelp.wordTrue for all participants of this course chelp.signPass=Generates the Boolean $\:chelp.wordTrue (\=Passed) or $\:chelp.wordFalse (\=Failed) from a course element with specified ID chelp.signRece=Generates the date of the last registration of the relevant course participant from the course element $\:chelp.en with specified ID. chelp.signReceCourse=Generates the date of a course participant's last course attendance. @@ -314,6 +316,7 @@ chelp.visF=<i>«$\:chelp.vis»</i> chelp.wordFalse=FALSE chelp.wordOr=or chelp.wordTrue=TRUE +command.admin.header=Collecting function command.closeeditor=Close editor command.copynode=Copy command.coursefolder=Storage folder @@ -321,10 +324,13 @@ command.coursepreview=Course preview command.deletenode=Delete command.deletenode.header=Modify course element command.movenode=Move +command.multi.sps=Multiple single pages command.publish=Publish condition.noAccessExplanation.title=Information if visible and no access condition.visibility.title=Visibility copy.course.element.title=Copy course element +course.building.block.disabled=This course element type has been deactivated. Please contact the system administrator or delete the course element from the course. +course.building.block.disabled.user=This course element type has been deactivated. Please contact the system or course administrator. coursefolder.close=Close storage folder coursefolder.name=Storage folder coursestatus.ok.message=No errors found in the editor of this course. @@ -385,6 +391,11 @@ movecopynode.error.rootnode=The main course element cannot be moved or copied. movecopynode.error.selectfirst=Please select a course element of your course first. movecopynode.info.condmoved=Course element moved successfully. Please check any restrictions regarding visibility or access as those might be invalid now. movecopynode.invalid.target=Moving/copying not possible\: the target node is part of the tree selected. Please choose another position using the tree on the left\! +multi.sps.asChild=Add as child of current course element +multi.sps.desc=Select one or multiple files to add them as single pages to the course menu +multi.sps.file=File +multi.sps.sameLevel=Add after current course element +multi.sps.title=Select the files. node.insert.info=Please select the position of the course element "{0}" you want to insert\! node.move.info=Please select the position of the course element "{0}" and its sub-elements you want to move\! nodeConfigForm.content_only=Content only @@ -417,6 +428,12 @@ pbl.nothing.button=Back to editor pbl.remind.catalog=In case you unlock your course for registered users or guests please remember to indicate this in the catalog under the corresponding heading. pbl.success=Selected modifications published successfully. publish.access.header=Modification of course access +publish.catalog.add=Add to catalog +publish.catalog.box=Add course to catalog +publish.catalog.error=You must create at least one catalog entry. +publish.catalog.header=Create catalog entry +publish.catalog.reminder1=Without catalog entry courses are difficult to find for students. Therefore it is recommended to select one or multiple catalog levels in which the course should be enlisted. You can add additional or remove existing catalog entries at any time. +publish.catalog.reminder2=Note\: a course is visible in the catalog only when the course access is set to all "Registered users" or "Registered users and guests" in the previous step. Select "back" to change this access settings. publish.header=How to publish modifications of course elements publish.notpossible.setincomplete=A combination of elements to be published will result in an invalid course structure. Check the following list of dependencies to resolve this problem. publish.nowarnings=No problems found diff --git a/src/main/java/org/olat/course/nodes/BlogCourseNode.java b/src/main/java/org/olat/course/nodes/BlogCourseNode.java index b12ee1a70375861d4ee52eff0a8edfe8fedd7551..ed03d540735435705447b80a3ed0069654574ac0 100644 --- a/src/main/java/org/olat/course/nodes/BlogCourseNode.java +++ b/src/main/java/org/olat/course/nodes/BlogCourseNode.java @@ -48,6 +48,7 @@ import org.olat.modules.webFeed.ui.FeedMainController; import org.olat.modules.webFeed.ui.FeedUIFactory; import org.olat.modules.webFeed.ui.blog.BlogUIFactory; import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -99,7 +100,9 @@ public class BlogCourseNode extends AbstractFeedCourseNode { String nodeId = this.getIdent(); boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); - FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isGuest); + //fxdiff BAKS-18 + boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), entry); + FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isOwner, isGuest); ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(this)); FeedMainController blogCtr = BlogUIFactory.getInstance(ureq.getLocale()).createMainController(entry.getOlatResource(), ureq, wControl, callback, courseId, nodeId); @@ -126,7 +129,9 @@ public class BlogCourseNode extends AbstractFeedCourseNode { String nodeId = this.getIdent(); boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); - FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isGuest); + //fxdiff BAKS-18 + boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), entry); + FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isOwner, isGuest); FeedUIFactory uiFactory = BlogUIFactory.getInstance(ureq.getLocale()); Controller peekViewController = new FeedPeekviewController(entry.getOlatResource(), ureq, wControl, callback, courseId, nodeId, uiFactory, 2, "o_blog_peekview"); return peekViewController; diff --git a/src/main/java/org/olat/course/nodes/CPCourseNode.java b/src/main/java/org/olat/course/nodes/CPCourseNode.java index e8e52496eec242ecec55dfb0c26c4fe841db5468..2f5580ab708889791c904cf5480cf61cc0500a71 100644 --- a/src/main/java/org/olat/course/nodes/CPCourseNode.java +++ b/src/main/java/org/olat/course/nodes/CPCourseNode.java @@ -174,6 +174,8 @@ public class CPCourseNode extends AbstractAccessableCourseNode { // use defaults for new course building blocks config.setBooleanEntry(NodeEditController.CONFIG_STARTPAGE, Boolean.FALSE.booleanValue()); config.setBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU, Boolean.TRUE.booleanValue()); + //fxdiff VCRP-13: cp navigation + config.setBooleanEntry(CPEditController.CONFIG_SHOWNAVBUTTONS, Boolean.TRUE.booleanValue()); config.setConfigurationVersion(2); } else { config.remove(NodeEditController.CONFIG_INTEGRATION); @@ -193,6 +195,12 @@ public class CPCourseNode extends AbstractAccessableCourseNode { config.set(NodeEditController.CONFIG_JS_ENCODING, NodeEditController.CONFIG_JS_ENCODING_AUTO); config.setConfigurationVersion(3); } + + //fxdiff VCRP-13: cp navigation + if(config.getConfigurationVersion() < 4) { + config.setBooleanEntry(CPEditController.CONFIG_SHOWNAVBUTTONS, Boolean.TRUE.booleanValue()); + config.setConfigurationVersion(4); + } // else node is up-to-date - nothing to do } } diff --git a/src/main/java/org/olat/course/nodes/ObjectivesHelper.java b/src/main/java/org/olat/course/nodes/ObjectivesHelper.java index 7a757f4979082e1d1494e4fcf1c3f590eb12838d..102cc95987dbc5d73538c4aa24a3d270b3e9a779 100644 --- a/src/main/java/org/olat/course/nodes/ObjectivesHelper.java +++ b/src/main/java/org/olat/course/nodes/ObjectivesHelper.java @@ -21,6 +21,8 @@ package org.olat.course.nodes; +import java.util.Locale; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -45,8 +47,11 @@ public class ObjectivesHelper { * @return the wrapper component */ public static Component createLearningObjectivesComponent(String learningObjectives, UserRequest ureq) { - VelocityContainer vc = new VelocityContainer("learningObjs", VELOCITY_ROOT + "/objectives.html", new PackageTranslator(PACKAGE, ureq - .getLocale()), null); + return createLearningObjectivesComponent(learningObjectives, ureq.getLocale()); + } + //fxdiff FXOLAT-116: SCORM improvements + public static Component createLearningObjectivesComponent(String learningObjectives, Locale locale) { + VelocityContainer vc = new VelocityContainer("learningObjs", VELOCITY_ROOT + "/objectives.html", new PackageTranslator(PACKAGE, locale), null); vc.contextPut("learningObjectives", learningObjectives); return vc; } diff --git a/src/main/java/org/olat/course/nodes/PodcastCourseNode.java b/src/main/java/org/olat/course/nodes/PodcastCourseNode.java index ba0b1d8763c73c2fbbb1a0097b0d8b42faafd301..99d41cf070ee1b9e11fddb0dd406cddf29992033 100644 --- a/src/main/java/org/olat/course/nodes/PodcastCourseNode.java +++ b/src/main/java/org/olat/course/nodes/PodcastCourseNode.java @@ -48,6 +48,7 @@ import org.olat.modules.webFeed.ui.FeedMainController; import org.olat.modules.webFeed.ui.FeedUIFactory; import org.olat.modules.webFeed.ui.podcast.PodcastUIFactory; import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -99,7 +100,9 @@ public class PodcastCourseNode extends AbstractFeedCourseNode { String nodeId = this.getIdent(); boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); - FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isGuest); + //fxdiff BAKS-18 + boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), entry); + FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isOwner, isGuest); ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrap(this)); FeedMainController podcastCtr = PodcastUIFactory.getInstance(ureq.getLocale()).createMainController(entry.getOlatResource(), ureq, control, callback, courseId, nodeId); @@ -125,7 +128,9 @@ public class PodcastCourseNode extends AbstractFeedCourseNode { String nodeId = this.getIdent(); boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin(); boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); - FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isGuest); + //fxdiff BAKS-18 + boolean isOwner = RepositoryManager.getInstance().isOwnerOfRepositoryEntry(ureq.getIdentity(), entry); + FeedSecurityCallback callback = new FeedNodeSecurityCallback(ne, isAdmin, isOwner, isGuest); FeedUIFactory uiFactory = PodcastUIFactory.getInstance(ureq.getLocale()); Controller peekViewController = new FeedPeekviewController(entry.getOlatResource(), ureq, wControl, callback, courseId, nodeId, uiFactory, 2, "o_podcast_peekview"); return peekViewController; diff --git a/src/main/java/org/olat/course/nodes/STCourseNode.java b/src/main/java/org/olat/course/nodes/STCourseNode.java index 8d02de715047255a18de90852f6cdd4fcc57e651..fb6db6dcc702ecb17f0cfdc89634150ed914d49e 100644 --- a/src/main/java/org/olat/course/nodes/STCourseNode.java +++ b/src/main/java/org/olat/course/nodes/STCourseNode.java @@ -139,6 +139,9 @@ public class STCourseNode extends AbstractAccessableCourseNode implements Assess // reuse the Run controller from the "Single Page" building block, since // we need to do exactly the same task Boolean allowRelativeLinks = getModuleConfiguration().getBooleanEntry(STCourseNodeEditController.CONFIG_KEY_ALLOW_RELATIVE_LINKS); + if(allowRelativeLinks == null) { + allowRelativeLinks = Boolean.FALSE; + } OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, userCourseEnv.getCourseEnvironment().getCourseResourceableId()); SinglePageController spCtr = new SinglePageController(ureq, wControl, userCourseEnv.getCourseEnvironment().getCourseFolderContainer(), relPath, null, allowRelativeLinks .booleanValue(), ores); @@ -189,7 +192,23 @@ public class STCourseNode extends AbstractAccessableCourseNode implements Assess // displayed in the ST-Runcontroller return new NodeRunConstructionResult(cont); } - + + /** + * Checks if the given CourseNode is of type "Structure Node" and if it is set + * to delegate to it's first visible child + * + * @param nodeToCheck + * @return returns true if the given coursenNode is a STCourseNode and is configured to delegate + */ + public static boolean isDelegatingSTCourseNode(CourseNode nodeToCheck) { + if (!(nodeToCheck instanceof STCourseNode)) return false; + + STCourseNode node = (STCourseNode) nodeToCheck; + String displayMode = node.getModuleConfiguration().getStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE, + STCourseNodeEditController.CONFIG_VALUE_DISPLAY_TOC); + return (STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE.equals(displayMode)); + } + /** * @see org.olat.course.nodes.GenericCourseNode#createPreviewController(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.WindowControl, diff --git a/src/main/java/org/olat/course/nodes/ScormCourseNode.java b/src/main/java/org/olat/course/nodes/ScormCourseNode.java index 69511c9ee53304bf467de34b06ce7bab508f1155..5c361dcd960fd1efdb7d8764c61fde0435aa9699 100644 --- a/src/main/java/org/olat/course/nodes/ScormCourseNode.java +++ b/src/main/java/org/olat/course/nodes/ScormCourseNode.java @@ -59,7 +59,7 @@ import org.olat.repository.RepositoryEntryImportExport; */ public class ScormCourseNode extends AbstractAccessableCourseNode implements AssessableCourseNode { private static final String TYPE = "scorm"; - private static final int CURRENT_CONFIG_VERSION = 3; + private static final int CURRENT_CONFIG_VERSION = 4; /** * Constructor for a course building block of the type IMS CP learning content @@ -179,6 +179,12 @@ public class ScormCourseNode extends AbstractAccessableCourseNode implements Ass config.setBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU, Boolean.TRUE.booleanValue()); config.setBooleanEntry(ScormEditController.CONFIG_SHOWNAVBUTTONS, Boolean.TRUE.booleanValue()); config.set(ScormEditController.CONFIG_HEIGHT, ScormEditController.CONFIG_HEIGHT_AUTO); + //fxdiff FXOLAT-116: SCORM improvements + config.setBooleanEntry(ScormEditController.CONFIG_FULLWINDOW, false); + config.setBooleanEntry(ScormEditController.CONFIG_CLOSE_ON_FINISH, false); + config.setBooleanEntry(ScormEditController.CONFIG_ADVANCESCORE, true); + config.setBooleanEntry(ScormEditController.CONFIG_ATTEMPTSDEPENDONSCORE, false); + config.setIntValue(ScormEditController.CONFIG_MAXATTEMPTS, 0); config.setConfigurationVersion(1); } else { int version = config.getConfigurationVersion(); @@ -198,6 +204,17 @@ public class ScormCourseNode extends AbstractAccessableCourseNode implements Ass config.set(NodeEditController.CONFIG_CONTENT_ENCODING, NodeEditController.CONFIG_CONTENT_ENCODING_AUTO); config.set(NodeEditController.CONFIG_JS_ENCODING, NodeEditController.CONFIG_JS_ENCODING_AUTO); } + + if (version == 3) { + version = 4; + //fxdiff FXOLAT-116: SCORM improvements + config.setBooleanEntry(ScormEditController.CONFIG_FULLWINDOW, false); + config.setBooleanEntry(ScormEditController.CONFIG_CLOSE_ON_FINISH, false); + config.setBooleanEntry(ScormEditController.CONFIG_ADVANCESCORE, false); + config.setBooleanEntry(ScormEditController.CONFIG_ATTEMPTSDEPENDONSCORE, false); + config.setIntValue(ScormEditController.CONFIG_MAXATTEMPTS, 0); + } + //version is now set to current version config.setConfigurationVersion(CURRENT_CONFIG_VERSION); } diff --git a/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java b/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java index 9e1fb55116dbed179e4b66d5df2adf61eb903bca..90cfee41f306a6711a25f0dbd1e3a6de23a8fb19 100644 --- a/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java +++ b/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java @@ -36,11 +36,17 @@ import org.olat.core.id.Identity; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.util.notifications.SubscriptionContext; +import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +import org.olat.course.CourseFactory; import org.olat.course.CourseModule; +import org.olat.course.ICourse; +import org.olat.course.groupsandrights.CourseRights; import org.olat.course.nodes.BCCourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.NodeEvaluation; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -75,7 +81,24 @@ public class BCCourseNodeRunController extends DefaultController implements Acti OlatNamedContainerImpl namedContainer = BCCourseNode.getNodeFolderContainer((BCCourseNode) ne.getCourseNode(), courseEnv); VFSSecurityCallback scallback = new FolderNodeCallback(namedContainer.getRelPath(), ne, isOlatAdmin, isGuestOnly, nodefolderSubContext); namedContainer.setLocalSecurityCallback(scallback); - frc = new FolderRunController(namedContainer, false, true, ureq, getWindowControl()); + + // fxdiff VCRP-12: copy files from course folder + // Allow copying of files from course folder if allowed to write and user + // has course editor rights (course owner and users in a right group with + // the author right) + VFSContainer courseContainer = null; + if(scallback.canWrite() && scallback.canCopy()) { + Identity identity = ureq.getIdentity(); + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId()); + RepositoryManager rm = RepositoryManager.getInstance(); + RepositoryEntry entry = rm.lookupRepositoryEntry(course, true); + if (isOlatAdmin || rm.isOwnerOfRepositoryEntry(identity, entry) + || courseEnv.getCourseGroupManager().hasRight(identity, CourseRights.RIGHT_COURSEEDITOR)) { + // use course folder as copy source + courseContainer = courseEnv.getCourseFolderContainer(); + } + } + frc = new FolderRunController(namedContainer, false, true, true, ureq, getWindowControl(), null, null, courseContainer); setInitialComponent(frc.getInitialComponent()); } diff --git a/src/main/java/org/olat/course/nodes/bc/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/bc/_i18n/LocalStrings_en.properties index 56e408adf992e5f3fd2f9ab1684af9537951a26f..b7b15c80469f7c4d5cd892625b4e9f08d5b9b725 100644 --- a/src/main/java/org/olat/course/nodes/bc/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/bc/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Mon Aug 16 13:06:35 CEST 2010 +#Thu Sep 08 17:28:38 CEST 2011 chelp.acc=<b>$org.olat.course.nodes.bc\:pane.tab.accessibility</b> chelp.bc=$org.olat.course.nodes\:title_bc chelp.bc1=You can store documents in this folder for your participants to download. @@ -19,6 +19,7 @@ peekview.allItemsLink=All documents preview.canDownload=Download allowed preview.canUpload=Upload allowed preview.conf.toggle=Show/hide configuration +preview.downloadfile=Download files preview.header=Configuration folder for simulated user preview.info=The content of this folder is shown below (read only for preview\!) preview.quotaKB=Quota in KB diff --git a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties index 6a47e86a2d0b4b8b7e36473701c2dc2b7a0162e1..a44c487f3519fec8c2b2b2c29861df7f24647edd 100644 --- a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties @@ -1,7 +1,7 @@ #Mon Mar 02 09:54:04 CET 2009 chelp.ced-co.title=E-Mail-Baustein: Empf\u00E4nger eingeben chelp.co0=Sie haben zwei Möglichkeiten, um Nachrichten zu versenden. Entweder können Sie die Gruppen und Lernbereiche auswählen, an deren Mitglieder eine Nachricht versendet werden soll oder Sie geben direkt die E-Mail-Adresse von bestimmten Personen ein. -chelp.co0a=Markieren Sie die Checkbox, um die Lerngruppen und Lernbereiche zu definieren, deren Mitglieder und/oder Betreuer Sie anschreiben möchten. +chelp.co0a=Markieren Sie die Checkbox, um die Lerngruppen und Lernbereiche zu definieren, deren Mitglieder Sie anschreiben möchten. Markieren Sie in einem zweiten Schritt, ob Sie Teilnehmer und/oder Betreuer anschreiben möchten. Klicken Sie keine Checkbox an, wird kein Mail verschickt. chelp.co0b= Markieren Sie die Checkbox, wenn Sie die Nachrichten an bestimmte Personen versenden möchten. chelp.co1=Geben Sie hierzu im Feld "$\:message.emailtoadresses" eine oder mehrere E-Mailadressen ein, an die die Nachricht aus dem Kurs abgeschickt wird. Bei mehreren Adressaten, müssen die E-Mailadressen durch einen Zeilenumbruch getrennt sein, d.h. jede E-Mailadresse muss auf einer eigenen Zeile stehen. chelp.co2=Geben Sie hier den Inhalt der Nachricht ein @@ -23,7 +23,7 @@ form.message.group=Lerngruppen header=Empf\u00E4nger help.hover.co=Hilfe zur Konfiguration der Empf\u00E4nger message.body=Nachricht (Vorlage) -message.want.group=Versand an Gruppenmitglieder oder Betreuer +message.want.group=Versand an Gruppenmitglieder message.want.email=Versand an E-Mailadressen message.emailtoadresses=E-Mailadressen message.subject=Betreff (Vorlage) diff --git a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties index cd4e537059af5814759e5f70f07da706c1c6d6f1..2c00dd359b1915bcb6c01b1e0cfb06130ec289d4 100644 --- a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties @@ -1,7 +1,7 @@ #Sun Aug 15 18:44:08 CEST 2010 chelp.ced-co.title=E-mail element\: indicate recipients chelp.co0=There are two possibilities to send messages. Either by selecting groups and learning areas whose members you want to send a message or by indicating the e-mail address of a specific person. -chelp.co0a=By checking this box you define learning groups and learning areas whose members (and/or tutors) you would like to send a message. +chelp.co0a=By checking this box you define learning groups and learning areas whose members you would like to send a message. In a second step you have to choose if you want to send a message to participants and/or tutors (else no mail is sent). chelp.co0b=Check this box if you would like to send messages to specific persons. chelp.co1=Please enter one or more e-mail addresses in the field "$\:message.emailtoadresses" to send messages from your course. If there are more than one recipients their addresses have to be separated by line breaks, i.e. each e-mail address has to be put into one separate row. chelp.co2=Enter your message text here @@ -22,7 +22,7 @@ form.areanames.wrong=Please indicate titles of learning areas separated by comma form.choose.coachesandpartips=You have to select some participants or tutors form.groupnames.wrong=Please indicate titles of learning groups separated by commas or leave this box blank. form.message.area=Learning areas -form.message.chckbx.coaches=Coaches of selected learning groups +form.message.chckbx.coaches=Tutors of selected learning groups form.message.chckbx.partips=Participants of selected learning groups form.message.example.area=(Example\: Gr_1, Gr_2) form.message.example.group=(Example\: Red, Green, Blue) @@ -34,7 +34,7 @@ message.body=Message (template) message.emailtoadresses=E-mail addresses message.subject=Subject (template) message.want.email=Distribution to e-mail addresses -message.want.group=Distribution to group members or tutors +message.want.group=Distribution to group members no.recipents.specified=Please select at least one recipient category pane.tab.accessibility=Access pane.tab.coconfig=Recipient diff --git a/src/main/java/org/olat/course/nodes/cp/CPEditController.java b/src/main/java/org/olat/course/nodes/cp/CPEditController.java index 627f69a3cc399c8e87235517daa5064b92d4a637..6c193cb7aa5fe8a5c14721f1ffcd1cf2616894a8 100644 --- a/src/main/java/org/olat/course/nodes/cp/CPEditController.java +++ b/src/main/java/org/olat/course/nodes/cp/CPEditController.java @@ -27,8 +27,8 @@ import java.util.Arrays; import java.util.Locale; import java.util.Map; -import org.olat.basesecurity.Constants; import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.Constants; import org.olat.core.commons.fullWebApp.LayoutMain3ColsPreviewController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -57,7 +57,6 @@ import org.olat.course.condition.ConditionEditController; import org.olat.course.editor.NodeEditController; import org.olat.course.nodes.CPCourseNode; import org.olat.course.nodes.CourseNodeFactory; -import org.olat.course.nodes.scorm.ScormEditController; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.ImsCPFileResource; @@ -80,6 +79,8 @@ public class CPEditController extends ActivateableTabbableDefaultController impl private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility"; private static final String CONFIG_KEY_REPOSITORY_SOFTKEY = "reporef"; private static final String VC_CHOSENCP = "chosencp"; + //fxdiff VCRP-13: cp navigation + public static final String CONFIG_SHOWNAVBUTTONS = "shownavbuttons"; // NLS support: private static final String NLS_ERROR_CPREPOENTRYMISSING = "error.cprepoentrymissing"; @@ -151,10 +152,12 @@ public class CPEditController extends ActivateableTabbableDefaultController impl cpConfigurationVc.contextPut(VC_CHOSENCP, translate(NLS_NO_CP_CHOSEN)); } - boolean cpMenu = config.getBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU); + Boolean cpMenu = config.getBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU); + //fxdiff VCRP-13: cp navigation + Boolean cpNavButtons = config.getBooleanEntry(CPEditController.CONFIG_SHOWNAVBUTTONS); String contentEncoding = (String)config.get(NodeEditController.CONFIG_CONTENT_ENCODING); String jsEncoding = (String)config.get(NodeEditController.CONFIG_JS_ENCODING); - cpMenuForm = new CompMenuForm(ureq, wControl, cpMenu, contentEncoding, jsEncoding); + cpMenuForm = new CompMenuForm(ureq, wControl, cpMenu, cpNavButtons, contentEncoding, jsEncoding); listenTo(cpMenuForm); cpConfigurationVc.put("cpMenuForm", cpMenuForm.getInitialComponent()); @@ -244,6 +247,8 @@ public class CPEditController extends ActivateableTabbableDefaultController impl } else if (source == cpMenuForm) { if (event == Event.DONE_EVENT) { config.setBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU, cpMenuForm.isCpMenu()); + //fxdiff VCRP-13: cp navigation + config.setBooleanEntry(CPEditController.CONFIG_SHOWNAVBUTTONS, cpMenuForm.isCpNavButtons()); config.set(NodeEditController.CONFIG_CONTENT_ENCODING, cpMenuForm.getContentEncoding()); config.set(NodeEditController.CONFIG_JS_ENCODING, cpMenuForm.getJSEncoding()); @@ -355,21 +360,29 @@ class CompMenuForm extends FormBasicController { // NLS support: private static final String NLS_DISPLAY_CONFIG_COMPMENU = "display.config.compMenu"; + //fxdiff VCRP-13: cp navigation + private static final String NLS_DISPLAY_CONFIG_COMP_NEXT_PREVIOUS = "display.config.compNextPrevious"; private SelectionElement cpMenu; + //fxdiff VCRP-13: cp navigation + private SelectionElement cpNavButtons; private SingleSelection encodingContentEl; private SingleSelection encodingJSEl; private boolean compMenuConfig; + //fxdiff VCRP-13: cp navigation + private boolean compNavButtonsConfig; private String contentEncoding; private String jsEncoding; private String[] encodingContentKeys, encodingContentValues; private String[] encodingJSKeys, encodingJSValues; - CompMenuForm(UserRequest ureq, WindowControl wControl, Boolean compMenuConfig, String contentEncoding, String jsEncoding) { + CompMenuForm(UserRequest ureq, WindowControl wControl, Boolean compMenuConfig, Boolean compNavButtons, String contentEncoding, String jsEncoding) { super(ureq, wControl); this.compMenuConfig = compMenuConfig == null ? true:compMenuConfig.booleanValue(); + //fxdiff VCRP-13: cp navigation + this.compNavButtonsConfig = compNavButtons == null ? true:compNavButtons.booleanValue(); this.contentEncoding = contentEncoding; this.jsEncoding = jsEncoding; @@ -413,6 +426,11 @@ class CompMenuForm extends FormBasicController { return cpMenu.isSelected(0); } + //fxdiff VCRP-13: cp navigation + public boolean isCpNavButtons() { + return cpNavButtons.isSelected(0); + } + @Override protected void formOK(UserRequest ureq) { fireEvent (ureq, Event.DONE_EVENT); @@ -423,6 +441,10 @@ class CompMenuForm extends FormBasicController { cpMenu = uifactory.addCheckboxesVertical("cpMenu", NLS_DISPLAY_CONFIG_COMPMENU, formLayout, new String[]{"xx"}, new String[]{null}, null, 1); cpMenu.select("xx",compMenuConfig); + //fxdiff VCRP-13: cp navigation + cpNavButtons = uifactory.addCheckboxesVertical("cpNextPrevious", NLS_DISPLAY_CONFIG_COMP_NEXT_PREVIOUS, formLayout, new String[]{"xx"}, new String[]{null}, null, 1); + cpNavButtons.select("xx",compNavButtonsConfig); + encodingContentEl = uifactory.addDropdownSingleselect("encoContent", "encoding.content", formLayout, encodingContentKeys, encodingContentValues, null); if (Arrays.asList(encodingContentKeys).contains(contentEncoding)) { encodingContentEl.select(contentEncoding, true); diff --git a/src/main/java/org/olat/course/nodes/cp/CPRunController.java b/src/main/java/org/olat/course/nodes/cp/CPRunController.java index 0149beeeb1b02051f1788fdc6f3150283f989f7c..3b8dd4dd9f8e287d05d1fc9fd322963823d4a15e 100644 --- a/src/main/java/org/olat/course/nodes/cp/CPRunController.java +++ b/src/main/java/org/olat/course/nodes/cp/CPRunController.java @@ -192,7 +192,9 @@ public class CPRunController extends BasicController implements ControllerEventL if ( (nodecmd != null) && !nodecmd.equals("") ) { activateFirstPage = false; } - cpDispC = CPUIFactory.getInstance().createContentOnlyCPDisplayController(ureq, getWindowControl(), new LocalFolderImpl(cpRoot), activateFirstPage, nodecmd, ores); + //fxdiff VCRP-13: cp navigation + boolean navButtons = isNavButtonConfigured(); + cpDispC = CPUIFactory.getInstance().createContentOnlyCPDisplayController(ureq, getWindowControl(), new LocalFolderImpl(cpRoot), activateFirstPage, navButtons, nodecmd, ores); cpDispC.setContentEncoding(getContentEncoding()); cpDispC.setJSEncoding(getJSEncoding()); cpDispC.addControllerListener(this); @@ -221,6 +223,12 @@ public class CPRunController extends BasicController implements ControllerEventL return (config.getBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU).booleanValue()); } + //fxdiff VCRP-13: cp navigation + private boolean isNavButtonConfigured() { + Boolean navButton = config.getBooleanEntry(CPEditController.CONFIG_SHOWNAVBUTTONS); + return navButton == null ? true : navButton.booleanValue(); + } + private String getContentEncoding() { String encoding = (String)config.get(NodeEditController.CONFIG_CONTENT_ENCODING); if(!encoding.equals(NodeEditController.CONFIG_CONTENT_ENCODING_AUTO)) { diff --git a/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_de.properties index dda294c4597345fca3e6c50d245c2c3f332e11e1..253837c4a9f4dcd92963074b2588c00df0c073c2 100644 --- a/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_de.properties @@ -34,6 +34,7 @@ command.preview=Vorschau anzeigen command.showcp=CP-Lerninhalt anzeigen condition.accessibility.title=Zugang display.config.compMenu=CP-Navigation anzeigen +display.config.compNextPrevious=CP Vor- Zur\u00FCck anzeigen display.config.fieldset=Darstellung display.config.startPage=Mit Startseite? display.config.startPage.false=Nein, Inhalt direkt starten diff --git a/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_en.properties index ff13aec374b39376e62dac393021f04b5235dce2..2a8ff4ede34eaa44a08986cf63b43ddbfde7a011 100644 --- a/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/cp/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 14:03:35 CET 2011 +#Mon May 16 17:18:39 CEST 2011 chelp.ced-cp-disp.title=$\:chelp.ced-disp.title chelp.ced-cp.title=CP learning content\: selection chelp.ced-disp.title=CP learning content\: display @@ -33,6 +33,7 @@ command.preview=Show preview command.showcp=Show CP learning content condition.accessibility.title=Access display.config.compMenu=Show CP navigation +display.config.compNextPrevious=Show previous/next navigation in content display.config.fieldset=Display display.config.startPage=With welcome page? display.config.startPage.false=No, start with content immediately. diff --git a/src/main/java/org/olat/course/nodes/feed/FeedNodeSecurityCallback.java b/src/main/java/org/olat/course/nodes/feed/FeedNodeSecurityCallback.java index 34f2c046a985994fcb38f44b7ba79a9f96921072..b538103a703b03921689513544f72da8e1c5e408 100644 --- a/src/main/java/org/olat/course/nodes/feed/FeedNodeSecurityCallback.java +++ b/src/main/java/org/olat/course/nodes/feed/FeedNodeSecurityCallback.java @@ -37,11 +37,13 @@ public class FeedNodeSecurityCallback implements FeedSecurityCallback { private NodeEvaluation ne; private boolean isOlatAdmin; private boolean isGuestOnly; + private boolean isOwner; - public FeedNodeSecurityCallback(NodeEvaluation ne, boolean isOlatAdmin, boolean isGuestOnly) { + public FeedNodeSecurityCallback(NodeEvaluation ne, boolean isOlatAdmin, boolean isOwner, boolean isGuestOnly) { this.ne = ne; this.isOlatAdmin = isOlatAdmin; this.isGuestOnly = isGuestOnly; + this.isOwner = isOwner; } /** @@ -75,4 +77,13 @@ public class FeedNodeSecurityCallback implements FeedSecurityCallback { if (isGuestOnly) return false; return ne.isCapabilityAccessible("moderator") || isOlatAdmin; } + + /** + * @see org.olat.modules.webFeed.FeedSecurityCallback#mayViewAllDrafts() + */ + @Override + //fxdiff BAKS-18 + public boolean mayViewAllDrafts() { + return isOwner || isOlatAdmin; + } } diff --git a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java index 06c1c76c74b8f4f26b08305cdce424f697628237..c7a26f84c3cd0ea1fad5405cd9d008bb4a2ea8eb 100644 --- a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java +++ b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java @@ -98,9 +98,9 @@ public class MSCourseNodeRunController extends DefaultController { myContent.contextPut(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD)); String infoTextUser = (String) config.get(MSCourseNode.CONFIG_KEY_INFOTEXT_USER); myContent.contextPut(MSCourseNode.CONFIG_KEY_INFOTEXT_USER, (infoTextUser == null ? "" : infoTextUser)); - myContent.contextPut(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE, config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE)); - myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MIN, config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN)); - myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MAX, config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX)); + myContent.contextPut(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE))); + myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MIN, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN))); + myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MAX, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX))); } private void exposeUserDataToVC(UserCourseEnvironment userCourseEnv, AssessableCourseNode courseNode) { diff --git a/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java b/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java index 164363d8942f5efbb9adda785e16eaae2ad6ec8a..f8996a4b5600d7f66ba4a7c1b6814645e52f3a54 100644 --- a/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java +++ b/src/main/java/org/olat/course/nodes/portfolio/PortfolioConfigForm.java @@ -81,6 +81,7 @@ public class PortfolioConfigForm extends FormBasicController { private Controller previewCtr; private LayoutMain3ColsBackController columnLayoutCtr; + private boolean isDirty; private final PortfolioCourseNode courseNode; public PortfolioConfigForm(UserRequest ureq, WindowControl wControl, ICourse course, PortfolioCourseNode courseNode) { @@ -148,6 +149,10 @@ public class PortfolioConfigForm extends FormBasicController { protected void doDispose() { // } + + public void setDirtyFromOtherForm(boolean dirty){ + this.isDirty = dirty; + } @Override protected void formOK(UserRequest ureq) { @@ -156,6 +161,10 @@ public class PortfolioConfigForm extends FormBasicController { @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (isDirty) { + showWarning("form.dirty"); + return; + } if (source == changeMapLink || source == chooseMapLink) { removeAsListenerAndDispose(searchController); searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, new String[]{EPTemplateMapResource.TYPE_NAME}, translate("select.map2"), @@ -164,6 +173,10 @@ public class PortfolioConfigForm extends FormBasicController { removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), translate("close"), searchController.getInitialComponent(), true, translate("select.map")); listenTo(cmc); + if (isDirty) { + showWarning("form.dirty"); + return; + } cmc.activate(); } else if (source == editMapLink) { CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, courseNode); diff --git a/src/main/java/org/olat/course/nodes/portfolio/PortfolioCourseNodeEditController.java b/src/main/java/org/olat/course/nodes/portfolio/PortfolioCourseNodeEditController.java index 445f035f2fc80c819ee67170c221bdd08c22765a..21f4739374ea6d50ee5c445f61c84e670bee058e 100644 --- a/src/main/java/org/olat/course/nodes/portfolio/PortfolioCourseNodeEditController.java +++ b/src/main/java/org/olat/course/nodes/portfolio/PortfolioCourseNodeEditController.java @@ -139,12 +139,20 @@ public class PortfolioCourseNodeEditController extends ActivateableTabbableDefau } else if (source == configForm) { if (event == Event.DONE_EVENT) { configForm.getUpdatedConfig(); + configForm.setDirtyFromOtherForm(false); fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } else if (event == Event.CHANGED_EVENT) { + // disable modification in other forms! + configForm.setDirtyFromOtherForm(true); } } else if (source == textForm) { if (event == Event.DONE_EVENT) { textForm.getUpdatedConfig(); + configForm.setDirtyFromOtherForm(false); fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } else if (event == Event.CHANGED_EVENT) { + // disable modification in other forms! + configForm.setDirtyFromOtherForm(true); } } else if (source == scoringController) { if (event == Event.CANCELLED_EVENT) { diff --git a/src/main/java/org/olat/course/nodes/portfolio/PortfolioTextForm.java b/src/main/java/org/olat/course/nodes/portfolio/PortfolioTextForm.java index 093661eec726a37cc280064cf5387be716c43d95..a3c7a7b4907e7a12390579e622aa17c4d24b8402 100644 --- a/src/main/java/org/olat/course/nodes/portfolio/PortfolioTextForm.java +++ b/src/main/java/org/olat/course/nodes/portfolio/PortfolioTextForm.java @@ -238,7 +238,7 @@ public class PortfolioTextForm extends FormBasicController { } else if (event.wasTriggerdBy(FormEvent.ONCHANGE)){ showWarningWhenInUse(); } - super.formInnerEvent(ureq, source, event); + fireEvent(ureq, Event.CHANGED_EVENT); } private void showWarningWhenInUse(){ diff --git a/src/main/java/org/olat/course/nodes/projectbroker/ProjectListController.java b/src/main/java/org/olat/course/nodes/projectbroker/ProjectListController.java index 72175e188fcf237312be6a66390d3669948cb27d..68c1e0b7835092dce4e99c95cfa40d9c91058c4b 100644 --- a/src/main/java/org/olat/course/nodes/projectbroker/ProjectListController.java +++ b/src/main/java/org/olat/course/nodes/projectbroker/ProjectListController.java @@ -214,12 +214,13 @@ public class ProjectListController extends BasicController implements GenericEve ProjectBrokerManagerFactory.getProjectGroupManager().sendGroupChangeEvent(project, courseId, ureq.getIdentity()); getLogger().debug("Created a new project=" + project); projectController = new ProjectController(ureq, this.getWindowControl(), userCourseEnv, nodeEvaluation, project, true, moduleConfig); - projectController.addControllerListener(this); + listenTo(projectController); mainPanel.pushContent(projectController.getInitialComponent()); } else if (event.getCommand().equals(OPEN_IDENTITY_CMD)){ Link link = (Link) source; if (calloutCtrl!=null) { calloutCtrl.deactivate(); + removeAsListenerAndDispose(calloutCtrl); calloutCtrl = null; } openUserInPopup(ureq, (Identity) link.getUserObject()); @@ -340,7 +341,7 @@ public class ProjectListController extends BasicController implements GenericEve int row = tableEvent.getRowId(); String targetDomID = ProjectManagerColumnRenderer.PROJECTMANAGER_COLUMN_ROW_IDENT + row; String title = translate("projectlist.callout.title", projectAt.getTitle()); - + removeAsListenerAndDispose(calloutCtrl); calloutCtrl = new CloseableCalloutWindowController(urequest, getWindowControl(), identityVC, targetDomID, title, true, null); calloutCtrl.activate(); listenTo(calloutCtrl); diff --git a/src/main/java/org/olat/course/nodes/projectbroker/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/projectbroker/_i18n/LocalStrings_en.properties index bb634889be59a70dec11bb1a8c5d7132ff23dcb1..1fcf05c512b0ec2017ceda17877a0e86f6add437 100644 --- a/src/main/java/org/olat/course/nodes/projectbroker/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/projectbroker/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 15:00:27 CET 2011 +#Thu Sep 08 17:28:43 CEST 2011 ENROLLMENT_EVENT=Registration date HANDOUT_EVENT=Due date account.manager.groupdescription=Administrator of $\:chelp.projectbroker.themenboerse <i>{0}</i>, created automatically. @@ -206,6 +206,8 @@ projectgroup.candidates.message=Please delist those candidates that could not be projectgroup.candidates.title=Candidates projectgroup.member.title=Participants accepted projectgroup.projectleader.title=Topic authors +projectlist.callout.intro=Klick a user to get more information. +projectlist.callout.title=$\:projectlist.tableheader.account.manager for {0} projectlist.no.projects=No $\:chelp.projectbroker.themaPl for this $\:chelp.projectbroker.themenboerse available. projectlist.numbers.delimiter=of projectlist.tableheader.account.manager=In charge diff --git a/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java b/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java index 88ff54b775d7e432b9ef4da3fdf7cd309a0863f2..2bdbbbdb9287a078ab9b88015c92e5d73fa20817 100644 --- a/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java +++ b/src/main/java/org/olat/course/nodes/projectbroker/service/ProjectGroupManagerImpl.java @@ -152,10 +152,13 @@ public class ProjectGroupManagerImpl extends BasicManager implements ProjectGrou } public void updateAccountManagerGroupName(String groupName, String groupDescription, BusinessGroup accountManagerGroup) { - BusinessGroup reloadedBusinessGroup = (BusinessGroup)DBFactory.getInstance().loadObject(BusinessGroupImpl.class, accountManagerGroup.getKey()); - reloadedBusinessGroup.setName(groupName); - reloadedBusinessGroup.setDescription(groupDescription); - BusinessGroupManagerImpl.getInstance().updateBusinessGroup(reloadedBusinessGroup); + // group could have been deleted, see FXOLAT-295 + if (accountManagerGroup != null){ + BusinessGroup reloadedBusinessGroup = (BusinessGroup)DBFactory.getInstance().loadObject(BusinessGroupImpl.class, accountManagerGroup.getKey()); + reloadedBusinessGroup.setName(groupName); + reloadedBusinessGroup.setDescription(groupDescription); + BusinessGroupManagerImpl.getInstance().updateBusinessGroup(reloadedBusinessGroup); + } } diff --git a/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java b/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java index e095a10ebfb32bb63f3c4af5c38ed3b1b15d14ec..9165414347251b31a3c9c3c5d84aff257441d1a9 100644 --- a/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java +++ b/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java @@ -32,9 +32,12 @@ import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.IntegerElement; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.SelectionElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.rules.RulesFactory; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.panel.Panel; @@ -80,11 +83,21 @@ public class ScormEditController extends ActivateableTabbableDefaultController i private static final String CONFIG_KEY_REPOSITORY_SOFTKEY = "reporef"; public static final String CONFIG_SHOWMENU = "showmenu"; + public static final String CONFIG_SKIPLAUNCHPAGE = "skiplaunchpage"; public static final String CONFIG_SHOWNAVBUTTONS = "shownavbuttons"; public static final String CONFIG_ISASSESSABLE = "isassessable"; public static final String CONFIG_CUTVALUE = "cutvalue"; public static final String CONFIG_HEIGHT = "height"; public final static String CONFIG_HEIGHT_AUTO = "auto"; + //fxdiff FXOLAT-116: SCORM improvements + public final static String CONFIG_FULLWINDOW = "fullwindow"; + public final static String CONFIG_CLOSE_ON_FINISH = "CLOSEONFINISH"; + + // <OLATCE-289> + public static final String CONFIG_MAXATTEMPTS = "attempts"; + public static final String CONFIG_ADVANCESCORE = "advancescore"; + public static final String CONFIG_ATTEMPTSDEPENDONSCORE = "scoreattampts"; + // </OLATCE-289> private static final String VC_CHOSENCP = "chosencp"; @@ -162,14 +175,24 @@ public class ScormEditController extends ActivateableTabbableDefaultController i // add the form for choosing the score variable boolean showMenu = config.getBooleanSafe(CONFIG_SHOWMENU, true); boolean showNavButtons = config.getBooleanSafe(CONFIG_SHOWNAVBUTTONS, true); - boolean assessable = config.getBooleanSafe(CONFIG_ISASSESSABLE); + boolean skipLaunchPage = config.getBooleanSafe(CONFIG_SKIPLAUNCHPAGE,false); + + // <OLATCE-289> + boolean assessable = config.getBooleanSafe(CONFIG_ISASSESSABLE, true); + boolean attemptsDependOnScore = config.getBooleanSafe(CONFIG_ATTEMPTSDEPENDONSCORE, true); + int maxAttempts = config.getIntegerSafe(CONFIG_MAXATTEMPTS, 0); + boolean advanceScore = config.getBooleanSafe(CONFIG_ADVANCESCORE, true); + // </OLATCE-289> int cutvalue = config.getIntegerSafe(CONFIG_CUTVALUE, 0); String height = (String) config.get(CONFIG_HEIGHT); String encContent = (String) config.get(NodeEditController.CONFIG_CONTENT_ENCODING); String encJS = (String) config.get(NodeEditController.CONFIG_JS_ENCODING); + //fxdiff FXOLAT-116: SCORM improvements + boolean fullWindow = config.getBooleanSafe(CONFIG_FULLWINDOW, false); + boolean closeOnFinish = config.getBooleanSafe(CONFIG_CLOSE_ON_FINISH, false); //= conf.get(CONFIG_CUTVALUE); - scorevarform = new VarForm(ureq, wControl, showMenu, showNavButtons, height, encContent, encJS, assessable, cutvalue); + scorevarform = new VarForm(ureq, wControl, showMenu, skipLaunchPage, showNavButtons, height, encContent, encJS, assessable, cutvalue, fullWindow, closeOnFinish, maxAttempts, advanceScore, attemptsDependOnScore); listenTo(scorevarform); cpConfigurationVc.put("scorevarform", scorevarform.getInitialComponent()); @@ -208,11 +231,12 @@ public class ScormEditController extends ActivateableTabbableDefaultController i } else { File cpRoot = FileResourceManager.getInstance().unzipFileResource(re.getOlatResource()); boolean showMenu = config.getBooleanSafe(CONFIG_SHOWMENU, true); + boolean fullWindow = config.getBooleanSafe(CONFIG_FULLWINDOW, false); if (previewLayoutCtr != null) previewLayoutCtr.dispose(); ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapScormRepositoryEntry(re)); ScormAPIandDisplayController previewController = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, cpRoot, null, course.getResourceableId().toString(), - ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, true, true); + ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, true, true, fullWindow); // configure some display options boolean showNavButtons = config.getBooleanSafe(ScormEditController.CONFIG_SHOWNAVBUTTONS, true); previewController.showNavButtons(showNavButtons); @@ -228,6 +252,7 @@ public class ScormEditController extends ActivateableTabbableDefaultController i if ( ! jsEncoding.equals(NodeEditController.CONFIG_JS_ENCODING_AUTO)) { previewController.setJSEncoding(jsEncoding); } + previewController.activate(); } } } @@ -262,20 +287,26 @@ public class ScormEditController extends ActivateableTabbableDefaultController i } } else if (source == scorevarform) { if (event == Event.DONE_EVENT) { - boolean showmenu = scorevarform.isShowMenu(); - config.setBooleanEntry(CONFIG_SHOWMENU, showmenu); - boolean showNavButtons = scorevarform.isShowNavButtons(); - config.setBooleanEntry(CONFIG_SHOWNAVBUTTONS, showNavButtons); - boolean assessable = scorevarform.isAssessable(); - config.setBooleanEntry(CONFIG_ISASSESSABLE, assessable); - int cutvalue = scorevarform.getCutValue(); - config.setIntValue(CONFIG_CUTVALUE, cutvalue); - String height = scorevarform.getHeightValue(); - config.set(CONFIG_HEIGHT, height); - String encContent = scorevarform.getEncodingContentValue(); - config.set(NodeEditController.CONFIG_CONTENT_ENCODING, encContent); - String encJS = scorevarform.getEncodingJSValue(); - config.set(NodeEditController.CONFIG_JS_ENCODING, encJS); + //save form-values to config + + config.setBooleanEntry(CONFIG_SHOWMENU, scorevarform.isShowMenu()); + //fxdiff FXOLAT-322 + config.setBooleanEntry(CONFIG_SKIPLAUNCHPAGE, scorevarform.isSkipLaunchPage()); + config.setBooleanEntry(CONFIG_SHOWNAVBUTTONS, scorevarform.isShowNavButtons()); + config.setBooleanEntry(CONFIG_ISASSESSABLE, scorevarform.isAssessable()); + config.setIntValue(CONFIG_CUTVALUE, scorevarform.getCutValue()); + //fxdiff FXOLAT-116: SCORM improvements + config.setBooleanEntry(CONFIG_FULLWINDOW, scorevarform.isFullWindow()); + config.setBooleanEntry(CONFIG_CLOSE_ON_FINISH, scorevarform.isCloseOnFinish()); + // <OLATCE-289> + config.setIntValue(CONFIG_MAXATTEMPTS, scorevarform.getAttemptsValue()); + config.setBooleanEntry(CONFIG_ADVANCESCORE, scorevarform.isAdvanceScore()); + config.setBooleanEntry(CONFIG_ATTEMPTSDEPENDONSCORE, scorevarform.getAttemptsDependOnScore()); + // </OLATCE-289> + config.set(CONFIG_HEIGHT, scorevarform.getHeightValue()); + config.set(NodeEditController.CONFIG_CONTENT_ENCODING, scorevarform.getEncodingContentValue()); + config.set(NodeEditController.CONFIG_JS_ENCODING, scorevarform.getEncodingJSValue()); + // fire event so the updated config is saved by the // editormaincontroller fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); @@ -360,36 +391,66 @@ public class ScormEditController extends ActivateableTabbableDefaultController i class VarForm extends FormBasicController { private SelectionElement showMenuEl; private SelectionElement showNavButtonsEl; + private SelectionElement fullWindowEl;//fxdiff FXOLAT-116: SCORM improvements + private SelectionElement closeOnFinishEl;//fxdiff FXOLAT-116: SCORM improvements private SelectionElement isAssessableEl; + private SelectionElement skipLaunchPageEl; //fxdiff FXOLAT-322 : skip start-page / auto-launch private IntegerElement cutValueEl; private SingleSelection heightEl; private SingleSelection encodingContentEl; private SingleSelection encodingJSEl; - private boolean showMenu, showNavButtons, isAssessable; + + private boolean showMenu, showNavButtons, isAssessable,skipLaunchPage; private String height; private String encodingContent; private String encodingJS; private int cutValue; + private boolean fullWindow;//fxdiff FXOLAT-116: SCORM improvements + private boolean closeOnFinish;//fxdiff FXOLAT-116: SCORM improvements private String[] keys, values; private String[] encodingContentKeys, encodingContentValues; private String[] encodingJSKeys, encodingJSValues; + + // <OLATCE-289> + private SingleSelection attemptsEl; + private MultipleSelectionElement advanceScoreEl; + private MultipleSelectionElement scoreAttemptsEl; + + private boolean advanceScore; + private boolean scoreAttempts; + private int maxattempts; + // </OLATCE-289> /** * * @param name Name of the form */ - public VarForm(UserRequest ureq, WindowControl wControl, boolean showMenu, boolean showNavButtons, String height, - String encodingContent, String encodingJS, boolean isAssessable, int cutValue) { + public VarForm(UserRequest ureq, WindowControl wControl, boolean showMenu, boolean skipLaunchPage, boolean showNavButtons, String height, + String encodingContent, String encodingJS, boolean isAssessable, int cutValue, boolean fullWindow, boolean closeOnFinish, + // <OLATCE-289> + int maxattempts, boolean advanceScore, boolean attemptsDependOnScore + // </OLATCE-289> + ) { super(ureq, wControl); this.showMenu = showMenu; + this.skipLaunchPage = skipLaunchPage; this.showNavButtons = showNavButtons; this.isAssessable = isAssessable; this.cutValue = cutValue; + //fxdiff FXOLAT-116: SCORM improvements + this.fullWindow = fullWindow; + this.closeOnFinish = closeOnFinish; this.height = height; this.encodingContent = encodingContent; this.encodingJS = encodingJS; + // <OLATCE-289> + this.advanceScore = advanceScore; + this.scoreAttempts = attemptsDependOnScore; + this.maxattempts = maxattempts; + // </OLATCE-289> + keys = new String[]{ ScormEditController.CONFIG_HEIGHT_AUTO, "460", "480", "500", "520", "540", "560", "580", "600", "620", "640", "660", "680", @@ -449,10 +510,22 @@ class VarForm extends FormBasicController { public int getCutValue() { return cutValueEl.getIntValue(); } + //fxdiff FXOLAT-116: SCORM improvements + public boolean isFullWindow() { + return fullWindowEl.isMultiselect() && fullWindowEl.isSelected(0); + } + //fxdiff FXOLAT-116: SCORM improvements + public boolean isCloseOnFinish() { + return closeOnFinishEl.isMultiselect() && closeOnFinishEl.isSelected(0); + } public boolean isShowMenu() { return showMenuEl.isSelected(0); } + + public boolean isSkipLaunchPage() { + return skipLaunchPageEl.isSelected(0); + } public boolean isShowNavButtons() { return showNavButtonsEl.isSelected(0); @@ -487,8 +560,17 @@ class VarForm extends FormBasicController { showMenuEl = uifactory.addCheckboxesVertical("showmenu", "showmenu.label", formLayout, new String[]{"xx"}, new String[]{null}, null, 1); showMenuEl.select("xx", showMenu); + skipLaunchPageEl = uifactory.addCheckboxesVertical("skiplaunchpage", "skiplaunchpage.label", formLayout, new String[]{"xx"}, new String[]{null}, null, 1); + skipLaunchPageEl.select("xx", skipLaunchPage); + showNavButtonsEl = uifactory.addCheckboxesVertical("shownavbuttons", "shownavbuttons.label", formLayout, new String[]{"xx"}, new String[]{null}, null, 1); showNavButtonsEl.select("xx", showNavButtons); + //fxdiff FXOLAT-116: SCORM improvements + fullWindowEl = uifactory.addCheckboxesVertical("fullwindow", "fullwindow.label", formLayout, new String[]{"fullwindow"}, new String[]{null}, null, 1); + fullWindowEl.select("fullwindow", fullWindow); + + closeOnFinishEl = uifactory.addCheckboxesVertical("closeonfinish", "closeonfinish.label", formLayout, new String[]{"closeonfinish"}, new String[]{null}, null, 1); + closeOnFinishEl.select("closeonfinish", closeOnFinish); heightEl = uifactory.addDropdownSingleselect("height", "height.label", formLayout, keys, values, null); if (Arrays.asList(keys).contains(height)) { @@ -511,15 +593,63 @@ class VarForm extends FormBasicController { encodingJSEl.select(NodeEditController.CONFIG_JS_ENCODING_AUTO, true); } - isAssessableEl = uifactory.addCheckboxesVertical("isassessable", "assessable.label", formLayout, new String[]{"xx"}, new String[]{null}, null, 1); - isAssessableEl.select("xx", isAssessable); + isAssessableEl = uifactory.addCheckboxesVertical("isassessable", "assessable.label", formLayout, new String[]{"ison"}, new String[]{null}, null, 1); + isAssessableEl.select("ison", isAssessable); cutValueEl = uifactory.addIntegerElement("cutvalue", "cutvalue.label", 0, formLayout); cutValueEl.setIntValue(cutValue); cutValueEl.setDisplaySize(3); + + // <OLATCE-289> + isAssessableEl.addActionListener(this, FormEvent.ONCHANGE); + advanceScoreEl = uifactory.addCheckboxesVertical("advanceScore", "advance.score.label", formLayout, new String[]{"ison"}, new String[]{null}, null, 1); + advanceScoreEl.select("ison", advanceScore); + advanceScoreEl.addActionListener(this, FormEvent.ONCHANGE); + + RulesFactory.createShowRule(isAssessableEl, "ison", advanceScoreEl, formLayout); + RulesFactory.createHideRule(isAssessableEl, null, advanceScoreEl, formLayout); + + scoreAttemptsEl = uifactory.addCheckboxesVertical("scoreAttempts", "attempts.depends.label", formLayout, new String[]{"ison"}, new String[]{null}, null, 1); + scoreAttemptsEl.select("ison", scoreAttempts); + scoreAttemptsEl.addActionListener(this, FormEvent.ONCHANGE); + + RulesFactory.createShowRule(advanceScoreEl, "ison",scoreAttemptsEl, formLayout); + RulesFactory.createHideRule(advanceScoreEl, null, scoreAttemptsEl, formLayout); + + // <BPS-252> BPS-252_1 + int maxNumber = 21; + String[] attemptsKeys = new String[maxNumber]; + attemptsKeys[0] = "0"; // position 0 means no restriction + for (int i = 1; i < maxNumber; i++) { + attemptsKeys[i] = (String.valueOf(i)); + } + String[] attemptsValues = new String[maxNumber]; + attemptsValues[0] = translate("attempts.noLimit"); + for (int i = 1; i < maxNumber; i++) { + attemptsValues[i] = (String.valueOf(i) + " x"); + } + if (maxattempts >= maxNumber) { + maxattempts = 0; + } + + attemptsEl = uifactory.addDropdownSingleselect("attempts.label", formLayout, attemptsKeys, attemptsValues, null); + attemptsEl.select("" + maxattempts, true); + // </OLATCE-289> + uifactory.addFormSubmitButton("save", formLayout); } - + + // <OLATCE-289> + public int getAttemptsValue() { + return Integer.valueOf(attemptsEl.getSelectedKey()); + } + public boolean isAdvanceScore() { + return advanceScoreEl.isSelected(0); + } + + public boolean getAttemptsDependOnScore() { + return scoreAttemptsEl.isSelected(0); + } @Override protected void doDispose() { diff --git a/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java b/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java index 4050ad14b42842626990b1a4816c8c1e7e71360a..789f6668931387c20b278c920cb39b4ecf62d1a8 100644 --- a/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java +++ b/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java @@ -1,23 +1,23 @@ /** -* 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. -* <p> -*/ + * 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. + * <p> + */ package org.olat.course.nodes.scorm; @@ -27,6 +27,7 @@ import java.util.Properties; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.Window; import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -37,8 +38,8 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.id.Identity; import org.olat.core.logging.AssertException; -import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; +import org.olat.core.util.event.GenericEventListener; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.editor.NodeEditController; import org.olat.course.nodes.ObjectivesHelper; @@ -56,18 +57,20 @@ import org.olat.repository.RepositoryEntry; import org.olat.util.logging.activity.LoggingResourceable; /** - * Description: <BR/>Run controller for content packaging course nodes <P/> + * Description: <BR/> + * Run controller for content packaging course nodes + * <P/> * * @author Felix Jost */ -public class ScormRunController extends BasicController implements ScormAPICallback { +public class ScormRunController extends BasicController implements ScormAPICallback, GenericEventListener { private ModuleConfiguration config; private File cpRoot; private Panel main; private VelocityContainer startPage; - //private Translator translator; + // private Translator translator; private ScormAPIandDisplayController scormDispC; private ScormCourseNode scormNode; @@ -77,14 +80,14 @@ public class ScormRunController extends BasicController implements ScormAPICallb private UserCourseEnvironment userCourseEnv; private ChooseScormRunModeForm chooseScormRunMode; private boolean isPreview; - + private Identity identity; private boolean isAssessable; /** - * Use this constructor to launch a CP via Repository reference key set in the - * ModuleConfiguration. On the into page a title and the learning objectives - * can be placed. + * Use this constructor to launch a CP via Repository reference key set in + * the ModuleConfiguration. On the into page a title and the learning + * objectives can be placed. * * @param config * @param ureq @@ -96,38 +99,76 @@ public class ScormRunController extends BasicController implements ScormAPICallb ScormCourseNode scormNode, boolean isPreview) { super(ureq, wControl); // assertion to make sure the moduleconfig is valid - if (!ScormEditController.isModuleConfigValid(config)) throw new AssertException("scorm run controller had an invalid module config:" - + config.toString()); + if (!ScormEditController.isModuleConfigValid(config)) + throw new AssertException("scorm run controller had an invalid module config:" + config.toString()); this.isPreview = isPreview; this.userCourseEnv = userCourseEnv; this.config = config; this.scormNode = scormNode; this.identity = ureq.getIdentity(); - + addLoggingResourceable(LoggingResourceable.wrap(scormNode)); init(ureq); } private void init(UserRequest ureq) { - startPage = createVelocityContainer ("run"); - // show browse mode option only if not assessable, hide it if in "real test mode" - isAssessable = config.getBooleanSafe(ScormEditController.CONFIG_ISASSESSABLE); + startPage = createVelocityContainer("run"); + // show browse mode option only if not assessable, hide it if in + // "real test mode" + isAssessable = config.getBooleanSafe(ScormEditController.CONFIG_ISASSESSABLE, true); + + // <OLATCE-289> + // attemptsDependOnScore means that attempts are only incremented when a + // score was given back by the SCORM + // set start button if max attempts are not reached + if (!maxAttemptsReached()) { + chooseScormRunMode = new ChooseScormRunModeForm(ureq, getWindowControl(), !isAssessable); + listenTo(chooseScormRunMode); + startPage.put("chooseScormRunMode", chooseScormRunMode.getInitialComponent()); + startPage.contextPut("maxAttemptsReached", Boolean.FALSE); + } else { + startPage.contextPut("maxAttemptsReached", Boolean.TRUE); + } + // </OLATCE-289> - chooseScormRunMode = new ChooseScormRunModeForm(ureq, getWindowControl(), !isAssessable); - listenTo(chooseScormRunMode); - startPage.put("chooseScormRunMode", chooseScormRunMode.getInitialComponent()); - main = new Panel("scormrunmain"); - // scorm always has a start page - doStartPage(ureq); - + doStartPage(); putInitialPanel(main); + + boolean doSkip = config.getBooleanSafe(ScormEditController.CONFIG_SKIPLAUNCHPAGE, true); + if (isAssessable && doSkip && !maxAttemptsReached()) { + doLaunch(ureq, true); + //CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), OresHelper.createOLATResourceableType(getClass().getName())); + getWindowControl().getWindowBackOffice().addCycleListener(this); + +// Component scormContent = scormDispC.getInitialComponent(); +// fireEvent(ureq, new FullWidthReplaceRequestEvent(true, scormContent)); + } } + // <OLATCE-289> + /** + * @return true if attempts of the user are equal to the maximum number of + * attempts. + */ + private boolean maxAttemptsReached() { + int maxAttempts = config.getIntegerSafe(ScormEditController.CONFIG_MAXATTEMPTS, 0); + boolean maxAttemptsReached = false; + if (maxAttempts > 0) { + if (scormNode.getUserAttempts(userCourseEnv) >= maxAttempts) { + maxAttemptsReached = true; + } + } + return maxAttemptsReached; + } + + // </OLATCE-289> + /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) + * org.olat.core.gui.components.Component, + * org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Component source, Event event) { // @@ -135,22 +176,33 @@ public class ScormRunController extends BasicController implements ScormAPICallb /** * @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) + * org.olat.core.gui.control.Controller, + * org.olat.core.gui.control.Event) */ public void event(UserRequest ureq, Controller source, Event event) { if (source == scormDispC) { // just pass on the event. - doStartPage(ureq); - fireEvent(ureq, event); + // <OLATCE-289> + if (event.equals(Event.BACK_EVENT)) { + if (maxAttemptsReached()) { + startPage.contextPut("maxAttemptsReached", Boolean.TRUE); + } + doStartPage(); + } else { + // </OLATCE-289> + doStartPage(); + fireEvent(ureq, event); + } } else if (source == null) { // external source if (event instanceof TreeEvent) { scormDispC.switchToPage((TreeEvent) event); } } else if (source == chooseScormRunMode) { - doLaunch(ureq); + doLaunch(ureq, true); + scormDispC.activate(); } } - private void doStartPage(UserRequest ureq) { + private void doStartPage() { // push title and learning objectives, only visible on intro page startPage.contextPut("menuTitle", scormNode.getShortTitle()); @@ -159,13 +211,13 @@ public class ScormRunController extends BasicController implements ScormAPICallb // Adding learning objectives String learningObj = scormNode.getLearningObjectives(); if (learningObj != null) { - Component learningObjectives = ObjectivesHelper.createLearningObjectivesComponent(learningObj, ureq); + Component learningObjectives = ObjectivesHelper.createLearningObjectivesComponent(learningObj, getLocale()); startPage.put("learningObjectives", learningObjectives); startPage.contextPut("hasObjectives", Boolean.TRUE); } else { startPage.contextPut("hasObjectives", Boolean.FALSE); } - + if (isAssessable) { ScoreEvaluation scoreEval = scormNode.getUserScoreEvaluation(userCourseEnv); Float score = scoreEval.getScore(); @@ -179,70 +231,89 @@ public class ScormRunController extends BasicController implements ScormAPICallb main.setContent(startPage); } - private void doLaunch(UserRequest ureq) { + private void doLaunch(UserRequest ureq, boolean doActivate) { if (cpRoot == null) { - // it is the first time we start the contentpackaging from this instance + // it is the first time we start the contentpackaging from this + // instance // of this controller. // need to be strict when launching -> "true" RepositoryEntry re = ScormEditController.getScormCPReference(config, true); - if (re == null) throw new AssertException("configurationkey 'CONFIG_KEY_REPOSITORY_SOFTKEY' of BB CP was missing"); + if (re == null) + throw new AssertException("configurationkey 'CONFIG_KEY_REPOSITORY_SOFTKEY' of BB CP was missing"); cpRoot = FileResourceManager.getInstance().unzipFileResource(re.getOlatResource()); addLoggingResourceable(LoggingResourceable.wrapScormRepositoryEntry(re)); - // should always exist because references cannot be deleted as long as + // should always exist because references cannot be deleted as long + // as // nodes reference them - if (cpRoot == null) throw new AssertException("file of repository entry " + re.getKey() + " was missing"); + if (cpRoot == null) + throw new AssertException("file of repository entry " + re.getKey() + " was missing"); } // else cpRoot is already set (save some db access if the user opens / // closes / reopens the cp from the same CPRuncontroller instance) String courseId; boolean showMenu = config.getBooleanSafe(ScormEditController.CONFIG_SHOWMENU, true); + // fxdiff FXOLAT-116: SCORM improvements + final boolean fullWindow = config.getBooleanSafe(ScormEditController.CONFIG_FULLWINDOW, false); if (isPreview) { courseId = new Long(CodeHelper.getRAMUniqueID()).toString(); - scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, cpRoot, null, courseId, ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, true, true); + scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, + cpRoot, null, courseId, ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, true, doActivate, + fullWindow); } else { courseId = userCourseEnv.getCourseEnvironment().getCourseResourceableId().toString(); - if(isAssessable) { - scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, this, cpRoot, null, courseId + "-" + scormNode.getIdent(), ScormConstants.SCORM_MODE_NORMAL, ScormConstants.SCORM_MODE_CREDIT, false, true); - scormNode.incrementUserAttempts(userCourseEnv); - } else if(chooseScormRunMode.getSelectedElement().equals(ScormConstants.SCORM_MODE_NORMAL)){ - scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, cpRoot, null, courseId + "-" + scormNode.getIdent(), ScormConstants.SCORM_MODE_NORMAL, ScormConstants.SCORM_MODE_CREDIT, false, true); + if (isAssessable) { + scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, this, + cpRoot, null, courseId + "-" + scormNode.getIdent(), ScormConstants.SCORM_MODE_NORMAL, + ScormConstants.SCORM_MODE_CREDIT, false, doActivate, fullWindow); + // <OLATCE-289> + // scormNode.incrementUserAttempts(userCourseEnv); + // </OLATCE-289> + } else if (chooseScormRunMode.getSelectedElement().equals(ScormConstants.SCORM_MODE_NORMAL)) { + scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, + cpRoot, null, courseId + "-" + scormNode.getIdent(), ScormConstants.SCORM_MODE_NORMAL, + ScormConstants.SCORM_MODE_CREDIT, false, doActivate, fullWindow); } else { - scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, cpRoot, null, courseId, ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, false, true); + scormDispC = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, getWindowControl(), showMenu, null, + cpRoot, null, courseId, ScormConstants.SCORM_MODE_BROWSE, ScormConstants.SCORM_MODE_NOCREDIT, false, doActivate, + fullWindow); } } // configure some display options boolean showNavButtons = config.getBooleanSafe(ScormEditController.CONFIG_SHOWNAVBUTTONS, true); scormDispC.showNavButtons(showNavButtons); String height = (String) config.get(ScormEditController.CONFIG_HEIGHT); - if ( ! height.equals(ScormEditController.CONFIG_HEIGHT_AUTO)) { + if (!height.equals(ScormEditController.CONFIG_HEIGHT_AUTO)) { scormDispC.setHeightPX(Integer.parseInt(height)); } String contentEncoding = (String) config.get(NodeEditController.CONFIG_CONTENT_ENCODING); - if ( ! contentEncoding.equals(NodeEditController.CONFIG_CONTENT_ENCODING_AUTO)) { + if (!contentEncoding.equals(NodeEditController.CONFIG_CONTENT_ENCODING_AUTO)) { scormDispC.setContentEncoding(contentEncoding); } String jsEncoding = (String) config.get(NodeEditController.CONFIG_JS_ENCODING); - if ( ! jsEncoding.equals(NodeEditController.CONFIG_JS_ENCODING_AUTO)) { + if (!jsEncoding.equals(NodeEditController.CONFIG_JS_ENCODING_AUTO)) { scormDispC.setJSEncoding(jsEncoding); } - + scormDispC.addControllerListener(this); // the scormDispC activates itself } - - /* (non-Javadoc) - * @see org.olat.modules.scorm.ScormAPICallback#lmsCommit(java.lang.String, java.util.Properties) + + /* + * (non-Javadoc) + * + * @see org.olat.modules.scorm.ScormAPICallback#lmsCommit(java.lang.String, + * java.util.Properties) */ public void lmsCommit(String olatSahsId, Properties scoScores) { // only write score info when node is configured to do so - if(isAssessable) { + if (isAssessable) { // do a sum-of-scores over all sco scores float score = 0f; for (Iterator it_score = scoScores.values().iterator(); it_score.hasNext();) { String aScore = (String) it_score.next(); float ascore = Float.parseFloat(aScore); - score+= ascore; + score += ascore; } float cutval = scormNode.getCutValueConfiguration().floatValue(); boolean passed = (score >= cutval); @@ -250,18 +321,86 @@ public class ScormRunController extends BasicController implements ScormAPICallb boolean incrementAttempts = false; scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, incrementAttempts); userCourseEnv.getScoreAccounting().scoreInfoChanged(scormNode, sceval); - - if (Tracing.isDebugEnabled(this.getClass())) { - String msg = "for scorm node:"+scormNode.getIdent()+" ("+scormNode.getShortTitle()+") a lmsCommit for scoId "+olatSahsId+" occured, total sum = "+score+", cutvalue ="+cutval+", passed: "+passed+", all scores now = "+scoScores.toString(); - Tracing.logDebug(msg, this.getClass()); + + if (isLogDebugEnabled()) { + String msg = "for scorm node:" + scormNode.getIdent() + " (" + scormNode.getShortTitle() + ") a lmsCommit for scoId " + + olatSahsId + " occured, total sum = " + score + ", cutvalue =" + cutval + ", passed: " + passed + + ", all scores now = " + scoScores.toString(); + logDebug(msg, null); + } + } + } + + // <BPS-620> + /** + * @see org.olat.modules.scorm.ScormAPICallback#lmsFinish(java.lang.String, + * java.util.Properties) + */ + public void lmsFinish(String olatSahsId, Properties scoProperties) { + if (isAssessable) { + // do a sum-of-scores over all sco scores + // <OLATEE-27> + float score = -1f; + // </OLATEE-27> + for (Iterator<Object> it_score = scoProperties.values().iterator(); it_score.hasNext();) { + // <OLATEE-27> + if (score == -1f) { + score = 0f; + } + // </OLATEE-27> + String aScore = (String) it_score.next(); + float ascore = Float.parseFloat(aScore); + score += ascore; } + + float cutval = scormNode.getCutValueConfiguration().floatValue(); + ScoreEvaluation sceval; + boolean passed = (score >= cutval); + // if advanceScore option is set update the score only if it is + // higher + // <OLATEE-27> + if (config.getBooleanSafe(ScormEditController.CONFIG_ADVANCESCORE, true)) { + if (score > (scormNode.getUserScoreEvaluation(userCourseEnv).getScore() != null ? scormNode.getUserScoreEvaluation( + userCourseEnv).getScore() : -1)) { + // </OLATEE-27> + sceval = new ScoreEvaluation(new Float(score), Boolean.valueOf(passed)); + scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, true); + userCourseEnv.getScoreAccounting().scoreInfoChanged(scormNode, sceval); + } else if (!config.getBooleanSafe(ScormEditController.CONFIG_ATTEMPTSDEPENDONSCORE, false)) { + sceval = scormNode.getUserScoreEvaluation(userCourseEnv); + scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, true); + userCourseEnv.getScoreAccounting().scoreInfoChanged(scormNode, sceval); + } + } else { + // <OLATEE-27> + if (score == -1f) { + score = 0f; + } + // </OLATEE-27> + sceval = new ScoreEvaluation(new Float(score), Boolean.valueOf(passed)); + scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, true); + userCourseEnv.getScoreAccounting().scoreInfoChanged(scormNode, sceval); + } + + if (isLogDebugEnabled()) { + String msg = "for scorm node:" + scormNode.getIdent() + " (" + scormNode.getShortTitle() + ") a lmsCommit for scoId " + + olatSahsId + " occured, total sum = " + score + ", cutvalue =" + cutval + ", passed: " + passed + + ", all scores now = " + scoProperties.toString(); + logDebug(msg, null); + } + } + + if (config.getBooleanSafe(ScormEditController.CONFIG_CLOSE_ON_FINISH, false)) { + doStartPage(); + scormDispC.close(); } } - + + // </BPS-620> /** - * @return true if there is a treemodel and an event listener ready to be used - * in outside this controller + * @return true if there is a treemodel and an event listener ready to be + * used in outside this controller */ public boolean isExternalMenuConfigured() { return (config.getBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU).booleanValue()); @@ -290,5 +429,13 @@ public class ScormRunController extends BasicController implements ScormAPICallb public ControllerEventListener getTreeNodeClickListener() { return treeNodeClickListener; } - + + @Override + public void event(Event event) { + if (event == Window.END_OF_DISPATCH_CYCLE) { + scormDispC.activate(); + getWindowControl().getWindowBackOffice().removeCycleListener(this); + } + } + } \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/scorm/_chelp/ced-scorm-settings.html b/src/main/java/org/olat/course/nodes/scorm/_chelp/ced-scorm-settings.html index 5acfe6fc29c8bab869aefbe4288747580f18574a..bec532393476eb501333cc758bab66a17a90b0d3 100644 --- a/src/main/java/org/olat/course/nodes/scorm/_chelp/ced-scorm-settings.html +++ b/src/main/java/org/olat/course/nodes/scorm/_chelp/ced-scorm-settings.html @@ -5,5 +5,6 @@ <b>$r.translate("encoding.content"):</b> $r.translate("chelp.set7")<br/><br/> <b>$r.translate("encoding.js"):</b> $r.translate("chelp.set8")<br/><br/> <b>$r.translate("assessable.label"):</b> $r.translate("chelp.set5") <br/><br/> - <b>$r.translate("cutvalue.label"):</b> $r.translate("chelp.set6") + <b>$r.translate("cutvalue.label"):</b> $r.translate("chelp.set6") <br/><br/> + <b>$r.translate("skiplaunchpage.label"):</b> $r.translate("chelp.set9") diff --git a/src/main/java/org/olat/course/nodes/scorm/_content/run.html b/src/main/java/org/olat/course/nodes/scorm/_content/run.html index 17d93e89dabea9c8c01b4bc93f1672f06ecb4008..a6dd6d58b16aee360433d5db6ba8598c0e649a7b 100644 --- a/src/main/java/org/olat/course/nodes/scorm/_content/run.html +++ b/src/main/java/org/olat/course/nodes/scorm/_content/run.html @@ -53,4 +53,10 @@ </div> #end - $r.render("chooseScormRunMode") \ No newline at end of file +## <OLATCE-289> +#if ($maxAttemptsReached) + $r.translate("attempts.max.reached") +#else + $r.render("chooseScormRunMode") +#end +## </OLATCE-289> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties index be37d4ef05ff42052373206259c92b9ae2faca8b..62702309381513f0ecb2799e9ee8ddfd2eeb4a7d 100644 --- a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties @@ -21,8 +21,9 @@ chelp.set3=Mittels des Drop-Down-Menus k\u00F6nnen Sie die H\u00F6he der Scorm-S chelp.set4=Sie haben die M\u00F6glichkeit, diese via $\:chelp.auto auf die jeweilige Fensterh\u00F6he zu setzen oder auf einen bestimmten Wert zu setzen. chelp.set5=Bestimmen Sie, ob die Summe aller im Scorm-Packet erreichten Punkte an das OLAT-Bewertungssystem weitergegeben werden soll. chelp.set6=Geben Sie eine Ganzzahl ein, die aussagt, wieviele Punkte erreicht werden m\u00FCssen, damit der Scorm-Test als bestanden gilt. -chelp.set7=OLAT versucht, den Zeichensatz automatisch zu erkennen. Wenn die Option "Automatisch" nicht zu der gewünschten Anzeige führt, kann die Kodierung des Inhalts anhand eines vordefinierten Zeichensatzes konfiguriert werden (ist keine Kodierung vorhanden, wird per Default der Zeichensatz ISO-8899-1 verwendet). +chelp.set7=OLAT versucht, den Zeichensatz automatisch zu erkennen. Wenn die Option "Automatisch" nicht zu der gew\u00FCnschten Anzeige f\u00FChrt, kann die Kodierung des Inhalts anhand eines vordefinierten Zeichensatzes konfiguriert werden (ist keine Kodierung vorhanden, wird per Default der Zeichensatz ISO-8899-1 verwendet). chelp.set8=Erlaubt die Kodierung des Javascript Codes anhand eines vordefinierten Zeichensatzes (per Default wird der gleiche Zeichensatz für Inhalt und Javascript verwendet). +chelp.set9=Bestimmen Sie, ob der SCORM-Lerninhalt automatisch startet, wenn der entsprechende Menu-Punkt im Kurs ausgewählt wird. Wenn Sie diese Option nicht aktivieren, wird stattdessen eine Startseite angezeigt. chosencp=Gew\u00E4hlter SCORM-Lerninhalt command.changecp=SCORM-Lerninhalt auswechseln command.importcp=SCORM-Lerninhalt w\u00E4hlen oder importieren @@ -47,6 +48,8 @@ form.scormmode.nocredit=Keine Punktevergabe form.scormmode.normal=Normal header=SCORM-Lerninhalt ausw\u00E4hlen headerform=Einstellungen +fullwindow.label=Nur Modul anzeigen, LMS ausblenden +closeonfinish.label=Modul automatisch schliessen wenn beendet height.auto=Automatisch height.label=H\u00F6he Anzeigefl\u00E4che help.hover.scorm-filename=Hilfe zur Auswahl eines SCORM-Lerninhaltes @@ -61,4 +64,12 @@ score.noscoreinfoyet=Zu diesem SCORM-Lerninhalt gibt es noch keine Punktangaben, score.title=Punkte score.yourscore=Erreichte Punktzahl showmenu.label=Menu anzeigen +skiplaunchpage.label=Inhalt automatisch starten shownavbuttons.label=Navigationsbuttons anzeigen +# <OLATCE-289> +advance.score.label = Reduzieren von Punkten bei erneutem Versuch verhindern +attempts.depends.label = L\u00F6sungsversuche nur z\u00E4hlen, wenn Punkte \u00FCbertragen werden +attempts.label = Maximale Anzahl L\u00F6sungsversuche +attempts.noLimit = unbegrenzt +attempts.max.reached = Die maximale Anzahl an Versuchen wurde erreicht. +# </OLATCE-289> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties index 016e97187057ba0ea04735ffc2f4504620ae315f..2eda534c34a6d2b5d1b359862a7b70d1f23b5542 100644 --- a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties @@ -23,6 +23,7 @@ chelp.set5=Determine if the total score of your SCORM packet should be transferr chelp.set6=Determine an integer to indicate your minimum score for passing that SCORM test. chelp.set7=OLAT tries to detect a character set automatically. If the option "Automatic" is not successful it is possible to configure the content coding by means of a predefined character set (should there be no coding the character set ISO-8899-1 will be used by default). chelp.set8=This permits the coding of Javascript by means of a predefined character set (by default the same set will be used for content and Javascript). +chelp.set9=Determine if the SCORM learning content should launch automatically if the corresponding menu-node is selected within the course. If you do not select this option, a start-page is displayed instead. chosencp=Selected SCORM learning content command.changecp=Replace SCORM learning content command.choosecp=Choose SCORM learning content @@ -49,6 +50,8 @@ header=Choose SCORM learning content headerform=Settings height.auto=Automatic height.label=Display height +fullwindow.label=Display only module, hide LMS +closeonfinish.label=Close module automatically on finish help.hover.scorm-filename=Help to select a SCORM learning content help.hover.scorm-settings-filename=Help to set SCORM settings no.cp.chosen=<i>No SCORM learning content selected</i> @@ -61,4 +64,12 @@ score.noscoreinfoyet=There is no score available for this SCORM object since you score.title=Score score.yourscore=Score achieved showmenu.label=Show menu +skiplaunchpage.label=Skip launch page shownavbuttons.label=Show navigation buttons +# <OLATCE-289> +advance.score.label = Prevent reducing score an subsequent attempt +attempts.depends.label = Count attempts only if score is transfered +attempts.label = Maximal attempts +attempts.noLimit = unlimited +attempts.max.reached = Maximum number of attempts are reached. +# </OLATCE-289> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/st/STCourseNodeDisplayConfigFormController.java b/src/main/java/org/olat/course/nodes/st/STCourseNodeDisplayConfigFormController.java index 93c504a3ab984edd11f5411d6560a6dee59b25de..0a4bd1a2b6a05ff1e2a001b884678f042d664df2 100644 --- a/src/main/java/org/olat/course/nodes/st/STCourseNodeDisplayConfigFormController.java +++ b/src/main/java/org/olat/course/nodes/st/STCourseNodeDisplayConfigFormController.java @@ -59,7 +59,7 @@ import org.olat.modules.ModuleConfiguration; * @author gnaegi, gnaegi@frentix.com, www.frentix.com */ public class STCourseNodeDisplayConfigFormController extends FormBasicController { - private static final String[] keys_displayType = new String[] { "system", "peekview", "file" }; + private static final String[] keys_displayType = new String[] { "system", "peekview", "file", "delegate" }; // read current configuration private String displayConfig = null; @@ -131,13 +131,16 @@ public class STCourseNodeDisplayConfigFormController extends FormBasicController // FormUIFactory formFact = FormUIFactory.getInstance(); // Display type - String[] values_displayType = new String[] { translate("form.system"), translate("form.peekview"), translate("form.self") }; + String[] values_displayType = new String[] { translate("form.system"), translate("form.peekview"), translate("form.self"), + translate("form.delegate") }; displayTypeRadios = formFact.addRadiosVertical("selforsystemoverview", formLayout, keys_displayType, values_displayType); displayTypeRadios.addActionListener(this, FormEvent.ONCLICK); if (displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE)) { displayTypeRadios.select("file", true); } else if (displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_PEEKVIEW)) { displayTypeRadios.select("peekview", true); + } else if (displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE)) { + displayTypeRadios.select("delegate", true); } else { displayTypeRadios.select("system", true); } @@ -153,9 +156,11 @@ public class STCourseNodeDisplayConfigFormController extends FormBasicController RulesFactory.createHideRule(displayTypeRadios, "file", selectedPeekviewChildren, formLayout); RulesFactory.createHideRule(displayTypeRadios, "system", selectedPeekviewChildren, formLayout); RulesFactory.createShowRule(displayTypeRadios, "peekview", selectedPeekviewChildren, formLayout); + RulesFactory.createHideRule(displayTypeRadios, "delegate", selectedPeekviewChildren, formLayout); RulesFactory.createHideRule(displayTypeRadios, "file", spacerChild, formLayout); RulesFactory.createHideRule(displayTypeRadios, "system", spacerChild, formLayout); RulesFactory.createShowRule(displayTypeRadios, "peekview", spacerChild, formLayout); + RulesFactory.createHideRule(displayTypeRadios, "delegate", spacerChild, formLayout); // Pre-select the first MAX_PEEKVIEW_CHILD_NODES child nodes if none is // selected to reflect meaningfull default configuration preselectConfiguredOrMaxChildNodes(); @@ -172,7 +177,7 @@ public class STCourseNodeDisplayConfigFormController extends FormBasicController if (columnsConfig == 2) { displayTwoColumns.selectAll(); } - if (displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE)) { + if (displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE) || displayConfig.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE)) { displayTwoColumns.setVisible(false); } // @@ -180,9 +185,11 @@ public class STCourseNodeDisplayConfigFormController extends FormBasicController RulesFactory.createHideRule(displayTypeRadios, "file", displayTwoColumns, formLayout); RulesFactory.createShowRule(displayTypeRadios, "peekview", displayTwoColumns, formLayout); RulesFactory.createShowRule(displayTypeRadios, "system", displayTwoColumns, formLayout); + RulesFactory.createHideRule(displayTypeRadios, "delegate", displayTwoColumns, formLayout); RulesFactory.createHideRule(displayTypeRadios, "file", spacerCols, formLayout); RulesFactory.createShowRule(displayTypeRadios, "peekview", spacerCols, formLayout); RulesFactory.createShowRule(displayTypeRadios, "system", spacerCols, formLayout); + RulesFactory.createHideRule(displayTypeRadios, "delegate", spacerCols, formLayout); } /** @@ -303,6 +310,9 @@ public class STCourseNodeDisplayConfigFormController extends FormBasicController } moduleConfig.set(STCourseNodeEditController.CONFIG_KEY_PEEKVIEW_CHILD_NODES, selectedPeekviewChildNodesConfig); } + } else if (STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE.equals(displayType)) { + moduleConfig.setStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE, + STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE); } else { // the old auto generated TOC view without peekview moduleConfig diff --git a/src/main/java/org/olat/course/nodes/st/STCourseNodeEditController.java b/src/main/java/org/olat/course/nodes/st/STCourseNodeEditController.java index ee34512efa944a4e4744a8bf79f0140e75395dc9..f100cd762a8fb8a9338c6c43683bab40ce46c12c 100644 --- a/src/main/java/org/olat/course/nodes/st/STCourseNodeEditController.java +++ b/src/main/java/org/olat/course/nodes/st/STCourseNodeEditController.java @@ -82,6 +82,8 @@ public class STCourseNodeEditController extends ActivateableTabbableDefaultContr public static final String CONFIG_VALUE_DISPLAY_TOC = "toc"; // display a detailed peek view public static final String CONFIG_VALUE_DISPLAY_PEEKVIEW = "peekview"; + //do not display peek view, delegate to first child CourseNode + public static final String CONFIG_VALUE_DISPLAY_DELEGATE = "delegate"; // key to display the enabled child node peek views public static final String CONFIG_KEY_PEEKVIEW_CHILD_NODES = "peekviewChildNodes"; // key to store the number of columns @@ -146,7 +148,7 @@ public class STCourseNodeEditController extends ActivateableTabbableDefaultContr chosenFile = (String) stNode.getModuleConfiguration().get(CONFIG_KEY_FILE); editorEnabled = (CONFIG_VALUE_DISPLAY_FILE.equals(stNode.getModuleConfiguration().getStringValue(CONFIG_KEY_DISPLAY_TYPE))); - allowRelativeLinks = stNode.getModuleConfiguration().getBooleanEntry(CONFIG_KEY_ALLOW_RELATIVE_LINKS); + allowRelativeLinks = stNode.getModuleConfiguration().getBooleanSafe(CONFIG_KEY_ALLOW_RELATIVE_LINKS); nodeDisplayConfigFormController = new STCourseNodeDisplayConfigFormController(ureq, wControl, stNode.getModuleConfiguration(), editorModel.getCourseEditorNodeById(stNode.getIdent())); listenTo(nodeDisplayConfigFormController); @@ -290,7 +292,7 @@ public class STCourseNodeEditController extends ActivateableTabbableDefaultContr } else { // user generated overview editorEnabled = false; configvc.contextPut("editorEnabled", Boolean.valueOf(editorEnabled)); - fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + //fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); // Let other config values from old config setup remain in config, // maybe used when user switches back to other config (OLAT-5610) } diff --git a/src/main/java/org/olat/course/nodes/st/_chelp/ced-st-overview.html b/src/main/java/org/olat/course/nodes/st/_chelp/ced-st-overview.html index 1e520ea041770e3a7d11a2604cab86ca0723bbad..469c7e721e523e869b141357ac15e4714430e5a7 100644 --- a/src/main/java/org/olat/course/nodes/st/_chelp/ced-st-overview.html +++ b/src/main/java/org/olat/course/nodes/st/_chelp/ced-st-overview.html @@ -7,6 +7,7 @@ <li>$r.translate("chelp.ov2")</li> <li>$r.translate("chelp.peekview")</li> <li>$r.translate("chelp.ov3")</li> + <li>$r.translate("chelp.delegate")</li> </ul> <br/> <b>$r.translate("selectedPeekviewChildren", "10"):</b>$r.translate("chelp.peekview.children")<br/><br/> diff --git a/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_de.properties index 702759a32808a10214558e7be361bb73fbbadd0e..1590e5c76d15829d3c73d8f628fa021af1fffc4a 100644 --- a/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_de.properties @@ -15,6 +15,7 @@ chelp.ms=<i>$org.olat.course.nodes\:title_ms</i> chelp.ov1=Sie k\u00F6nnen zwischen drei Darstellungsarten w\u00E4hlen, die beim Klick auf den Baustein $\:chelp.st angezeigt werden. chelp.ov2=Wenn Sie den Radio-Button $\:chelp.syst w\u00E4hlen, wird ein von OLAT generiertes Verzeichnis der untergeordneten Kursbausteine angezeigt. chelp.ov3=Wenn Sie den Radio-Button $\:chelp.self w\u00E4hlen, k\u00F6nnen Sie eine von Ihnen gew\u00E4hlte Datei aus dem Ablageordner (*.html, *.htm) anzeigen lassen. Nachdem Sie gespeichert haben, erweitert sich der Tab um das Feld $\:chelp.filec. +chelp.delegate=Wenn Sie den Radio-Button $:chelp.delgt wählen, wird anstelle einer Übersicht der erste sichtbare Kursbaustein angezeigt. chelp.peekview=Wenn Sie den Radio-Button <i>«$org.olat.course.nodes.st\:form.peekview»</i>, wird eine von OLAT generierte Vorschau der untergeordneten Kursbausteine angezeigt. chelp.peekview.children=W\u00E4hlen Sie aus der Liste der vorhandenen untergeordneten Kursbausteine diejenigen aus, welche in der Vorschau angezeigt werden sollen. Diese Liste steht nur zur Verf\u00FCgung, wenn die automatische Vorschau ausgew\u00E4hlt wurde. chelp.columns=W\u00E4hlen Sie die Checkbox <i>$\:displayTwoColumns</i> an, um das automatische Inhaltsverzeichnis bzw. die Vorschau in zwei Spalten anzeigen zu lassen. @@ -31,6 +32,7 @@ chelp.self=<i>«$org.olat.course.nodes.st\:form.self»</i> chelp.st=<i>$org.olat.course.nodes\:title_st</i> chelp.sum=<b>$org.olat.course.nodes.st\:scform.scoreNodeIndents</b> chelp.syst=<i>«$org.olat.course.nodes.st\:form.system»</i> +chelp.delgt=<i>«$org.olat.course.nodes.st\:form.delegate»</i> chelp.ta=<i>$org.olat.course.nodes\:title_ta</i> chelp.yes=<i>«$org.olat.course.nodes.st\:scform.hasScore.yes»</i> cmd.activate.easyMode=Einfachen Modus anzeigen @@ -48,6 +50,7 @@ form.peekview=automatische \u00DCbersicht mit Vorschau form.peekview.max.reached=Sie haben das Maximum von {0} Kursbausteinen erreicht, welche in der automatischen \u00DCbersicht mit Vorschau angezeigt werden können. form.peekview.error.mandatory.child = Es muss mindestens ein Kursbaustein f\u00FCr die automatische \u00DCbersicht mit Vorschau ausgew\u00E4hlt werden. form.system=automatische \u00DCbersicht +form.delegate=Keine Übersicht, erster sichtbarer Kursbaustein aktivieren help.st=Hilfe zur zusammengefassten Bewertung help.st.design=Hilfe zur Gestaltung der \u00DCbersichtsseite pane.tab.accessibility=Zugang diff --git a/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_en.properties index 2b1a305a921bd64c1b2664b152d9c1529395dad4..a93d113c0d8360f8711420331b84128ccc25756f 100644 --- a/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/st/_i18n/LocalStrings_en.properties @@ -16,6 +16,7 @@ chelp.ms=<i>$org.olat.course.nodes\:title_ms</i> chelp.ov1=You can choose between three ways of presentation by clicking on the element $\:chelp.st chelp.ov2=If you select the button $\:chelp.syst, a list of the subordinate course elements generated by OLAT will be displayed. chelp.ov3=If you select the button $\:chelp.self, a file of your choice will be displayed from the storage folder (*.html, *.htm). Saving it will extend the tab by the field $\:chelp.filec +chelp.delegate=If you select the button $:chelp.delgt, no overview will be displayed, but this structure node will delegate to it's first visible and accessible child-node. chelp.passCutVal=<b>$org.olat.course.nodes.st\:scform.passedCutValue</b> chelp.passInh=<i>«$org.olat.course.nodes.st\:scform.passedtype.inherit»</i> chelp.passNodes=<b>$org.olat.course.nodes.st\:scform.passedNodeIndents</b> @@ -31,6 +32,7 @@ chelp.self=<i>«$org.olat.course.nodes.st\:form.self»</i> chelp.st=<i>$org.olat.course.nodes\:title_st</i> chelp.sum=<b>$org.olat.course.nodes.st\:scform.scoreNodeIndents</b> chelp.syst=<i>«$org.olat.course.nodes.st\:form.system»</i> +chelp.delgt=<i>«$org.olat.course.nodes.st\:form.delegate»</i> chelp.ta=<i>$org.olat.course.nodes\:title_ta</i> chelp.yes=<i>«$org.olat.course.nodes.st\:scform.hasScore.yes»</i> cmd.activate.easyMode=Display simple mode @@ -49,6 +51,7 @@ form.peekview.max.reached=You have reached the maximum of {0} course elements th form.save=Save form.self=Your HTML page form.system=Automatic overview +form.delegate=No overview, activate first visible child node help.st=Help for combined assessment help.st.design=Help to design the overview page pane.tab.accessibility=Access diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java index 0dec9a9b897b867bf4eed598dbbffc9ee6b1dad5..0af06582a16f3f57a5dff27cbd55b7593b77ac1a 100644 --- a/src/main/java/org/olat/course/run/RunMainController.java +++ b/src/main/java/org/olat/course/run/RunMainController.java @@ -126,6 +126,7 @@ import org.olat.repository.controllers.EntryChangedEvent; import org.olat.repository.controllers.RepositoryDetailsController; import org.olat.repository.controllers.RepositoryMainController; import org.olat.repository.site.RepositorySite; +import org.olat.resource.accesscontrol.ui.SecurityGroupsRepositoryMainController; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -208,8 +209,26 @@ public class RunMainController extends MainLayoutBasicController implements Gene */ public RunMainController(final UserRequest ureq, final WindowControl wControl, final ICourse course, final String initialViewIdentifier, final boolean offerBookmark, final boolean showCourseConfigLink) { - super(ureq, wControl); - + this(ureq, wControl, course, initialViewIdentifier, offerBookmark, showCourseConfigLink, false); + } + + /** + * fxdiff: change these two constructors to allow setting of launchFromSite + * for CourseSite + * + * @param ureq + * @param wControl + * @param course + * @param initialViewIdentifier + * @param offerBookmark + * @param showCourseConfigLink + * @param launchFromSite allows to set disposed controller after init! + */ + public RunMainController(final UserRequest ureq, final WindowControl wControl, final ICourse course, final String initialViewIdentifier, + final boolean offerBookmark, final boolean showCourseConfigLink, final boolean launchFromSite) { + + super(ureq, wControl); + this.course = course; addLoggingResourceable(LoggingResourceable.wrap(course)); this.courseTitle = course.getCourseTitle(); @@ -229,6 +248,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene // set up the components all = new Panel("allofcourse"); luTree = new MenuTree("luTreeRun", this); + luTree.setExpandSelectedNode(false); contentP = new Panel("building_block_content"); // get all group memberships for this course @@ -391,14 +411,18 @@ public class RunMainController extends MainLayoutBasicController implements Gene } putInitialPanel(coursemain); - - //disposed message controller - //must be created beforehand - Panel empty = new Panel("empty");//empty panel set as "menu" and "tool" - Controller courseCloser = CourseFactory.createDisposedCourseRestartController(ureq, wControl, courseRepositoryEntry.getResourceableId()); - Controller disposedRestartController = new LayoutMain3ColsController(ureq, wControl, empty, empty, courseCloser.getInitialComponent(), "disposed course" + this.course.getResourceableId()); - setDisposedMsgController(disposedRestartController); - + + if (!launchFromSite) { + // disposed message controller + // must be created beforehand + Panel empty = new Panel("empty");// empty panel set as "menu" and "tool" + Controller courseCloser = CourseFactory.createDisposedCourseRestartController(ureq, wControl, + courseRepositoryEntry.getResourceableId()); + Controller disposedRestartController = new LayoutMain3ColsController(ureq, wControl, empty, empty, + courseCloser.getInitialComponent(), "disposed course" + this.course.getResourceableId()); + setDisposedMsgController(disposedRestartController); + } + // add as listener to course so we are being notified about course events: // - publish changes // - assessment events @@ -532,6 +556,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene luTree.setTreeModel(treeModel); String selNodeId = nclr.getSelectedNodeId(); luTree.setSelectedNodeId(selNodeId); + luTree.setOpenNodeIds(nclr.getOpenNodeIds()); CourseNode courseNode = nclr.getCalledCourseNode(); updateState(courseNode); @@ -589,7 +614,18 @@ public class RunMainController extends MainLayoutBasicController implements Gene return; } // a click to a subtree's node - if (nclr.isHandledBySubTreeModelListener()) return; + if (nclr.isHandledBySubTreeModelListener() || nclr.getSelectedNodeId() == null) { + if(nclr.getRunController() != null) { + //there is an update to the currentNodeController, apply it + if (currentNodeController != null && !currentNodeController.isDisposed()) { + currentNodeController.dispose(); + } + currentNodeController = nclr.getRunController(); + Component nodeComp = currentNodeController.getInitialComponent(); + contentP.setContent(nodeComp); + } + return; + } // set the new treemodel treeModel = nclr.getTreeModel(); @@ -598,6 +634,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene // set the new tree selection String nodeId = nclr.getSelectedNodeId(); luTree.setSelectedNodeId(nodeId); + luTree.setOpenNodeIds(nclr.getOpenNodeIds()); currentCourseNode = nclr.getCalledCourseNode(); updateState(currentCourseNode); @@ -791,7 +828,15 @@ public class RunMainController extends MainLayoutBasicController implements Gene listenTo(currentToolCtr); all.setContent(currentToolCtr.getInitialComponent()); } else throw new OLATSecurityException("clicked groupmanagement, but no according right"); - + //fxdiff VCRP-1,2: access control of resources + } else if (cmd.equals("simplegroupmngt")) { + if (hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || isCourseAdmin) { + boolean mayModifyMembers = true; + currentToolCtr = new SecurityGroupsRepositoryMainController(ureq, getWindowControl(), course, courseRepositoryEntry, mayModifyMembers); + listenTo(currentToolCtr); + all.setContent(currentToolCtr.getInitialComponent()); + } else throw new OLATSecurityException("clicked groupmanagement, but no according right"); + } else if (cmd.equals("rightmngt")) { if (isCourseAdmin) { currentToolCtr = new CourseGroupManagementMainController(ureq, getWindowControl(), course, BusinessGroup.TYPE_RIGHTGROUP); @@ -805,8 +850,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene listenTo(currentToolCtr); all.setContent(currentToolCtr.getInitialComponent()); } else throw new OLATSecurityException("clicked statistic, but no according right"); - - }else if (cmd.equals("archiver")) { + } else if (cmd.equals("archiver")) { if (hasCourseRight(CourseRights.RIGHT_ARCHIVING) || isCourseAdmin) { currentToolCtr = new ArchiverMainController(ureq, getWindowControl(), course, new IArchiverCallback() { public boolean mayArchiveQtiResults() { @@ -1087,6 +1131,9 @@ public class RunMainController extends MainLayoutBasicController implements Gene myTool.addLink(COMMAND_EDIT, translate("command.openeditor")); } if (hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || isCourseAdmin) { + //fxdiff VCRP-1,2: access control of resources + myTool.addLink("simplegroupmngt", translate("command.opensimplegroupmngt")); + // myTool.addLink("groupmngt", translate("command.opengroupmngt")); } if (isCourseAdmin) { @@ -1104,7 +1151,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene if (hasCourseRight(CourseRights.RIGHT_STATISTICS) || isCourseAdmin) { myTool.addLink("statistic", translate("command.openstatistic")); } - + // /* * if (isCourseAdmin) { myTool.addLink(TOOLBOX_LINK_COURSECONFIG, @@ -1159,7 +1206,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene // new toolbox 'general' myTool.addHeader(translate("header.tools.general")); - if (cc.isCalendarEnabled()) { + if (cc.isCalendarEnabled() && !isGuest) { myTool.addPopUpLink(ACTION_CALENDAR, translate("command.calendar"), null, null, "950", "750", false); } if (cc.hasGlossary()) { @@ -1168,14 +1215,16 @@ public class RunMainController extends MainLayoutBasicController implements Gene if (showCourseConfigLink) { myTool.addLink(TOOLBOX_LINK_COURSECONFIG, translate("command.courseconfig")); } - myTool.addPopUpLink("personalnote", translate("command.personalnote"), null, null, "750", "550", false); + if (!isGuest) { + myTool.addPopUpLink("personalnote", translate("command.personalnote"), null, null, "750", "550", false); + } if (offerBookmark && !isGuest) { myTool.addLink(ACTION_BOOKMARK, translate("command.bookmark"), TOOL_BOOKMARK, null); BookmarkManager bm = BookmarkManager.getInstance(); if (bm.isResourceableBookmarked(identity, courseRepositoryEntry)) myTool.setEnabled(TOOL_BOOKMARK, false); } - if (cc.isEfficencyStatementEnabled() && course.hasAssessableNodes()) { + if (cc.isEfficencyStatementEnabled() && course.hasAssessableNodes() && !isGuest) { // link to efficiency statements should // - not appear when not configured in course configuration // - not appear when configured in course configuration but no assessable @@ -1196,7 +1245,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene //add group chat to toolbox boolean instantMsgYes = InstantMessagingModule.isEnabled(); boolean chatIsEnabled = CourseModule.isCourseChatEnabled() && cc.isChatEnabled(); - if (instantMsgYes && chatIsEnabled) { + if (instantMsgYes && chatIsEnabled && !isGuest) { // we add the course chat link controller to the toolbox if (courseChatManagerCtr == null && ureq != null) createCourseGroupChatLink(ureq); if (courseChatManagerCtr != null){ @@ -1207,7 +1256,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene } } - if (CourseModule.displayParticipantsCount()) { + if (CourseModule.displayParticipantsCount() && !isGuest) { addCurrentUserCount(myTool); } diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties index 089857c82824503eadaec226e1d90ad03a36be29..80ea3a6bf845a47763805ab0b9b23304dbc469fd 100644 --- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties @@ -15,8 +15,9 @@ command.openarchiver=Datenarchivierung command.openassessment=Bewertungswerkzeug command.openeditor=Kurseditor command.opengroupmngt=Gruppenmanagement +command.opensimplegroupmngt=Mitgliederverwaltung command.openrightmngt=Rechtemanagement -command.openstatistic=Statistiken +command.openstatistic=Kurs Statistiken command.personalnote=Notizen course.closed = Dieser Kurs wurde beendet und wird nicht l\u00e4nger bearbeitet oder aktualisiert. course.disposed.command.restart=Den Kurs beenden und neu starten @@ -31,6 +32,7 @@ coursenoaccess.title=Keinen Zugriff auf diesen Kurs error.editoralreadylocked=Der Kurs wird momentan von {0} editiert und ist deshalb gesperrt. error.invalid.group=Sie wurden aus dieser Gruppe ausgetragen. Die Gruppe kann nicht mehr angezeigt werden. error.noglossary=Das Glossar ist zurzeit nicht verf\u00FCgbar. +error.accesscontrol=Zugang verweigert header.tools=Kurswerkzeuge header.tools.general=Allgemeines header.tools.ownerGroups=Betreute Gruppen diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties index 951a5776f11cb8155f1be66d979b1eb445ba324b..b362323a927413d3ee6f1c906c87d54e835ba1f0 100644 --- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 15:49:48 CET 2011 +#Mon May 16 17:20:33 CEST 2011 command.bookmark=Set bookmark command.calendar=Calendar command.close=Close course @@ -16,7 +16,8 @@ command.openassessment=Assessment tool command.openeditor=Course editor command.opengroupmngt=Group management command.openrightmngt=Rights management -command.openstatistic=Statistics +command.opensimplegroupmngt=Members management +command.openstatistic=Course Statistics command.personalnote=Notes course.closed=This course is closed and can therefore no longer be edited or updated. course.disposed.command.restart=Close course and restart @@ -28,6 +29,7 @@ course.noaccess.title=No access to this course course.presence.message.enter=Work on course\: course.presence.message.leave=Leave course\: coursenoaccess.title=No access to this course +error.accesscontrol=Access refused error.editoralreadylocked=This course is being edited by {0} and therefore locked. error.invalid.group=You have been removed from this group as group member. This group cannot be displayed anymore. error.noglossary=This glossary is currently not available diff --git a/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java b/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java index 5586cebb089f38d40fd94f7ed1d6d44eab514e13..25553e50d501efcb98faa9c1c8d3ee2f892c6637 100644 --- a/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java +++ b/src/main/java/org/olat/course/run/calendar/CourseLinkProviderController.java @@ -84,9 +84,16 @@ public class CourseLinkProviderController extends BasicController implements Lin // we do not use the tree event's getSelectedNodeIDs, instead // we walk through the model and fetch the children in order // to keep the sorting. - List kalendarEventLinks = kalendarEvent.getKalendarEventLinks(); + //fxdiff + List<KalendarEventLink> kalendarEventLinks = kalendarEvent.getKalendarEventLinks(); TreeNode rootNode = selectionTree.getTreeModel().getRootNode(); - kalendarEventLinks.clear(); + for(Iterator<KalendarEventLink> linkIt = kalendarEventLinks.iterator(); linkIt.hasNext(); ) { + KalendarEventLink link = linkIt.next(); + if(COURSE_LINK_PROVIDER.equals(link.getProvider())) { + linkIt.remove(); + } + } + clearSelection(rootNode); rebuildKalendarEventLinks(rootNode, te.getNodeIds(), kalendarEventLinks); // if the calendarevent is already associated with a calendar, save the modifications. diff --git a/src/main/java/org/olat/course/run/glossary/CourseGlossaryFactory.java b/src/main/java/org/olat/course/run/glossary/CourseGlossaryFactory.java index 3f978e332eda44e6a070db25928561ca7a75a1ea..f0c6a72504971da386f9c03651648389f5cd97db 100644 --- a/src/main/java/org/olat/course/run/glossary/CourseGlossaryFactory.java +++ b/src/main/java/org/olat/course/run/glossary/CourseGlossaryFactory.java @@ -21,7 +21,13 @@ package org.olat.course.run.glossary; +import java.util.Properties; + +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.commons.modules.glossary.GlossaryItemManager; import org.olat.core.commons.modules.glossary.GlossaryMainController; +import org.olat.core.commons.modules.glossary.GlossarySecurityCallback; +import org.olat.core.commons.modules.glossary.GlossarySecurityCallbackImpl; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.control.WindowControl; @@ -93,7 +99,7 @@ public class CourseGlossaryFactory { * @return */ public static GlossaryMainController createCourseGlossaryMainRunController(WindowControl lwControl, UserRequest lureq, CourseConfig cc, - boolean allowGlossaryEditing) { + boolean hasGlossaryRights) { if (cc.hasGlossary()) { RepositoryEntry repoEntry = RepositoryManager.getInstance().lookupRepositoryEntryBySoftkey(cc.getGlossarySoftKey(), false); @@ -101,8 +107,17 @@ public class CourseGlossaryFactory { // seems to be removed return null; } + boolean owner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(lureq.getIdentity(), repoEntry.getOwnerGroup()); VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(repoEntry.getOlatResource()); - return new GlossaryMainController(lwControl, lureq, glossaryFolder, repoEntry.getOlatResource(), allowGlossaryEditing); + Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); + boolean editUsersEnabled = "true".equals(glossProps.getProperty(GlossaryItemManager.EDIT_USERS)); + GlossarySecurityCallback secCallback; + if (lureq.getUserSession().getRoles().isGuestOnly()) { + secCallback = new GlossarySecurityCallbackImpl(); + } else { + secCallback = new GlossarySecurityCallbackImpl(hasGlossaryRights, owner, editUsersEnabled, lureq.getIdentity().getKey()); + } + return new GlossaryMainController(lwControl, lureq, glossaryFolder, repoEntry.getOlatResource(), secCallback, true); } return null; } diff --git a/src/main/java/org/olat/course/run/glossary/CourseGlossaryToolLinkController.java b/src/main/java/org/olat/course/run/glossary/CourseGlossaryToolLinkController.java index 7bc7c4ed70f5f515f2574aaddbc1791b7d65ce7a..584cf0be76f988c3d3511e0a8d655ef0b67304e8 100644 --- a/src/main/java/org/olat/course/run/glossary/CourseGlossaryToolLinkController.java +++ b/src/main/java/org/olat/course/run/glossary/CourseGlossaryToolLinkController.java @@ -21,9 +21,11 @@ */ package org.olat.course.run.glossary; +import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; import org.olat.core.commons.modules.glossary.GlossaryMainController; +import org.olat.core.commons.modules.glossary.OpenAuthorProfilEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; @@ -40,6 +42,7 @@ import org.olat.core.gui.control.generic.dtabs.DTabs; import org.olat.core.gui.control.generic.messages.MessageUIFactory; import org.olat.core.gui.control.generic.textmarker.GlossaryMarkupItemController; import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; import org.olat.core.util.prefs.Preferences; import org.olat.course.ICourse; import org.olat.course.config.CourseConfig; @@ -47,6 +50,9 @@ import org.olat.course.run.RunMainController; import org.olat.course.run.environment.CourseEnvironment; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.olat.user.HomePageConfig; +import org.olat.user.HomePageConfigManagerImpl; +import org.olat.user.HomePageDisplayController; /** * Description:<br> @@ -144,6 +150,7 @@ public class CourseGlossaryToolLinkController extends BasicController { public Controller createController(UserRequest lureq, WindowControl lwControl) { GlossaryMainController glossaryController = CourseGlossaryFactory.createCourseGlossaryMainRunController(lwControl, lureq, cc, allowGlossaryEditing); + listenTo(glossaryController); if (glossaryController == null) { // happens in the unlikely event of a user who is in a course and // now @@ -169,6 +176,37 @@ public class CourseGlossaryToolLinkController extends BasicController { } } + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(event instanceof OpenAuthorProfilEvent) { + OpenAuthorProfilEvent uriEvent = (OpenAuthorProfilEvent)event; + Long identityKey = uriEvent.getKey(); + if(identityKey == null) return; + Identity identity = BaseSecurityManager.getInstance().loadIdentityByKey(identityKey, false); + if(identity == null) return; + + final HomePageConfig homePageConfig = HomePageConfigManagerImpl.getInstance().loadConfigFor(identity.getName()); + + ControllerCreator ctrlCreator = new ControllerCreator() { + public Controller createController(UserRequest lureq, WindowControl lwControl) { + HomePageDisplayController homePageCtrl = new HomePageDisplayController(lureq, lwControl, homePageConfig); + LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, lwControl, null, null, homePageCtrl + .getInitialComponent(), null); + // dispose glossary on layout dispose + layoutCtr.addDisposableChildController(homePageCtrl); + return layoutCtr; + } + }; + + ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createAuthMinimalPopupLayout(ureq, ctrlCreator); + // open in new browser window + openInNewBrowserWindow(ureq, layoutCtrlr); + return;// immediate return after opening new browser window! + } else { + super.event(ureq, source, event); + } + } + /** * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ diff --git a/src/main/java/org/olat/course/run/navigation/NavigationHandler.java b/src/main/java/org/olat/course/run/navigation/NavigationHandler.java index 6e34215b84752dd9139bfbb39505b9c87d57e356..c12e2b3d3f3bb0aec76210264038ee28d703ac4b 100644 --- a/src/main/java/org/olat/course/run/navigation/NavigationHandler.java +++ b/src/main/java/org/olat/course/run/navigation/NavigationHandler.java @@ -21,6 +21,14 @@ package org.olat.course.run.navigation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.tree.GenericTreeModel; import org.olat.core.gui.components.tree.TreeEvent; @@ -30,7 +38,6 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.ControllerEventListener; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.messages.MessageUIFactory; -import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.BusinessControlFactory; @@ -42,16 +49,19 @@ import org.olat.core.logging.activity.CourseLoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.Formatter; import org.olat.core.util.Util; +import org.olat.core.util.nodes.INode; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.xml.XStreamHelper; import org.olat.course.editor.EditorMainController; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.CourseNodeFactory; +import org.olat.course.nodes.STCourseNode; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.TreeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.util.logging.activity.LoggingResourceable; + /** * Description: <br> * TODO: Felix Jost Class Description for NavigationHandler @@ -59,16 +69,20 @@ import org.olat.util.logging.activity.LoggingResourceable; * @author Felix Jost */ public class NavigationHandler { - OLog log = Tracing.createLoggerFor(NavigationHandler.class); + private static final OLog log = Tracing.createLoggerFor(NavigationHandler.class); - private static final String LOG_NODE_ACCESS = "NODE_ACCESS"; - private static final String LOG_NODE_NO_ACCESS = "NODE_NO_ACCESS"; + private final UserCourseEnvironment userCourseEnv; private final boolean previewMode; // remember so subsequent click to a subtreemodel's node has a handler private ControllerEventListener subtreemodelListener = null; + + private String selectedCourseNodeId; + private Set<String> openCourseNodeIds = new HashSet<String>(); + private List<String> openTreeNodeIds = new ArrayList<String>(); + private Map<String,TreeModel> externalTreeModels = new HashMap<String,TreeModel>(); /** * @param userCourseEnv @@ -98,7 +112,7 @@ public class NavigationHandler { } else { cn = calledCourseNode; } - return doEvaluateJumpTo(ureq, wControl, cn, listeningController, nodecmd); + return doEvaluateJumpTo(ureq, wControl, cn, listeningController, nodecmd, null, null); } /** @@ -124,14 +138,57 @@ public class NavigationHandler { Object userObject = selTN.getUserObject(); if (!(userObject instanceof NodeEvaluation)) { // yes, appropriate - if (subtreemodelListener == null) throw new AssertException("no handler for subtreemodelcall!"); + + NodeRunConstructionResult nrcr = null; + CourseNode internCourseNode = null; + GenericTreeModel subTreeModel; + if (subtreemodelListener == null) { + //throw new AssertException("no handler for subtreemodelcall!"); + //reattach the subtreemodellistener + TreeNode internNode = getFirstInternParentNode(selTN); + NodeEvaluation prevEval = (NodeEvaluation) internNode.getUserObject(); + internCourseNode = prevEval.getCourseNode(); + + final OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseNode.class, Long.parseLong(internCourseNode.getIdent())); + ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(ores); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); + nrcr = internCourseNode.createNodeRunConstructionResult(ureq, bwControl, userCourseEnv, prevEval, nodecmd); + // remember as instance variable for next click + subtreemodelListener = nrcr.getSubTreeListener(); + subTreeModel = (GenericTreeModel)nrcr.getSubTreeModel(); + externalTreeModels.put(internCourseNode.getIdent(), subTreeModel); + } else { + TreeNode internNode = getFirstInternParentNode(selTN); + NodeEvaluation prevEval = (NodeEvaluation) internNode.getUserObject(); + internCourseNode = prevEval.getCourseNode(); + subTreeModel = (GenericTreeModel)externalTreeModels.get(internCourseNode.getIdent()); + } if (log.isDebug()){ log.debug("delegating to handler: treeNodeId = " + treeNodeId); } + + // update the node and event to match the new tree model + selTN = subTreeModel.findNodeByUserObject(userObject); + treeEvent = new TreeEvent(treeEvent.getCommand(), treeEvent.getSubCommand(), selTN.getIdent()); + + boolean dispatch = true; + if(userObject instanceof String) { + if(TreeEvent.COMMAND_TREENODE_OPEN.equals(treeEvent.getSubCommand())) { + openCourseNodeIds.add((String)userObject); + openTreeNodeIds.add((String)userObject); + dispatch = false; + } else if(TreeEvent.COMMAND_TREENODE_CLOSE.equals(treeEvent.getSubCommand())) { + removeChildrenFromOpenNodes(selTN); + dispatch = false; + } + } + + if(dispatch) { // null as controller source since we are not a controller - subtreemodelListener.dispatchEvent(ureq, null, treeEvent); - // no node construction result indicates handled - ncr = new NodeClickedRef(null, true, null, null, null); + subtreemodelListener.dispatchEvent(ureq, null, treeEvent); + // no node construction result indicates handled + } + ncr = new NodeClickedRef(treeModel, true, null, null, internCourseNode, nrcr, true); } else { // normal dispatching to a coursenode. // get the courseNode that was called @@ -144,17 +201,25 @@ public class NavigationHandler { // the new node controller. It is important that the old node controller is // disposed before the new one to not get conflicts with cacheable mappers that // might be used in both controllers with the same ID (e.g. the course folder) - if (currentNodeController != null) { - currentNodeController.dispose(); + if(TreeEvent.COMMAND_TREENODE_OPEN.equals(treeEvent.getSubCommand()) || TreeEvent.COMMAND_TREENODE_CLOSE.equals(treeEvent.getSubCommand())) { + if(isInParentLine(calledCourseNode)) { + if (currentNodeController != null) { + currentNodeController.dispose(); + } + } + ncr = doEvaluateJumpTo(ureq, wControl, calledCourseNode, listeningController, nodecmd, treeEvent.getSubCommand(), currentNodeController); + } else { + if (currentNodeController != null) { + currentNodeController.dispose(); + } + ncr = doEvaluateJumpTo(ureq, wControl, calledCourseNode, listeningController, nodecmd, treeEvent.getSubCommand(), currentNodeController); } - ncr = doEvaluateJumpTo(ureq, wControl, calledCourseNode, listeningController, nodecmd); } return ncr; - } private NodeClickedRef doEvaluateJumpTo(UserRequest ureq, WindowControl wControl, CourseNode courseNode, - ControllerEventListener listeningController, String nodecmd) { + ControllerEventListener listeningController, String nodecmd, String nodeSubCmd, Controller currentNodeController) { NodeClickedRef nclr; if (log.isDebug()){ log.debug("evaluateJumpTo courseNode = " + courseNode.getIdent() + ", " + courseNode.getShortName()); @@ -178,7 +243,7 @@ public class NavigationHandler { // -> issue an user infomative msg // nclr: the new treemodel, not visible, no selected nodeid, no // calledcoursenode, no nodeconstructionresult - nclr = new NodeClickedRef(treeModel, false, null, null, null); + nclr = new NodeClickedRef(treeModel, false, null, null, null, null, false); } else { // calculate the NodeClickedRef // 1. get the correct (new) nodeevaluation @@ -207,13 +272,31 @@ public class NavigationHandler { NodeRunConstructionResult ncr = new NodeRunConstructionResult(controller, null, null, null); // nclr: the new treemodel, visible, selected nodeid, calledcoursenode, // nodeconstructionresult - nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, courseNode, ncr); + nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, null, courseNode, ncr, false); } else if (!CourseNodeFactory.getInstance().getCourseNodeConfigurationEvenForDisabledBB(courseNode.getType()).isEnabled()) { Translator pT = Util.createPackageTranslator(EditorMainController.class, ureq.getLocale()); Controller controller = MessageUIFactory.createInfoMessage(ureq, wControl, null, pT.translate("course.building.block.disabled.user")); NodeRunConstructionResult ncr = new NodeRunConstructionResult(controller, null, null, null); - nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, courseNode, ncr); - } else { // access ok + nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, null, courseNode, ncr, false); + } else { // access ok + + // fxdiff FXOLAT-262 + if (STCourseNode.isDelegatingSTCourseNode(courseNode) && (courseNode.getChildCount() > 0)) { + // the clicked node is a STCourse node and is set to "delegate", so + // delegate to its first visible child; if no child is visible, just skip and do normal eval + INode child; + for (int i = 0; i < courseNode.getChildCount(); i++) { + child = courseNode.getChildAt(i); + if (child instanceof CourseNode) { + CourseNode cn = (CourseNode) child; + NodeEvaluation cnEval = cn.eval(userCourseEnv.getConditionInterpreter(), treeEval); + if (cnEval.isVisible()) return this.doEvaluateJumpTo(ureq, wControl, cn, listeningController, nodecmd, nodeSubCmd, + currentNodeController); + } + } + } + + // access the node, display its result in the right pane NodeRunConstructionResult ncr; @@ -224,10 +307,8 @@ public class NavigationHandler { Long oresK = new Long(Long.parseLong(courseNode.getIdent())); final OLATResourceable ores = OresHelper.createOLATResourceableInstance(oresC, oresK); - //REVIEW:pb:this is responsible for building up the jumpable businesspath/REST URL kind of ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(ores); WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); - if (previewMode) { ncr = new NodeRunConstructionResult(courseNode.createPreviewController(ureq, bwControl, userCourseEnv, nodeEval)); } else { @@ -236,16 +317,47 @@ public class NavigationHandler { // remember as instance variable for next click subtreemodelListener = ncr.getSubTreeListener(); if (subtreemodelListener != null) { - addSubTreeModel(newCalledTreeNode, ncr.getSubTreeModel()); + externalTreeModels.put(courseNode.getIdent(), ncr.getSubTreeModel()); + if(!newSelectedNodeId.equals(ncr.getSelectedTreeNodeId())) { + TreeNode selectedNode = ncr.getSubTreeModel().getNodeById(ncr.getSelectedTreeNodeId()); + openCourseNodeIds.add((String)selectedNode.getUserObject()); + } } } + + if(TreeEvent.COMMAND_TREENODE_OPEN.equals(nodeSubCmd)) { + openCourseNodeIds.add(courseNode.getIdent()); + newSelectedNodeId = convertToTreeNodeId(treeEval, selectedCourseNodeId); + } else if(TreeEvent.COMMAND_TREENODE_CLOSE.equals(nodeSubCmd)) { + removeChildrenFromOpenNodes(courseNode); + newSelectedNodeId = convertToTreeNodeId(treeEval, selectedCourseNodeId); + if(!isInParentLine(courseNode)) { + selectedCourseNodeId = courseNode.getIdent(); + } else { + selectedCourseNodeId = null; + newSelectedNodeId = null; + } + } else { + //add the selected node to the open one, if not, strange behaviour + selectedCourseNodeId = courseNode.getIdent(); + openCourseNodeIds.add(selectedCourseNodeId); + } + + openTreeNodeIds = convertToTreeNodeIds(treeEval, openCourseNodeIds); + reattachExternalTreeModels(treeEval); + - // nclr: the new treemodel, visible, selected nodeid, calledcoursenode, - // nodeconstructionresult - nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, courseNode, ncr); - // attach listener; we know we have a runcontroller here - if (listeningController != null) { - nclr.getRunController().addControllerListener(listeningController); + if((TreeEvent.COMMAND_TREENODE_OPEN.equals(nodeSubCmd) || TreeEvent.COMMAND_TREENODE_CLOSE.equals(nodeSubCmd)) && + currentNodeController != null && !currentNodeController.isDisposed()) { + nclr = new NodeClickedRef(treeModel, true, null, openTreeNodeIds, null, null, false); + } else { + // nclr: the new treemodel, visible, selected nodeid, calledcoursenode, + // nodeconstructionresult + nclr = new NodeClickedRef(treeModel, true, newSelectedNodeId, openTreeNodeIds, courseNode, ncr, false); + // attach listener; we know we have a runcontroller here + if (listeningController != null) { + nclr.getRunController().addControllerListener(listeningController); + } } // write log information ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_NAVIGATION_NODE_ACCESS, getClass(), @@ -254,6 +366,81 @@ public class NavigationHandler { } return nclr; } + + private void reattachExternalTreeModels(TreeEvaluation treeEval) { + if(externalTreeModels == null || externalTreeModels.isEmpty()) return; + + for(Map.Entry<String, TreeModel> entry:externalTreeModels.entrySet()) { + String courseNodeId = entry.getKey(); + TreeModel treeModel = entry.getValue(); + + CourseNode courseNode = userCourseEnv.getCourseEnvironment().getRunStructure().getNode(courseNodeId); + TreeNode treeNode = treeEval.getCorrespondingTreeNode(courseNode); + if(treeNode != null) { + addSubTreeModel(treeNode, treeModel); + } + } + } + + private TreeNode getFirstInternParentNode(TreeNode node) { + while(node != null) { + if(node.getUserObject() instanceof NodeEvaluation) { + return node; + } + node = (TreeNode)node.getParent(); + } + return null; + } + + private void removeChildrenFromOpenNodes(TreeNode treeNode) { + openCourseNodeIds.remove(treeNode.getIdent()); + openCourseNodeIds.remove(treeNode.getUserObject()); + for(int i=treeNode.getChildCount(); i-->0; ) { + removeChildrenFromOpenNodes((TreeNode)treeNode.getChildAt(i)); + } + } + + private void removeChildrenFromOpenNodes(CourseNode courseNode) { + openCourseNodeIds.remove(courseNode.getIdent()); + for(int i=courseNode.getChildCount(); i-->0; ) { + removeChildrenFromOpenNodes((CourseNode)courseNode.getChildAt(i)); + } + } + + private boolean isInParentLine(CourseNode courseNode) { + if(selectedCourseNodeId == null) return false; + + CourseNode selectedCourseNode = userCourseEnv.getCourseEnvironment().getRunStructure().getNode(selectedCourseNodeId); + while(selectedCourseNode != null) { + if(selectedCourseNode.getIdent().equals(courseNode.getIdent())) { + return true; + } + selectedCourseNode = (CourseNode)selectedCourseNode.getParent(); + } + return false; + } + + private List<String> convertToTreeNodeIds(TreeEvaluation treeEval, Collection<String> courseNodeIds) { + if(courseNodeIds == null || courseNodeIds.isEmpty()) return new ArrayList<String>(); + + List<String> convertedIds = new ArrayList<String>(courseNodeIds.size()); + for(String courseNodeId:courseNodeIds) { + convertedIds.add(convertToTreeNodeId(treeEval, courseNodeId)); + } + return convertedIds; + } + + private String convertToTreeNodeId(TreeEvaluation treeEval, String courseNodeId) { + if(courseNodeId == null) return null; + + CourseNode courseNode = userCourseEnv.getCourseEnvironment().getRunStructure().getNode(courseNodeId); + TreeNode newCalledTreeNode = treeEval.getCorrespondingTreeNode(courseNode); + if(newCalledTreeNode == null) { + return courseNodeId; + } else { + return newCalledTreeNode.getIdent(); + } + } private void addSubTreeModel(TreeNode parent, TreeModel modelToAppend) { // ignore root and directly add children. @@ -265,8 +452,8 @@ public class NavigationHandler { // full cloning of ETH webclass energie takes about 4/100 of a second for (int i = chdCnt; i > 0; i--) { - TreeNode chd = (TreeNode) root.getChildAt(i-1); - TreeNode chdc = (TreeNode) XStreamHelper.xstreamClone(chd); + INode chd = root.getChildAt(i-1); + INode chdc = (INode) XStreamHelper.xstreamClone(chd); // always insert before already existing course building block children parent.insert(chdc, 0); } diff --git a/src/main/java/org/olat/course/run/navigation/NodeClickedRef.java b/src/main/java/org/olat/course/run/navigation/NodeClickedRef.java index 7a3b4ce06df62e636a4007168d17cb7b049f88a7..6efa2cf50944f19fced14341338d5e588865363f 100644 --- a/src/main/java/org/olat/course/run/navigation/NodeClickedRef.java +++ b/src/main/java/org/olat/course/run/navigation/NodeClickedRef.java @@ -21,6 +21,8 @@ package org.olat.course.run.navigation; +import java.util.List; + import org.olat.core.gui.components.tree.TreeModel; import org.olat.core.gui.control.Controller; import org.olat.course.nodes.CourseNode; @@ -52,6 +54,8 @@ public class NodeClickedRef { // no node is selected // (which can only be the case if even the root course node is not visible) private String selectedNodeId; + + private List<String> openNodeIds; // the coursenode which was called when clicking the treenode (used only to // update scoring on that coursenode). @@ -61,6 +65,9 @@ public class NodeClickedRef { // the resulting controller, the subtreelistener, and the subtree: fetched // from the coursenode private NodeRunConstructionResult nodeConstructionResult; + + + private boolean handledBySubTreeModelListener; /** * @param treeModel @@ -70,20 +77,22 @@ public class NodeClickedRef { * @param nodeConstructionResult null means that no new node controller has * been created, but is was handled by the subtreemodellistener */ - NodeClickedRef(TreeModel treeModel, boolean visible, String selectedNodeId, CourseNode calledCourseNode, - NodeRunConstructionResult nodeConstructionResult) { + NodeClickedRef(TreeModel treeModel, boolean visible, String selectedNodeId, List<String> openNodeIds, CourseNode calledCourseNode, + NodeRunConstructionResult nodeConstructionResult, boolean handledBySubTreeModelListener) { this.treeModel = treeModel; this.visible = visible; this.selectedNodeId = selectedNodeId; + this.openNodeIds = openNodeIds; this.calledCourseNode = calledCourseNode; this.nodeConstructionResult = nodeConstructionResult; + this.handledBySubTreeModelListener = handledBySubTreeModelListener; } /** * @return if handled by the sublistener */ public boolean isHandledBySubTreeModelListener() { - return nodeConstructionResult == null; + return handledBySubTreeModelListener; } /** @@ -97,14 +106,22 @@ public class NodeClickedRef { * @return the selected node id */ public String getSelectedNodeId() { - String subNodeId = nodeConstructionResult.getSelectedTreeNodeId(); - // if subNodeId != null (e.g. a inner node of a content-packaging -> select this node - if (subNodeId != null) { - return subNodeId; - } else { + if(nodeConstructionResult == null) { return selectedNodeId; + } else { + String subNodeId = nodeConstructionResult.getSelectedTreeNodeId(); + // if subNodeId != null (e.g. a inner node of a content-packaging -> select this node + if (subNodeId != null) { + return subNodeId; + } else { + return selectedNodeId; + } } } + + public List<String> getOpenNodeIds() { + return openNodeIds; + } /** * @return the treemodel @@ -124,6 +141,7 @@ public class NodeClickedRef { * @return the run controller or null */ public Controller getRunController() { + if(calledCourseNode == null || nodeConstructionResult == null) return null; RepositoryManager.setLastUsageNowFor(calledCourseNode.getReferencedRepositoryEntry()); return nodeConstructionResult.getRunController(); } diff --git a/src/main/java/org/olat/course/run/preview/PreviewRunController.java b/src/main/java/org/olat/course/run/preview/PreviewRunController.java index ce8d8c467438b30a717df8991b588ee4bc105668..ac243ef103b8ea3300ad12cd3d2fe4a4d77d9e34 100644 --- a/src/main/java/org/olat/course/run/preview/PreviewRunController.java +++ b/src/main/java/org/olat/course/run/preview/PreviewRunController.java @@ -149,7 +149,10 @@ public class PreviewRunController extends MainLayoutBasicController { getWindowControl().setWarning(translate("warn.notvisible")); return; } - if (nclr.isHandledBySubTreeModelListener()) return; + if (nclr.isHandledBySubTreeModelListener()) { + //not used: + return; + } // set the new treemodel treeModel = nclr.getTreeModel(); @@ -157,16 +160,23 @@ public class PreviewRunController extends MainLayoutBasicController { // set the new tree selection luTree.setSelectedNodeId(nclr.getSelectedNodeId()); + luTree.setOpenNodeIds(nclr.getOpenNodeIds()); // get the controller (in this case it is a preview controller). Dispose only if not already disposed in navHandler.evaluateJumpToTreeNode() - if (currentNodeController != null && !currentNodeController.isDisposed()) currentNodeController.dispose(); - currentNodeController = nclr.getRunController(); + if(nclr.getRunController() != null) { + if (currentNodeController != null && !currentNodeController.isDisposed()){ + currentNodeController.dispose(); + } + currentNodeController = nclr.getRunController(); + } CourseNode cn = nclr.getCalledCourseNode(); - Condition c = cn.getPreConditionVisibility(); - String visibilityExpr = (c.getConditionExpression() == null? translate("details.visibility.none") : c.getConditionExpression()); - detail.contextPut("visibilityExpr", visibilityExpr); - detail.contextPut("coursenode", cn); + if(cn != null) { + Condition c = cn.getPreConditionVisibility(); + String visibilityExpr = (c.getConditionExpression() == null? translate("details.visibility.none") : c.getConditionExpression()); + detail.contextPut("visibilityExpr", visibilityExpr); + detail.contextPut("coursenode", cn); + } Component nodeComp = currentNodeController.getInitialComponent(); content.setContent(nodeComp); @@ -222,6 +232,7 @@ public class PreviewRunController extends MainLayoutBasicController { luTree.setTreeModel(treeModel); String selNodeId = nclr.getSelectedNodeId(); luTree.setSelectedNodeId(selNodeId); + luTree.setOpenNodeIds(nclr.getOpenNodeIds()); // dispose old node controller if (currentNodeController != null) { diff --git a/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml b/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml index bac7920723b8b432704dddcd7aa8218bb1073c66..bf97de97791c931833fbc270e2fa9d946ea60b68 100644 --- a/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml +++ b/src/main/java/org/olat/course/statistic/_spring/statisticContext.xml @@ -429,7 +429,7 @@ <list> <value> update o_stat_weekly as old, - (select businesspath,concat(year(creationdate),'-',lpad(week(creationdate,3),2,'0')) week, count(*) cnt from o_loggingtable where actionverb='launch' and actionobject='node' group by businesspath,week) as delta + (select businesspath,concat(year(creationdate),'-',lpad(week(creationdate,3),2,'0')) week, count(*) cnt from o_stat_temptable group by businesspath,week) as delta SET old.value=old.value+delta.cnt where old.week=delta.week and old.businesspath=delta.businesspath; </value> diff --git a/src/main/java/org/olat/course/tree/CourseEditorTreeModel.java b/src/main/java/org/olat/course/tree/CourseEditorTreeModel.java index 547b4061fa3a07bc8541603fd764b4713e128ad0..fc3b507c3b4ae06bf3a79dd60d9d38be2767a7d7 100644 --- a/src/main/java/org/olat/course/tree/CourseEditorTreeModel.java +++ b/src/main/java/org/olat/course/tree/CourseEditorTreeModel.java @@ -21,7 +21,9 @@ package org.olat.course.tree; +import org.olat.core.gui.components.tree.DnDTreeModel; import org.olat.core.gui.components.tree.GenericTreeModel; +import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; @@ -35,7 +37,8 @@ import org.olat.course.nodes.CourseNode; * * @author Felix Jost */ -public class CourseEditorTreeModel extends GenericTreeModel { +//fxdiff VCRP-9: drag and drop in menu tree +public class CourseEditorTreeModel extends GenericTreeModel implements DnDTreeModel { private long latestPublishTimestamp = -1; private long highestNodeId; // start at Long.MAX_VALUE - 1000000; if set to // zero -> meaning we read from an old @@ -226,6 +229,39 @@ public class CourseEditorTreeModel extends GenericTreeModel { return cloneCn; } + + //fxdiff VCRP-9: drag and drop in menu tree + @Override + public boolean canDrop(TreeNode droppedNode, TreeNode targetNode, boolean sibling) { + if(droppedNode == null || targetNode == null) return false; + + CourseEditorTreeNode selectedNode = getCourseEditorNodeById(droppedNode.getIdent()); + CourseEditorTreeNode parentNode = getCourseEditorNodeById(targetNode.getIdent()); + if(selectedNode == null || parentNode == null) return false; + if(sibling && parentNode.getParent() != null) { + parentNode = getCourseEditorNodeById(parentNode.getParent().getIdent()); + } + + // check if insert position is within the to-be-copied tree + if (checkIfIsChild(parentNode, selectedNode)) { + return false; + } + return true; + } + + //fxdiff VCRP-9: drag and drop in menu tree + public boolean checkIfIsChild(CourseEditorTreeNode prospectChild, CourseEditorTreeNode sourceTree) { + if (sourceTree.getIdent().equals(prospectChild.getIdent())) { + return true; + } + for (int i = sourceTree.getChildCount(); i-->0; ) { + INode child = sourceTree.getChildAt(i); + if (checkIfIsChild(prospectChild, getCourseEditorNodeById(child.getIdent()))) { + return true; + } + } + return false; + } /** * @return Returns the version. diff --git a/src/main/java/org/olat/dispatcher/DMZDispatcher.java b/src/main/java/org/olat/dispatcher/DMZDispatcher.java index e9efc70a6230b5e4346cfc40e1cd5801088da3e7..b802fdecfe007b8e03df4b88f35ebe927d6dd70a 100644 --- a/src/main/java/org/olat/dispatcher/DMZDispatcher.java +++ b/src/main/java/org/olat/dispatcher/DMZDispatcher.java @@ -22,6 +22,7 @@ package org.olat.dispatcher; +import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; @@ -36,7 +37,10 @@ import org.olat.core.gui.Windows; import org.olat.core.gui.components.Window; import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.control.ChiefControllerCreator; +import org.olat.core.gui.control.generic.dtabs.DTabs; import org.olat.core.gui.exception.MsgFactory; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.Tracing; import org.olat.core.util.UserSession; import org.olat.core.util.i18n.I18nManager; @@ -47,6 +51,9 @@ import org.olat.core.util.i18n.I18nManager; * @author Mike Stock */ public class DMZDispatcher implements Dispatcher { + //fxdiff FXOLAT-113: business path in DMZ + public static final String DMZDISPATCHER_BUSINESSPATH = "DMZDispatcher:businessPath"; + /** * set by spring to create the starting workflow for /dmz/ */ @@ -289,6 +296,13 @@ public class DMZDispatcher implements Dispatcher { window = occ.getWindow(); window.setUriPrefix(uriPrefix); ws.registerWindow(window); + //fxdiff FXOLAT-113: business path in DMZ + String businessPath = (String) usess.removeEntryFromNonClearedStore(DMZDISPATCHER_BUSINESSPATH); + if (businessPath != null) { + List<ContextEntry> ces = BusinessControlFactory.getInstance().createCEListFromString(businessPath); + DTabs dts = (DTabs) window.getAttribute("DTabs"); + dts.activate(ureq, null, null, ces); + } window.dispatchRequest(ureq, true); diff --git a/src/main/java/org/olat/dispatcher/RESTDispatcher.java b/src/main/java/org/olat/dispatcher/RESTDispatcher.java index fc05389f8208327278b0d17ef1eaecd6c74e3bd7..ef3ac2f9faa1b444d90fed5a6916ad2e0a0ecc63 100644 --- a/src/main/java/org/olat/dispatcher/RESTDispatcher.java +++ b/src/main/java/org/olat/dispatcher/RESTDispatcher.java @@ -27,6 +27,7 @@ import java.util.Locale; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.AuthHelper; import org.olat.core.CoreSpringFactory; import org.olat.core.dispatcher.Dispatcher; @@ -42,6 +43,7 @@ import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.WebappHelper; import org.olat.core.util.i18n.I18nManager; @@ -173,33 +175,53 @@ public class RESTDispatcher implements Dispatcher { if (!usess.isAuthenticated() || !restIdentity.equalsByPersistableKey(usess.getIdentity())) { // Re-authenticate user session for this user and start a fresh // standard OLAT session - AuthHelper.doLogin(restIdentity, RestSecurityHelper.SEC_TOKEN, ureq); + int loginStatus = AuthHelper.doLogin(restIdentity, RestSecurityHelper.SEC_TOKEN, ureq); + if (loginStatus == AuthHelper.LOGIN_OK) { + //fxdiff: FXOLAT-268 update last login date and register active user + UserDeletionManager.getInstance().setIdentityAsActiv(restIdentity); + } else { + //error, redirect to login screen + DispatcherAction.redirectToDefaultDispatcher(response); + } } else if (Windows.getWindows(usess).getAttribute("AUTHCHIEFCONTROLLER") == null) { // Session is already available, but no main window (Head-less REST // session). Only create the base chief controller and the window AuthHelper.createAuthHome(ureq); + // no need to call setIdentityAsActive as this was already done by RestApiLoginFilter... } } } boolean auth = usess.isAuthenticated(); if (auth) { - usess.putEntryInNonClearedStore(AuthenticatedDispatcher.AUTHDISPATCHER_BUSINESSPATH, businessPath); + //fxdiff FXOLAT-113: business path in DMZ + setBusinessPathInUserSession(usess, businessPath); - String url = getRedirectToURL(usess); - DispatcherAction.redirectTo(response, url); + //fxdiff + if (Windows.getWindows(usess).getAttribute("AUTHCHIEFCONTROLLER") == null) { + // Session is already available, but no main window (Head-less REST + // session). Only create the base chief controller and the window + AuthHelper.createAuthHome(ureq); + String url = getRedirectToURL(usess) + ";jsessionid=" + usess.getSessionInfo().getSession().getId(); + DispatcherAction.redirectTo(response, url); + } else { + String url = getRedirectToURL(usess); + DispatcherAction.redirectTo(response, url); + } } else { //prepare for redirect - usess.putEntryInNonClearedStore(AuthenticatedDispatcher.AUTHDISPATCHER_BUSINESSPATH, businessPath); + //fxdiff FXOLAT-113: business path in DMZ + setBusinessPathInUserSession(usess, businessPath); String invitationAccess = ureq.getParameter(AuthenticatedDispatcher.INVITATION); - - if (invitationAccess != null && LoginModule.isInvitationEnabled()) { // try to log in as anonymous // use the language from the lang paramter if available, otherwhise use the system default locale Locale guestLoc = getLang(ureq); int loginStatus = AuthHelper.doInvitationLogin(invitationAccess, ureq, guestLoc); if ( loginStatus == AuthHelper.LOGIN_OK) { + Identity invite = usess.getIdentity(); + //fxdiff: FXOLAT-268 update last login date and register active user + UserDeletionManager.getInstance().setIdentityAsActiv(invite); //logged in as invited user, continue String url = getRedirectToURL(usess); DispatcherAction.redirectTo(response, url); @@ -234,6 +256,23 @@ public class RESTDispatcher implements Dispatcher { } } + /** + * The method allows for a finite sets of business path to redirect to the DMZ + * @param usess + * @param businessPath + */ + //fxdiff FXOLAT-113: business path in DMZ + private void setBusinessPathInUserSession(UserSession usess, String businessPath) { + if(StringHelper.containsNonWhitespace(businessPath) && usess != null) { + if(businessPath.startsWith("[changepw:0]") || "[registration:0]".equals(businessPath) || "[guest:0]".equals(businessPath) + || "[browsercheck:0]".equals(businessPath) || "[accessibility:0]".equals(businessPath) || "[about:0]".equals(businessPath)) { + usess.putEntryInNonClearedStore(DMZDispatcher.DMZDISPATCHER_BUSINESSPATH, businessPath); + } else { + usess.putEntryInNonClearedStore(AuthenticatedDispatcher.AUTHDISPATCHER_BUSINESSPATH, businessPath); + } + } + } + private Locale getLang(UserRequest ureq) { // try to log in as anonymous // use the language from the lang paramter if available, otherwhise use the system default locale diff --git a/src/main/java/org/olat/dispatcher/RemoteLoginformDispatcher.java b/src/main/java/org/olat/dispatcher/RemoteLoginformDispatcher.java index 52976db2dedd7948acc8bd26557973045b9c9833..340ca28cabb3fa348125cbafc96d8a915378bfa8 100644 --- a/src/main/java/org/olat/dispatcher/RemoteLoginformDispatcher.java +++ b/src/main/java/org/olat/dispatcher/RemoteLoginformDispatcher.java @@ -26,6 +26,7 @@ import java.net.URLDecoder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.AuthHelper; import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.dispatcher.Dispatcher; @@ -135,6 +136,7 @@ public class RemoteLoginformDispatcher implements Dispatcher { int loginStatus = AuthHelper.doLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq); if (loginStatus == AuthHelper.LOGIN_OK) { // redirect to authenticated environment + UserDeletionManager.getInstance().setIdentityAsActiv(identity); final String origUri = request.getRequestURI(); String restPart = origUri.substring(uriPrefix.length()); diff --git a/src/main/java/org/olat/group/BusinessGroupManagerImpl.java b/src/main/java/org/olat/group/BusinessGroupManagerImpl.java index 49e8e8f40507cceaa82dd10321873ea88555f684..00dcb3319eb32291b62fd465ec2a7caae8addfec 100644 --- a/src/main/java/org/olat/group/BusinessGroupManagerImpl.java +++ b/src/main/java/org/olat/group/BusinessGroupManagerImpl.java @@ -1179,7 +1179,11 @@ public class BusinessGroupManagerImpl extends BasicManager implements BusinessGr if (calendarAccess != null) { newGroup.calendarAccess = calendarAccess; } - + //fxdiff VCRP-8: collaboration tools folder access control + Long folderAccess = ct.lookupFolderAccess(); + if(folderAccess != null) { + newGroup.folderAccess = folderAccess; + } String info = ct.lookupNews(); if (info != null && !info.trim().equals("")) { newGroup.info = info.trim(); @@ -1295,9 +1299,9 @@ public class BusinessGroupManagerImpl extends BasicManager implements BusinessGr ct.saveCalendarAccess(calendarAccess); } //fxdiff VCRP-8: collaboration tools folder access control - //TODO SR if(group.folderAccess != null) { - //TODO SR ct.saveFolderAccess(group.folderAccess); - //TODO SR } + if(group.folderAccess != null) { + ct.saveFolderAccess(group.folderAccess); + } if (group.info != null) { ct.saveNews(group.info); } diff --git a/src/main/java/org/olat/group/GroupXStream.java b/src/main/java/org/olat/group/GroupXStream.java index 1c94d3abb802dfa78d55244f3d39e4483789cd56..c6b880146444cca4a376ca7c54a365283e94b0fd 100644 --- a/src/main/java/org/olat/group/GroupXStream.java +++ b/src/main/java/org/olat/group/GroupXStream.java @@ -53,6 +53,7 @@ public class GroupXStream { xstream.aliasAttribute(Group.class, "showWaitingList", "showWaitingList"); xstream.aliasAttribute(Group.class, "description", "description"); xstream.aliasAttribute(Group.class, "info", "info"); + xstream.aliasAttribute(Group.class, "folderAccess", "folderAccess"); //CollabTools xstream.aliasAttribute(Group.class, "tools", "CollabTools"); @@ -147,6 +148,7 @@ class Group { public List<String> areaRelations; public Long calendarAccess; public String info; + public Long folderAccess; } class CollabTools { diff --git a/src/main/java/org/olat/group/GroupfoldersWebDAVProvider.java b/src/main/java/org/olat/group/GroupfoldersWebDAVProvider.java index d95f8b86b43b984358ba567e5919cf028cbe75d4..5800077fcfa68e7561a0c27544cc18052c1af6a1 100644 --- a/src/main/java/org/olat/group/GroupfoldersWebDAVProvider.java +++ b/src/main/java/org/olat/group/GroupfoldersWebDAVProvider.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; import org.olat.admin.quota.QuotaConstants; +import org.olat.basesecurity.BaseSecurityManager; import org.olat.collaboration.CollaborationTools; import org.olat.collaboration.CollaborationToolsFactory; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; @@ -38,6 +39,8 @@ 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.callbacks.FullAccessWithQuotaCallback; +import org.olat.core.util.vfs.callbacks.ReadOnlyCallback; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; /** * * Description:<br> @@ -84,8 +87,28 @@ public class GroupfoldersWebDAVProvider implements WebDAVProvider { q = QuotaManager.getInstance().createQuota(tools.getFolderRelPath(), defQuota.getQuotaKB(), defQuota.getUlLimitKB()); } - SubscriptionContext sc = new SubscriptionContext(group, "toolfolder"); - FullAccessWithQuotaCallback secCallback = new FullAccessWithQuotaCallback(q, sc); + //fxdiff VCRP-8: collaboration tools folder access control + boolean writeAccess; + boolean isOwner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(identity, group.getOwnerGroup()); + if (!isOwner) { + // check if participants have read/write access + int folderAccess = CollaborationTools.FOLDER_ACCESS_ALL; + Long lFolderAccess = tools.lookupFolderAccess(); + if (lFolderAccess != null) { + folderAccess = lFolderAccess.intValue(); + } + writeAccess = (folderAccess == CollaborationTools.CALENDAR_ACCESS_ALL); + } else { + writeAccess = true; + } + + VFSSecurityCallback secCallback; + if(writeAccess) { + SubscriptionContext sc = new SubscriptionContext(group, "toolfolder"); + secCallback = new FullAccessWithQuotaCallback(q, sc); + } else { + secCallback = new ReadOnlyCallback(); + } grpContainer.setLocalSecurityCallback(secCallback); // add container diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java index 58d34c01831ae721ff681c9b8d79964cbe347a45..87a1da51b3dee41e6e238c10cf2f6d634a897fde 100644 --- a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java +++ b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java @@ -726,8 +726,8 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im // calculate the new businesscontext for the forum clicked ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(ORES_TOOLFOLDER); bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, bwControl); - - collabToolCtr = collabTools.createFolderController(ureq, bwControl, sc); + //fxdiff VCRP-8: collaboration tools folder access control + collabToolCtr = collabTools.createFolderController(ureq, bwControl, businessGroup, isAdmin, sc); listenTo(collabToolCtr); mainPanel.setContent(collabToolCtr.getInitialComponent()); } else if (ACTIVITY_MENUSELECT_MEMBERSLIST.equals(cmd)) { diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupSendToChooserForm.java b/src/main/java/org/olat/group/ui/run/BusinessGroupSendToChooserForm.java index 87947ae4afc9a29c62ee89b9bd1a00c74f102766..4d5479ce7c6acb34b900fef65a3fe064d2d63ac6 100644 --- a/src/main/java/org/olat/group/ui/run/BusinessGroupSendToChooserForm.java +++ b/src/main/java/org/olat/group/ui/run/BusinessGroupSendToChooserForm.java @@ -42,10 +42,11 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.id.Identity; import org.olat.core.id.User; -import org.olat.core.id.UserConstants; import org.olat.core.util.ArrayHelper; import org.olat.group.BusinessGroup; import org.olat.group.properties.BusinessGroupPropertyManager; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; /** * Description: <BR> @@ -124,10 +125,12 @@ public class BusinessGroupSendToChooserForm extends FormBasicController { }; // Owner MultiSelection - SecurityGroup owners = businessGroup.getOwnerGroup(); - keysOwner = getMemberKeys(ureq, owners); - valuesOwner = getMemberValues(ureq, owners); - ArrayHelper.sort(keysOwner, valuesOwner, false, true, false); + if (!isRightGroup()) { + SecurityGroup owners = businessGroup.getOwnerGroup(); + keysOwner = getMemberKeys(ureq, owners); + valuesOwner = getMemberValues(ureq, owners); + ArrayHelper.sort(keysOwner, valuesOwner, false, true, false); + } } else { radioKeysOwners = new String[]{ @@ -227,16 +230,22 @@ public class BusinessGroupSendToChooserForm extends FormBasicController { * @param ureq * @param securityGroup * @return + * fxdiff: FXOLAT-227 get properties and order by user-properties-context. */ private String[] getMemberValues(UserRequest ureq, SecurityGroup securityGroup) { String[] values = new String[0]; + List<UserPropertyHandler> propHandlers = UserManager.getInstance().getUserPropertyHandlersFor(this.getClass().getCanonicalName(), ureq.getUserSession().getRoles().isOLATAdmin()); List<Identity> membersList = BaseSecurityManager.getInstance().getIdentitiesOfSecurityGroup(securityGroup); values = new String[membersList.size()]; for (int i = 0; i < membersList.size(); i++) { - User currentUser = membersList.get(i).getUser(); - values[i] = currentUser.getProperty(UserConstants.FIRSTNAME, ureq.getLocale()) + " " - + currentUser.getProperty(UserConstants.LASTNAME, ureq.getLocale()) + " [" - + membersList.get(i).getName().toString() + "]"; + User currentUser = membersList.get(i).getUser(); + StringBuffer userInfo = new StringBuffer(); + for (UserPropertyHandler userProp : propHandlers) { + userInfo.append(userProp.getUserProperty(currentUser, getLocale())); + userInfo.append(" "); + } + userInfo.append("[").append(membersList.get(i).getName().toString()).append("]"); + values[i] = userInfo.toString(); } return values; } @@ -416,13 +425,14 @@ public class BusinessGroupSendToChooserForm extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("sendtochooser.form.header"); - - radioButtonOwner = uifactory.addRadiosVertical("radioButtonOwner", "sendtochooser.form.radio.owners", formLayout, radioKeysOwners, radioValuesOwners); - radioButtonOwner.select(NLS_RADIO_ALL, true); - radioButtonOwner.addActionListener(listener, FormEvent.ONCLICK); - if ( (keysOwner != null) && (valuesOwner != null) ) { - multiSelectionOwnerKeys = uifactory.addCheckboxesVertical("multiSelectionOwnerKeys", "", formLayout, keysOwner, valuesOwner, null, 1); - } + if (!isRightGroup()) { + radioButtonOwner = uifactory.addRadiosVertical("radioButtonOwner", "sendtochooser.form.radio.owners", formLayout, radioKeysOwners, radioValuesOwners); + radioButtonOwner.select(NLS_RADIO_ALL, true); + radioButtonOwner.addActionListener(listener, FormEvent.ONCLICK); + if ( (keysOwner != null) && (valuesOwner != null) ) { + multiSelectionOwnerKeys = uifactory.addCheckboxesVertical("multiSelectionOwnerKeys", "", formLayout, keysOwner, valuesOwner, null, 1); + } + } radioButtonPartips = uifactory.addRadiosVertical("radioButtonPartip", "sendtochooser.form.radio.rightgroup", formLayout, radioKeysPartips, radioValuesPartips); radioButtonPartips.select(NLS_RADIO_ALL, true); @@ -432,7 +442,7 @@ public class BusinessGroupSendToChooserForm extends FormBasicController { } radioButtonWaitings = uifactory.addRadiosVertical("radioButtonWaiting", "sendtochooser.form.radio.waitings", formLayout, radioKeysWaitings, radioValuesWaitings); - radioButtonWaitings.select(NLS_RADIO_ALL, true); + radioButtonWaitings.select(NLS_RADIO_NOTHING, true); radioButtonWaitings.addActionListener(listener, FormEvent.ONCLICK); if ( (keysWaitings != null) && (valuesWaitings != null) ) { multiSelectionWaitingKeys = uifactory.addCheckboxesVertical("multiSelectionWaitingKeys", "", formLayout, keysWaitings, valuesWaitings, null, 1); @@ -451,6 +461,20 @@ public class BusinessGroupSendToChooserForm extends FormBasicController { @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + + if (source == radioButtonOwner && radioButtonOwner.getSelectedKey() != NLS_RADIO_NOTHING) { + radioButtonWaitings.select(NLS_RADIO_NOTHING, true); + } + if (source == radioButtonPartips && radioButtonPartips.getSelectedKey() != NLS_RADIO_NOTHING) { + radioButtonWaitings.select(NLS_RADIO_NOTHING, true); + } + if (source == radioButtonWaitings && radioButtonWaitings.getSelectedKey() != NLS_RADIO_NOTHING) { + radioButtonOwner.select(NLS_RADIO_NOTHING, true); + radioButtonPartips.select(NLS_RADIO_NOTHING, true); + } + + errorKeyDisplay.clearError(); + update(); } diff --git a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties index 305453e1fd5fb58c4b409c66fcc29285d98ebef8..c294eb3cbee224126c31e7cb7e7e00c87279d06f 100644 --- a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_de.properties @@ -30,6 +30,8 @@ menutree.wiki=Wiki menutree.wiki.alt=Kollaboratives Content Management System menutree.portfolio=Portfolio menutree.portfolio.alt=Kollaborative Portfoliomappe +menutree.ac=Buchungen +menutree.ac.alt=Buchungen resources.intro=Zu dieser Gruppe geh\u00F6rt der untenstehende Kurs. resources.launch=Starten resources.noresources=Es wurde kein Kurs f\u00FCr diese Gruppe gefunden. diff --git a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties index 5ee3eaa3d7971327ca8d3b82e149ae4a062cbefc..0b3a0125ad141a7ff48bb864ad5b5ee8197eb57d 100644 --- a/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/group/ui/run/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Wed Jan 05 17:31:54 CET 2011 +#Thu May 26 09:55:51 CEST 2011 businessgroup.contact.bodytext=\n\n---\nGo immediately to group {0} \: "{1}" businessgroup.contact.subject={0} \: grouprun.configurationchanged=This group's configuration has been altered. The group has been restarted. @@ -7,6 +7,8 @@ grouprun.details.name=Group name grouprun.details.title=Information grouprun.disabled=Currently all collaborative elements in OLAT are locked (e.g. because you are editing a test). Cancel or complete first in order to be able to use collaborative tools. grouprun.removedfromgroup=This group's configuration has been modified (group deleted, members changed). Please close the tab. +menutree.ac=Bookings +menutree.ac.alt=Bookings menutree.administration=Administration menutree.administration.alt=Administration menutree.calendar=Calendar diff --git a/src/main/java/org/olat/home/controllerCreators/PersonalFolderControllerCreator.java b/src/main/java/org/olat/home/controllerCreators/PersonalFolderControllerCreator.java index e84e09ab0929b7208308ebfad0cfbcc8dda38814..f67e9ca648a6ebd86fa9f41667473b2b12c59be4 100644 --- a/src/main/java/org/olat/home/controllerCreators/PersonalFolderControllerCreator.java +++ b/src/main/java/org/olat/home/controllerCreators/PersonalFolderControllerCreator.java @@ -55,6 +55,6 @@ public class PersonalFolderControllerCreator extends AutoCreator { */ @Override public Controller createController(UserRequest ureq, WindowControl lwControl) { - return new FolderRunController(PersonalFolderManager.getInstance().getContainer(ureq.getIdentity()), true, true, ureq, lwControl); + return new FolderRunController(PersonalFolderManager.getInstance().getContainer(ureq.getIdentity()), true, true, true, ureq, lwControl); } } diff --git a/src/main/java/org/olat/ims/cp/ui/CPContentController.java b/src/main/java/org/olat/ims/cp/ui/CPContentController.java index 3c9c4e0f76e0270e7fafd75c16691d3ff249d063..704e40a196a8051fc44c5d47f9ec9e27ee07eb69 100644 --- a/src/main/java/org/olat/ims/cp/ui/CPContentController.java +++ b/src/main/java/org/olat/ims/cp/ui/CPContentController.java @@ -24,6 +24,7 @@ package org.olat.ims.cp.ui; import org.olat.core.commons.editor.htmleditor.HTMLEditorController; import org.olat.core.commons.editor.htmleditor.WysiwygFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsPreviewController; +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -36,6 +37,8 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.iframe.IFrameDisplayController; import org.olat.core.gui.control.generic.iframe.NewIframeUriEvent; +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.ims.cp.CPManager; @@ -123,7 +126,12 @@ public class CPContentController extends BasicController { private void setContent(UserRequest ureq, String filePath) { if (filePath.toLowerCase().lastIndexOf(FILE_SUFFIX_HTM) >= (filePath.length() - 4)) { if (mceCtr != null) mceCtr.dispose(); - mceCtr = WysiwygFactory.createWysiwygController(ureq, getWindowControl(), currentPage.getRootDir(), filePath, false); + //fxdiff FXOLAT-125: virtual file system for CP + VFSContainer rootDir = currentPage.getRootDir(); + String virtualRootFolderName = translate("cpfileuploadcontroller.virtual.root"); + VFSContainer pseudoContainer = new VFSRootCPContainer(virtualRootFolderName, cp, rootDir, getTranslator()); + + mceCtr = WysiwygFactory.createWysiwygController(ureq, getWindowControl(), pseudoContainer, filePath, false); mceCtr.setCancelButtonEnabled(false); mceCtr.setSaveCloseButtonEnabled(false); mceCtr.setShowMetadataEnabled(false); diff --git a/src/main/java/org/olat/ims/cp/ui/CPTreeController.java b/src/main/java/org/olat/ims/cp/ui/CPTreeController.java index dd537904d6068042611aa73e6647d690d26a841d..85e48ef3de33a4ca584e44f28093debf86940ef2 100644 --- a/src/main/java/org/olat/ims/cp/ui/CPTreeController.java +++ b/src/main/java/org/olat/ims/cp/ui/CPTreeController.java @@ -96,6 +96,8 @@ public class CPTreeController extends BasicController { setLinks(); contentVC.put("cptreecontroller.tree", treeCtr.getInitialComponent()); + //fxdiff FXOLAT-132: alert unsaved changes in HTML editor + contentVC.contextPut("treeId", Long.toString(treeCtr.getTreePanelID())); putInitialPanel(contentVC); } diff --git a/src/main/java/org/olat/ims/cp/ui/VFSCPContainer.java b/src/main/java/org/olat/ims/cp/ui/VFSCPContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..8632c7b213265b6da810d159592097d609dfcaf3 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSCPContainer.java @@ -0,0 +1,158 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONException; +import org.olat.core.gui.control.generic.ajax.tree.AjaxTreeNode; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Encoder; +import org.olat.core.util.vfs.AbstractVirtualContainer; +import org.olat.core.util.vfs.VFSConstants; +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.VFSStatus; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +import org.olat.core.util.vfs.filters.VFSItemFilter; +import org.olat.ims.cp.CPManager; +import org.olat.ims.cp.CPManagerImpl; +import org.olat.ims.cp.CPTreeDataModel; +import org.olat.ims.cp.ContentPackage; + +/** + * + * Description:<br> + * Map the content (pages) of the CP to simulate the VFS + * + * <P> + * Initial Date: 4 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSCPContainer extends AbstractVirtualContainer implements VFSContainer { + + private static final OLog log = Tracing.createLoggerFor(VFSCPContainer.class); + + private VFSSecurityCallback secCallback; + private final CPTreeDataModel treeModel; + private final CPManagerImpl cpMgm; + private final ContentPackage cp; + private String rootNodeId; + + public VFSCPContainer(String name, ContentPackage cp) { + super(name); + + String orgaIdentifier = cp.getFirstOrganizationInManifest().getIdentifier(); + rootNodeId = Encoder.encrypt(orgaIdentifier); + + this.cp = cp; + cpMgm = (CPManagerImpl) CPManager.getInstance(); + treeModel = cpMgm.getTreeDataModel(cp); + } + + @Override + public boolean isSame(VFSItem vfsItem) { + if(this == vfsItem) { + return true; + } + return false; + } + + @Override + public VFSItem resolve(String path) { + if(path == null || path.length() == 0 || path.equals("/")) { + return this; + } + return VFSManager.resolveFile(this, path); + } + + @Override + public List<VFSItem> getItems() { + return getItems(cp, treeModel, rootNodeId); + } + + protected static List<VFSItem> getItems(ContentPackage cp, CPTreeDataModel model, String nodeId) { + List<AjaxTreeNode> nodes = model.getChildrenFor(nodeId); + + CPManager cpMgm =CPManager.getInstance(); + + List<VFSItem> items = new ArrayList<VFSItem>(); + for(AjaxTreeNode node:nodes) { + try { + String nid = (String)node.get(AjaxTreeNode.CONF_ID); + String id = model.getIdentifierForNodeID(nid); + String filePath = cpMgm.getPageByItemId(cp, id); + String title = cpMgm.getItemTitle(cp, id); + VFSItem f = cp.getRootDir().resolve(filePath); + if(f instanceof VFSLeaf) { + title += " (" + filePath + ")"; + + VFSItem item; + List<AjaxTreeNode> children = model.getChildrenFor(nid); + if(children.isEmpty()) { + item = new VFSCPNamedItem(title, (VFSLeaf)f); + } else { + item = new VFSCPNamedContainerItem(nid, title, (VFSLeaf)f, cp, model); + } + items.add(item); + } + } catch (JSONException e) { + log.error("", e); + } + } + + return items; + } + + @Override + public List<VFSItem> getItems(VFSItemFilter filter) { + return getItems(); + } + + @Override + public VFSContainer getParentContainer() { + return null; + } + + @Override + public void setParentContainer(VFSContainer parentContainer) { + // + } + + @Override + public VFSStatus canWrite() { + return VFSConstants.NO; + } + + @Override + public VFSSecurityCallback getLocalSecurityCallback() { + return secCallback; + } + + @Override + public void setLocalSecurityCallback(VFSSecurityCallback secCallback) { + this.secCallback = secCallback; + } +} diff --git a/src/main/java/org/olat/ims/cp/ui/VFSCPNamedContainerItem.java b/src/main/java/org/olat/ims/cp/ui/VFSCPNamedContainerItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d32b1e36e1fb412b8904986b6787310bb9999797 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSCPNamedContainerItem.java @@ -0,0 +1,104 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import java.util.List; + +import org.olat.core.util.vfs.NamedLeaf; +import org.olat.core.util.vfs.VFSConstants; +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.VFSStatus; +import org.olat.core.util.vfs.filters.VFSItemFilter; +import org.olat.ims.cp.CPManager; +import org.olat.ims.cp.CPManagerImpl; +import org.olat.ims.cp.CPTreeDataModel; +import org.olat.ims.cp.ContentPackage; + + +/** + * + * Description:<br> + * This is an hybrid leaf - container as the CP allows a page to have children. + * + * <P> + * Initial Date: 5 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSCPNamedContainerItem extends NamedLeaf implements VFSContainer { + + private final CPTreeDataModel treeModel; + private final CPManagerImpl cpMgm; + private final ContentPackage cp; + private final String ident; + + public VFSCPNamedContainerItem(String ident, String name, VFSLeaf delegate, ContentPackage cp, CPTreeDataModel treeModel) { + super(name, delegate); + + this.cp = cp; + this.ident = ident; + cpMgm = (CPManagerImpl) CPManager.getInstance(); + this.treeModel = treeModel; + } + + @Override + public VFSItem resolve(String path) { + VFSItem resolved = VFSManager.resolveFile(this, path); + return resolved; + } + + @Override + public List<VFSItem> getItems() { + return VFSCPContainer.getItems(cp, treeModel, ident); + } + + @Override + public List<VFSItem> getItems(VFSItemFilter filter) { + return getItems(); + } + + @Override + public VFSStatus copyFrom(VFSItem source) { + return VFSConstants.NO; + } + + @Override + public VFSContainer createChildContainer(String name) { + return null; + } + + @Override + public VFSLeaf createChildLeaf(String name) { + return null; + } + + @Override + public VFSItemFilter getDefaultItemFilter() { + return null; + } + + @Override + public void setDefaultItemFilter(VFSItemFilter defaultFilter) { + // + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/cp/ui/VFSCPNamedItem.java b/src/main/java/org/olat/ims/cp/ui/VFSCPNamedItem.java new file mode 100644 index 0000000000000000000000000000000000000000..b0fb3d3547f0cfe4ad4e9de328f00052586d8519 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSCPNamedItem.java @@ -0,0 +1,49 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import org.olat.core.util.vfs.NamedLeaf; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; + +/** + * + * Description:<br> + * A specialization of the NamedLeaf with a custom resolve method + * + * <P> + * Initial Date: 5 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSCPNamedItem extends NamedLeaf { + + public VFSCPNamedItem(String name, VFSLeaf delegate) { + super(name, delegate); + } + + @Override + public VFSItem resolve(String path) { + if(path == null || path.length() == 0 || path.equals("/")) { + return this; + } + return delegate.resolve(path); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/cp/ui/VFSMediaFilesContainer.java b/src/main/java/org/olat/ims/cp/ui/VFSMediaFilesContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..2e816b74a09460708e0be1bfc1d15b72d895ccfd --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSMediaFilesContainer.java @@ -0,0 +1,108 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import java.util.List; + +import org.olat.core.util.vfs.AbstractVirtualContainer; +import org.olat.core.util.vfs.VFSConstants; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSStatus; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +import org.olat.core.util.vfs.filters.VFSItemFilter; + +/** + * + * Description:<br> + * This container has special isSame implementation + * + * <P> + * Initial Date: 4 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSMediaFilesContainer extends AbstractVirtualContainer implements VFSContainer { + + private final VFSContainer rootContainer; + private VFSSecurityCallback secCallback; + + + public VFSMediaFilesContainer(String name, VFSContainer rootContainer) { + super(name); + this.rootContainer = rootContainer; + this.rootContainer.setParentContainer(null); + } + + @Override + public boolean isSame(VFSItem vfsItem) { + return this == vfsItem || rootContainer.isSame(vfsItem); + } + + @Override + public VFSItem resolve(String path) { + return rootContainer.resolve(path); + } + + @Override + public List<VFSItem> getItems() { + return rootContainer.getItems(); + } + + @Override + public List<VFSItem> getItems(VFSItemFilter filter) { + return rootContainer.getItems(filter); + } + + @Override + public VFSContainer getParentContainer() { + return null; + } + + @Override + public void setParentContainer(VFSContainer parentContainer) { + // + } + + @Override + public VFSStatus canWrite() { + return VFSConstants.NO; + } + + @Override + public VFSItemFilter getDefaultItemFilter() { + return rootContainer.getDefaultItemFilter(); + } + + @Override + public void setDefaultItemFilter(VFSItemFilter defaultFilter) { + rootContainer.setDefaultItemFilter(defaultFilter); + } + + @Override + public VFSSecurityCallback getLocalSecurityCallback() { + return secCallback; + } + + @Override + public void setLocalSecurityCallback(VFSSecurityCallback secCallback) { + this.secCallback = secCallback; + } +} diff --git a/src/main/java/org/olat/ims/cp/ui/VFSMediaFilter.java b/src/main/java/org/olat/ims/cp/ui/VFSMediaFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..efe6ef52876c1ce45e1c1ac87e9fd0989b85d0a6 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSMediaFilter.java @@ -0,0 +1,67 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import java.util.HashSet; +import java.util.Set; + +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.filters.VFSItemFilter; + +/** + * Description:<br> + * The filter exclude system files (start with .) and the CP specific standard file, + * manifests and XSD. + * + * <P> + * Initial Date: 4 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSMediaFilter implements VFSItemFilter { + + private static final Set<String> blackLists = new HashSet<String>(); + + static { + blackLists.add("__macosx"); + blackLists.add("imscp_v1p1.xsd"); + blackLists.add("imsmanifest.xml"); + blackLists.add("imsmd_v1p2p2.xsd"); + blackLists.add("ims_xml.xsd"); + } + + private boolean filterHtml; + + public VFSMediaFilter(boolean filterHtml) { + this.filterHtml = filterHtml; + } + + @Override + public boolean accept(VFSItem vfsItem) { + String name = vfsItem.getName().toLowerCase(); + if(name.startsWith(".") || blackLists.contains(name)) { + return false; + } + if(filterHtml && (name.endsWith(".html") || name.endsWith(".htm") || name.endsWith(".xhtml"))) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/olat/ims/cp/ui/VFSRootCPContainer.java b/src/main/java/org/olat/ims/cp/ui/VFSRootCPContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..60f80d0e6383f1dd553494a65e10df43494eb2f9 --- /dev/null +++ b/src/main/java/org/olat/ims/cp/ui/VFSRootCPContainer.java @@ -0,0 +1,160 @@ +/** +* 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> +* frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.ims.cp.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.vfs.AbstractVirtualContainer; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.VFSConstants; +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.VFSStatus; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +import org.olat.core.util.vfs.filters.VFSItemFilter; +import org.olat.ims.cp.ContentPackage; + +/** + * + * Description:<br> + * this the root of the VFS for the CP with three + * containers as children: CP Pages, media (files - html...) and raw files + * + * <P> + * Initial Date: 4 mai 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-125: virtual file system for CP +public class VFSRootCPContainer extends AbstractVirtualContainer implements VFSContainer { + + private VFSContainer rootContainer; + + private VFSSecurityCallback secCallback; + + private final List<VFSItem> roots = new ArrayList<VFSItem>(); + + public VFSRootCPContainer(String name, ContentPackage cp, VFSContainer rootContainer, Translator translator) { + super(name); + this.rootContainer = rootContainer; + + String contentTitle = translator.translate("cpfileuploadcontroller.pages"); + VFSCPContainer cpContainer = new VFSCPContainer(contentTitle, cp); + roots.add(cpContainer); + + String mediaTitle = translator.translate("cpfileuploadcontroller.media"); + VFSContainer mediaContainer = new VFSMediaFilesContainer(mediaTitle, cloneContainer(rootContainer)); + mediaContainer.setDefaultItemFilter(new VFSMediaFilter(true)); + roots.add(mediaContainer); + + String rawTitle = translator.translate("cpfileuploadcontroller.raw"); + VFSContainer rawContainer = new VFSMediaFilesContainer(rawTitle, cloneContainer(rootContainer)); + rawContainer.setDefaultItemFilter(new VFSMediaFilter(false)); + roots.add(rawContainer); + } + + private VFSContainer cloneContainer(VFSContainer container) { + if(container instanceof OlatRootFolderImpl) { + OlatRootFolderImpl folder = (OlatRootFolderImpl)container; + return new OlatRootFolderImpl(folder.getRelPath(), folder.getParentContainer()); + } else if(container instanceof LocalFolderImpl) { + LocalFolderImpl folder = (LocalFolderImpl)container; + LocalFolderImpl clone = new LocalFolderImpl(folder.getBasefile()); + clone.setParentContainer(folder.getParentContainer()); + return clone; + } + return null; + } + + @Override + public boolean isSame(VFSItem vfsItem) { + if(this == vfsItem) { + return true; + } + for(VFSItem root:roots) { + if(root.isSame(vfsItem)) { + return true; + } + } + return false; + } + + @Override + public VFSItem resolve(String path) { + for(VFSItem root:roots) { + if(root instanceof VFSContainer) { + VFSContainer container = (VFSContainer)root; + VFSItem item = container.resolve(path); + if(item != null) { + return item; + } + } + } + return null; + } + + @Override + public List<VFSItem> getItems() { + return roots; + } + + @Override + public List<VFSItem> getItems(VFSItemFilter filter) { + return getItems(); + } + + @Override + public VFSContainer createChildContainer(String name) { + return rootContainer.createChildContainer(name); + } + + @Override + public VFSLeaf createChildLeaf(String name) { + return rootContainer.createChildLeaf(name); + } + + @Override + public VFSContainer getParentContainer() { + return null; + } + + @Override + public void setParentContainer(VFSContainer parentContainer) { + // + } + + @Override + public VFSStatus canWrite() { + return VFSConstants.YES; + } + + @Override + public VFSSecurityCallback getLocalSecurityCallback() { + return secCallback; + } + + @Override + public void setLocalSecurityCallback(VFSSecurityCallback secCallback) { + this.secCallback = secCallback; + } +} diff --git a/src/main/java/org/olat/ims/cp/ui/_content/treeView.html b/src/main/java/org/olat/ims/cp/ui/_content/treeView.html index 3df2761322c1b8fadf709ed36019629590448ed9..6719a13b80a65e4fe987f7c9e655964ee801f6d4 100644 --- a/src/main/java/org/olat/ims/cp/ui/_content/treeView.html +++ b/src/main/java/org/olat/ims/cp/ui/_content/treeView.html @@ -9,4 +9,20 @@ <div class="o_cpeditor_menu_tree"> $r.render("cptreecontroller.tree") </div> + +<script type="text/javascript"> +/* <![CDATA[ */ + if(o_info.objectMap.containsKey('o_streePanel$treeId')) { + var tree = o_info.objectMap.get('o_streePanel$treeId'); + tree.on('beforeclick', function() { + if(o_info.linkbusy) return; + if(o2c == 0 || confirm(o_info.dirty_form)) { + return; + } else { + return false; + } + }); + }; +/* ]]> */ +</script> </div> diff --git a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_de.properties index 9b50a70f5749da9ee54313458f9195eda8701c04..61f746a2c54fc4b7a350494d28914a693f7a2218 100644 --- a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_de.properties @@ -40,6 +40,10 @@ cpfileuploadcontroller.form.description=Laden Sie Dateien hoch und importieren S cpfileuploadcontroller.form.file.types=Wählen Sie die Dateitypen aus, die dem Menübaum hinzugefügt werden sollen. cpfileuploadcontroller.form.all.types=Alle Dateien (inkl. Bilder etc.) cpfileuploadcontroller.no.files.imported=Das angegebene ZIP-Archiv enthält keine Dateien der ausgewählten Typen. +cpfileuploadcontroller.virtual.root=Dateien +cpfileuploadcontroller.pages=Inhalt +cpfileuploadcontroller.media=Media +cpfileuploadcontroller.raw=Alle Dateien pagecontroller.title=Seite editieren saveandclose=Speichern und schliessen diff --git a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties index 0eccfac54fc8ce913e1a816edefd797036f9f33f..ca633c09e28f1f1f6b87b8645c627db8c982e05d 100644 --- a/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/cp/ui/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 20 16:34:35 CET 2011 +#Thu May 26 09:38:48 CEST 2011 FileResource.IMSCP=CP learning content chelp.cpeditorhelp.add=Select the page you want to add a sub-page to before clicking on the button "Add page." Your sub-page will be added right underneath the page selected, however, you can still move your sub-page. chelp.cpeditorhelp.add.title=Add page @@ -32,7 +32,11 @@ cpfileuploadcontroller.form.file.types=Select those file types that should be ad cpfileuploadcontroller.form.title=Import pages cpfileuploadcontroller.import.button=Import cpfileuploadcontroller.import.text=File +cpfileuploadcontroller.media=Media cpfileuploadcontroller.no.files.imported=The selected ZIP archive does not contain any files of this type. +cpfileuploadcontroller.pages=Content +cpfileuploadcontroller.raw=All files +cpfileuploadcontroller.virtual.root=Files cpmd.flexi.author=Author cpmd.flexi.date=Date cpmd.flexi.descr=Description @@ -57,7 +61,6 @@ cptreecontroller.q_delete_text=Do you really want to delete the following elemen cptreecontroller.q_delete_title=Delete? cptreecontrolller.delete.items.and.files=Delete menu element and files cptreecontrolller.delete.items.only=Delete menu element only - maincontroller.cp.created.with.third.party.editor=This Content Package was not created in OLAT. Some parts of it might not be fully supported. Therefore you might experience data loss when saving pages. maincontroller.loaderror=An error occurred while loading this manifest\!<br /><i>{0}</i> pagecontroller.title=Edit page diff --git a/src/main/java/org/olat/ims/qti/QTIResultTableModel.java b/src/main/java/org/olat/ims/qti/QTIResultTableModel.java index 3c2609c0a16ad814576012c03acf2d54853d29b6..d4007df480a1a949d50f70073ad3a99870c4434b 100644 --- a/src/main/java/org/olat/ims/qti/QTIResultTableModel.java +++ b/src/main/java/org/olat/ims/qti/QTIResultTableModel.java @@ -27,6 +27,7 @@ import java.util.List; import org.olat.core.gui.components.table.BaseTableDataModelWithoutFilter; import org.olat.core.gui.components.table.TableDataModel; import org.olat.core.util.Formatter; +import org.olat.course.assessment.AssessmentHelper; /** * Initial Date: 12.01.2005 @@ -74,7 +75,7 @@ public class QTIResultTableModel extends BaseTableDataModelWithoutFilter impleme return Formatter.formatDuration(resultSet.getDuration().longValue()); } } - case 2: return "" + resultSet.getScore(); + case 2: return "" + AssessmentHelper.getRoundedScore(resultSet.getScore()); default: return "error"; } } diff --git a/src/main/java/org/olat/instantMessaging/AdminUserConnection.java b/src/main/java/org/olat/instantMessaging/AdminUserConnection.java index d38cadd7e595cf9adce810b5d25504d83c9859f8..1702159199f118e89ec7ad1b1401da4b719740f2 100644 --- a/src/main/java/org/olat/instantMessaging/AdminUserConnection.java +++ b/src/main/java/org/olat/instantMessaging/AdminUserConnection.java @@ -25,9 +25,8 @@ import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.olat.core.logging.AssertException; -import org.olat.core.logging.OLog; +import org.olat.core.logging.LogDelegator; import org.olat.core.logging.StartupException; -import org.olat.core.logging.Tracing; /** * Description:<br> @@ -37,15 +36,10 @@ import org.olat.core.logging.Tracing; * Initial Date: 11.08.2008 <br> * @author guido */ -public class AdminUserConnection { - - final static OLog log = Tracing.createLoggerFor(AdminUserConnection.class); - +public class AdminUserConnection extends LogDelegator { + private XMPPConnection connection; - private String serverName; - private String adminUsername; - private String adminPassword; - private String nodeId; + private IMConfig imConfig; /** * [used by spring] @@ -54,25 +48,26 @@ public class AdminUserConnection { * @param adminPassword * @param nodeId */ - private AdminUserConnection(String serverName, String adminUsername, String adminPassword, String nodeId, boolean enabled) { - if (serverName == null || adminUsername == null || adminPassword == null) { + private AdminUserConnection(IMConfig imConfig) { + if (imConfig == null || imConfig.getAdminName() == null || imConfig.getAdminPassword() == null || imConfig.getServername() == null) { throw new StartupException("Instant Messaging settings for admin user are not correct"); } - this.serverName = serverName; - this.adminUsername = adminUsername; - this.adminPassword = adminPassword; - this.nodeId = nodeId; - if (enabled) { + this.imConfig = imConfig; + + if (imConfig.isEnabled()) { final int LOOP_CNT=5; for(int i=0; i<LOOP_CNT; i++) { try{ - connect(serverName, adminUsername, adminPassword, nodeId); + connect(imConfig.getServername(), imConfig.getAdminName(), imConfig.getAdminPassword(), imConfig.getNodeId()); break; // leave if connect works fine } catch(AssertException e) { if (i+1==LOOP_CNT) { - throw e; + // fxdiff: allow startup without IM + logError("Could not connect to IM within " + LOOP_CNT + " attempts. IM will not be available for this OLAT instance! You could try to enable it later by reconnect in IMAdmin.", e); + return; +// throw e; // dont throw anything, spring gets confused during startup! } - log.error("Could not connect to IM (yet?). Retrying in 2sec...", e); + logError("Could not connect to IM (yet?). Retrying in 2sec...", e); try { Thread.sleep(2000); } catch (InterruptedException e1) { @@ -89,7 +84,8 @@ public class AdminUserConnection { conConfig.setNotMatchingDomainCheckEnabled(false); conConfig.setSASLAuthenticationEnabled(false); //the admin reconnects automatically upon server restart or failure but *not* on a resource conflict and a manual close of the connection - conConfig.setReconnectionAllowed(true); + //fxdiff: FXOLAT-233 don't reconnect upon lost connection here, each sendPacket will try for its own. this reconnection never stops! + conConfig.setReconnectionAllowed(false); //conConfig.setDebuggerEnabled(true); if (connection == null || !connection.isAuthenticated()) { connection = new XMPPConnection(conConfig); @@ -97,35 +93,34 @@ public class AdminUserConnection { connection.addConnectionListener(new ConnectionListener() { public void connectionClosed() { - log.warn("IM admin connection closed!"); + logWarn("IM admin connection closed!", null); } public void connectionClosedOnError(Exception arg0) { - log.warn("IM admin connection closed on exception!", arg0); + logWarn("IM admin connection closed on exception!", arg0); } public void reconnectingIn(int arg0) { - log.warn("IM admin connection reconnectingIn "+arg0); + logWarn("IM admin connection reconnectingIn "+arg0, null); } public void reconnectionFailed(Exception arg0) { - log.warn("IM admin connection reconnection failed: ", arg0); + logWarn("IM admin connection reconnection failed: ", arg0); } public void reconnectionSuccessful() { - log.warn("IM admin connection reconnection successful"); + logWarn("IM admin connection reconnection successful", null); } }); if (nodeId != null) { connection.login(adminUsername, adminPassword, "node"+nodeId); - log.info("connected to IM at "+servername+" with user "+adminUsername+" and nodeId "+nodeId); + logInfo("connected to IM at "+servername+" with user "+adminUsername+" and nodeId "+nodeId); } else { connection.login(adminUsername, adminPassword); - log.info("connected to IM at "+servername+" with user "+adminUsername); + logInfo("connected to IM at "+servername+" with user "+adminUsername); } } - //TODO:gs why does auto reconnect not work } catch (XMPPException e) { throw new AssertException("Instant Messaging is enabled in olat.properties but instant messaging server not running: " + e.getMessage()); @@ -145,6 +140,7 @@ public class AdminUserConnection { * @return a valid connection to the IM server for the admin user */ public XMPPConnection getConnection() { + if (!precheckConnection()) return null; return connection; } @@ -160,7 +156,21 @@ public class AdminUserConnection { public void resetAndReconnect() { if (connection != null) { if (connection.isConnected()) connection.disconnect(); - connect(this.serverName, this.adminUsername, this.adminPassword, this.nodeId); + connect(imConfig.getServername(), imConfig.getAdminName(), imConfig.getAdminPassword(), imConfig.getNodeId()); + } + } + + //fxdiff: FXOLAT-233 precheck if connection is still available, or try once to reconnect. + private boolean precheckConnection(){ + if (connection != null && connection.isConnected()) return true; + else { + try { + resetAndReconnect(); + } catch (Exception e) { + logWarn("the lost IM connection could not be recovered", null); + return false; + } + return (connection != null && connection.isConnected()); } } diff --git a/src/main/java/org/olat/instantMessaging/ClientManager.java b/src/main/java/org/olat/instantMessaging/ClientManager.java index 632c530b29423bac5cb46a9b1adba05c0ff6581e..0c89b346471f8a771c07f33c7449cb919c8cc269 100644 --- a/src/main/java/org/olat/instantMessaging/ClientManager.java +++ b/src/main/java/org/olat/instantMessaging/ClientManager.java @@ -90,4 +90,7 @@ public interface ClientManager { */ public String getInstantMessagingCredentialsForUser(String username); + //fxdiff: FXOLAT-219 decrease the load for synching groups + public void checkInstantMessagingCredentialsForUser(Long identityKey); + } \ No newline at end of file diff --git a/src/main/java/org/olat/instantMessaging/ClientManagerImpl.java b/src/main/java/org/olat/instantMessaging/ClientManagerImpl.java index 7838fab32780552a594d57e3c4c21d931bc9ff01..d3ddd2e6ba88c591bb9fa83eb372a097dc21b272 100644 --- a/src/main/java/org/olat/instantMessaging/ClientManagerImpl.java +++ b/src/main/java/org/olat/instantMessaging/ClientManagerImpl.java @@ -41,7 +41,6 @@ import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.gui.control.Controller; import org.olat.core.id.Identity; import org.olat.core.id.UserConstants; -import org.olat.core.logging.Tracing; import org.olat.core.manager.BasicManager; import org.olat.core.util.event.GenericEventListener; @@ -214,50 +213,67 @@ public class ClientManagerImpl extends BasicManager implements ClientManager { // OLAT-3556: masking this error temporarily //@TODO //@FIXME - Tracing.logWarn("Identity not found for username="+username, ClientManagerImpl.class); + logWarn("Identity not found for username="+username, null); return null; } + return getInstantMessagingCredentialsForUser(identity); + } + + //fxdiff: FXOLAT-219 decrease the load for synching groups + public String getInstantMessagingCredentialsForUser(Identity identity) { + if(identity == null) return null; // synchronized: not needed here, since the credentials are only created once for a user (when that user logs in for the very first time). // And a user will almost! never log in at the very same time from two different machines. Authentication auth = BaseSecurityManager.getInstance().findAuthentication(identity, PROVIDER_INSTANT_MESSAGING); - InstantMessaging im = InstantMessagingModule.getAdapter(); + if (auth == null) { // create new authentication for provider and also a new IM-account - - //if account exists on IM server but not on OLAT delete it first - if (im.hasAccount(username)) { - im.deleteAccount(username); - } - - String pw = RandomStringUtils.randomAlphanumeric(6); - if (im.createAccount(username, pw, - getFullname(identity), identity.getUser().getProperty(UserConstants.EMAIL, null))) { - auth = BaseSecurityManager.getInstance().createAndPersistAuthentication(identity, PROVIDER_INSTANT_MESSAGING, - identity.getName().toLowerCase(), pw); - Tracing.logAudit("New instant messaging authentication account created for user:" + username, this.getClass()); - return auth.getCredential(); - } else { - Tracing.logWarn("new instant messaging account creation failed for user: " + username, ClientManagerImpl.class); - return null; - } + auth = createIMAuthentication(identity); } /** * this does not decouple IM from the loginprocess, move account recreations in background thread somewhere else * maybe to the login background thread... */ -// else { -// //user has IM account credentials on OLAT, check whether account on IM server side exists -// if (!im.hasAccount(username)) { -// boolean success = im.createAccount(username, auth.getCredential(), getFullname(identity), identity.getUser().getProperty(UserConstants.EMAIL, null)); -// if (success) { -// Tracing.logAudit("New instant messaging authentication account created for user:" + username, this.getClass()); -// } else { -// Tracing.logWarn("new instant messaging account creation failed for user: " + username, ClientManagerImpl.class); -// } -// } -// } - return auth.getCredential(); + //fxdiff: FXOLAT-233 + if (auth != null) return auth.getCredential(); + else return ""; } + @Override + //fxdiff: FXOLAT-219 decrease the load for synching groups + public void checkInstantMessagingCredentialsForUser(Long identityKey) { + if(identityKey == null) return; + boolean auth = BaseSecurityManager.getInstance().hasAuthentication(identityKey, PROVIDER_INSTANT_MESSAGING); + if (!auth) { // create new authentication for provider and also a new IM-account + Identity identity = BaseSecurityManager.getInstance().loadIdentityByKey(identityKey, false); + if(identity != null) { + createIMAuthentication(identity); + } + } + } + + //fxdiff: FXOLAT-219 decrease the load for synching groups + private Authentication createIMAuthentication(Identity identity) { + String username = identity.getName(); + InstantMessaging im = InstantMessagingModule.getAdapter(); + //if account exists on IM server but not on OLAT delete it first + if (im.hasAccount(username)) { + im.deleteAccount(username); + } + + String pw = RandomStringUtils.randomAlphanumeric(6); + if (im.createAccount(username, pw, + getFullname(identity), identity.getUser().getProperty(UserConstants.EMAIL, null))) { + Authentication auth = BaseSecurityManager.getInstance().createAndPersistAuthentication(identity, PROVIDER_INSTANT_MESSAGING, + identity.getName().toLowerCase(), pw); + logAudit("New instant messaging authentication account created for user:" + username, null); + return auth; + } else { + logWarn("new instant messaging account creation failed for user: " + username, null); + return null; + } + } + + /** * * @param identity diff --git a/src/main/java/org/olat/instantMessaging/IMConfig.java b/src/main/java/org/olat/instantMessaging/IMConfig.java index d726e3d583b10fb237ec5748952cf7053baaed1a..7636fc695759a1dcdd37ff7cd9ed92d26a13c7ce 100644 --- a/src/main/java/org/olat/instantMessaging/IMConfig.java +++ b/src/main/java/org/olat/instantMessaging/IMConfig.java @@ -45,6 +45,10 @@ public class IMConfig { private boolean syncPersonalGroups; private boolean syncLearningGroups; public static final String RESOURCE = "OLAT"; + // fxdiff: FXOLAT-46 + private boolean hideExternalClientInfo; + private String adminName; + private String nodeId; @@ -98,6 +102,22 @@ public class IMConfig { this.adminPassword = adminPassword; } + public void setAdminName(String adminName) { + this.adminName = adminName; + } + + public String getAdminName(){ + return adminName; + } + + public void setNodeId(String nodeId){ + this.nodeId = nodeId; + } + + public String getNodeId(){ + return nodeId; + } + public boolean generateTestUsers() { return generateTestUsers; } @@ -110,6 +130,7 @@ public class IMConfig { return CONFERENCE_PREFIX+"."+servername; } + // attention! always returns the default admin-name "admin" public String getAdminUsername() { return adminUsername; } @@ -145,5 +166,20 @@ public class IMConfig { public void setSyncLearningGroups(boolean syncLearningGroups) { this.syncLearningGroups = syncLearningGroups; } + + // fxdiff: FXOLAT-46 + /** + * @param hideExternalClientInfo The hideExternalClientInfo to set. + */ + public void setHideExternalClientInfo(boolean hideExternalClientInfo) { + this.hideExternalClientInfo = hideExternalClientInfo; + } + + /** + * @return Returns the hideExternalClientInfo. + */ + public boolean isHideExternalClientInfo() { + return hideExternalClientInfo; + } } diff --git a/src/main/java/org/olat/instantMessaging/IMNameHelper.java b/src/main/java/org/olat/instantMessaging/IMNameHelper.java index 893fc97ef94ece51bc5d27508f92cecc978a739f..802748e4e076d29a2cb521367c0ee2003fe47216 100644 --- a/src/main/java/org/olat/instantMessaging/IMNameHelper.java +++ b/src/main/java/org/olat/instantMessaging/IMNameHelper.java @@ -20,9 +20,13 @@ */ package org.olat.instantMessaging; -import org.olat.core.logging.OLog; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import org.olat.core.logging.LogDelegator; +import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.StartupException; -import org.olat.core.logging.Tracing; /** @@ -36,8 +40,7 @@ import org.olat.core.logging.Tracing; * @author Roman Haag, roman.haag@frentix.com * @author guido */ -public class IMNameHelper { - private static OLog log = Tracing.createLoggerFor(IMNameHelper.class); +public class IMNameHelper extends LogDelegator { private String instanceID; private String USERNAME_INSTANCE_DELIMITER = "_"; @@ -66,6 +69,15 @@ public class IMNameHelper { // replace "@" by "_at_" --> this allows "@" also for olat-usernames String imUsername = olatUsername.replace("@", config.getReplaceStringForEmailAt()); if (config.isMultipleInstances()) { + if (olatUsername.contains(USERNAME_INSTANCE_DELIMITER + instanceID)){ + // build stacktrace to find bad places in code: + OLATRuntimeException ore = new OLATRuntimeException("stacktrace for calls to IMNameHelper.getIMUsernameByOlatUsername()", null); + final Writer result = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(result); + ore.printStackTrace(printWriter); + logError("Double wrapping of olat-username! check the usage of it, as this IMUser (given olat-username: " + olatUsername + " imUsername: " + imUsername + " ) already contained the instanceID!! Calling Stack: " + result.toString(), null); + return olatUsername.toLowerCase(); + } return (imUsername + USERNAME_INSTANCE_DELIMITER + instanceID).toLowerCase(); } else { @@ -90,7 +102,7 @@ public class IMNameHelper { try { imUsername = imUsername.substring(0, imUsername.lastIndexOf(USERNAME_INSTANCE_DELIMITER)); } catch (StringIndexOutOfBoundsException e) { - log.error("Can not extract OLAT username from Jabber username::" + imUsername, e); + logError("Can not extract OLAT username from Jabber username::" + imUsername, e); return "?"; } } diff --git a/src/main/java/org/olat/instantMessaging/ImPrefsManager.java b/src/main/java/org/olat/instantMessaging/ImPrefsManager.java index 2892b665aeda1bf382c909dcb331b6b151bba39d..662192edc61e91de5e351cbf49e225e47c521222 100644 --- a/src/main/java/org/olat/instantMessaging/ImPrefsManager.java +++ b/src/main/java/org/olat/instantMessaging/ImPrefsManager.java @@ -48,6 +48,9 @@ public class ImPrefsManager extends BasicManager { private static ImPrefsManager INSTANCE; private final static String LOCK_KEY = ImPrefsManager.class.toString(); + + //fxdiff + private boolean awarenessVisible = false; private ImPrefsManager() { INSTANCE = this; @@ -62,6 +65,15 @@ public class ImPrefsManager extends BasicManager { return INSTANCE; } + //fxdiff + public boolean isAwarenessVisible() { + return awarenessVisible; + } + + public void setAwarenessVisible(boolean awarenessVisible) { + this.awarenessVisible = awarenessVisible; + } + /** * create new property for user with default IM prefs, or load an existing IM * prefs from the property store. @@ -82,7 +94,8 @@ public class ImPrefsManager extends BasicManager { imPrefs = new ImPreferences(identity); imPrefs.setVisibleToOthers(true); imPrefs.setOnlineTimeVisible(false); - imPrefs.setAwarenessVisible(false); + //fxdiff + imPrefs.setAwarenessVisible(isAwarenessVisible()); imPrefs.setRosterDefaultStatus(Presence.Mode.available.toString()); String props = XStreamHelper.toXML(imPrefs); Property imProperty = PropertyManager.getInstance().createPropertyInstance(identity, null, null, null, ImPreferences.USER_PROPERTY_KEY, null, null, null, props); diff --git a/src/main/java/org/olat/instantMessaging/InstantMessaging.java b/src/main/java/org/olat/instantMessaging/InstantMessaging.java index 07964179046effba2186eba283059d516bbe59e7..18045218e27007c5476eb3351418bb0308cfa56b 100644 --- a/src/main/java/org/olat/instantMessaging/InstantMessaging.java +++ b/src/main/java/org/olat/instantMessaging/InstantMessaging.java @@ -59,7 +59,8 @@ public interface InstantMessaging { * * @param group */ - public boolean synchonizeBuddyRoster(BusinessGroup group); + //fxdiff: FXOLAT-219 decrease to load generated by synching learning groups (method not used) + //public boolean synchonizeBuddyRoster(BusinessGroup group); /** * This method should only be called once as it creates the main controller and the groupchat controller for a single user @@ -242,6 +243,12 @@ public interface InstantMessaging { * but not on resource conflicts and manual connection close. */ public void resetAdminConnection(); + + /** + * use rarely, as this is normally linked by spring! + * @return AdminUserConnection the connection object + */ + public AdminUserConnection getAdminUserConnection(); /** * check wheter the server plugin is running and the correct version is @@ -249,4 +256,11 @@ public interface InstantMessaging { */ public String checkServerPlugin(); + /** + * can be used to synchronize all OLAT users with IM-server + * will also recreate the buddy-roaster + * it tries to find former invalid users, double wrapped (account_instanceID_instanceID) and delete them + */ + public String synchronizeAllOLATUsers(); + } \ No newline at end of file diff --git a/src/main/java/org/olat/instantMessaging/InstantMessagingJob.java b/src/main/java/org/olat/instantMessaging/InstantMessagingJob.java new file mode 100644 index 0000000000000000000000000000000000000000..4e3a12028b8f49910a16051e2f6bfa9c8734e8fb --- /dev/null +++ b/src/main/java/org/olat/instantMessaging/InstantMessagingJob.java @@ -0,0 +1,77 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.instantMessaging; + +import static org.olat.instantMessaging.InstantMessagingModule.CONFIG_SYNCED_LEARNING_GROUPS; +import static org.olat.instantMessaging.InstantMessagingModule.createPropertyName; + +import java.util.List; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.commons.scheduler.JobWithDB; +import org.olat.properties.Property; +import org.olat.properties.PropertyManager; +import org.quartz.JobExecutionContext; + +/** + * + * Description:<br> + * TODO: srosse Class Description for InstantMessagingJob + * + * <P> + * Initial Date: 14 juil. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.fentix.com + */ + +//fxdiff: FXOLAT-219 decrease the load for synching groups +public class InstantMessagingJob extends JobWithDB { + + @Override + public void executeWithDB(JobExecutionContext job) { + if(!InstantMessagingModule.isEnabled()) return; + + final InstantMessaging im = InstantMessagingModule.getAdapter(); + final PropertyManager propertyManager = PropertyManager.getInstance(); + final DB database = DBFactory.getInstance(); + + boolean success = false; + try { + List<Property> props = propertyManager.findProperties(null, null, null, "classConfig", createPropertyName(this.getClass(), CONFIG_SYNCED_LEARNING_GROUPS)); + if (props.size() == 0 || !Boolean.getBoolean(props.get(0).getStringValue())) { + if (InstantMessagingModule.isSyncLearningGroups()) { + long start = System.currentTimeMillis(); + log.info("Start synching learning groups with IM"); + boolean result = im.synchronizeLearningGroupsWithIMServer(); + Property property = propertyManager.createPropertyInstance(null, null, null, "classConfig", createPropertyName(this.getClass(), CONFIG_SYNCED_LEARNING_GROUPS), null, null, Boolean.toString(result), null); + propertyManager.saveProperty(property); + log.info("Synching learning groups with IM terminated in " + (System.currentTimeMillis() - start) + " (ms)"); + } + } + database.commitAndCloseSession(); + success = true; + } finally { + if (!success) { + database.rollbackAndCloseSession(); + } + } + } +} diff --git a/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java b/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java index 9be5b470969be35b8f7106c225e13d8cdf1bcc96..c0c8653e766fccd092c056771348632963828887 100644 --- a/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java +++ b/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java @@ -65,7 +65,8 @@ public class InstantMessagingModule implements Initializable, Destroyable, UserD private IMConfig config; private static boolean enabled = false; private static final String CONFIG_SYNCED_BUDDY_GROUPS = "issynced"; - private static final String CONFIG_SYNCED_LEARNING_GROUPS = "syncedlearninggroups"; + //fxdiff: FXOLAT-219 decrease the load for synching groups + public static final String CONFIG_SYNCED_LEARNING_GROUPS = "syncedlearninggroups"; OLog log = Tracing.createLoggerFor(this.getClass()); @Autowired private PropertyManager propertyManager; @@ -151,7 +152,9 @@ public class InstantMessagingModule implements Initializable, Destroyable, UserD * @param configurationName * @return String */ - private String createPropertyName(Class clazz, String configurationName) { + + //fxdiff: FXOLAT-219 decrease the load for synching groups + public static String createPropertyName(Class clazz, String configurationName) { return clazz.getName() + "::" + configurationName; } @@ -244,8 +247,7 @@ public class InstantMessagingModule implements Initializable, Destroyable, UserD */ public void deleteUserData(Identity identity, String newDeletedUserName) { if (instantMessaging.getConfig().isEnabled()) { - String imUsername = instantMessaging.getIMUsername(identity.getName()); - instantMessaging.deleteAccount(imUsername); + instantMessaging.deleteAccount(identity.getName()); log.debug("Deleted IM account for identity=" + identity); } } @@ -286,24 +288,6 @@ public class InstantMessagingModule implements Initializable, Destroyable, UserD @Override public void event(Event event) { // synchronistion of learning groups needs the whole olat course stuff loaded - if (event instanceof FrameworkStartedEvent) { - boolean success = false; - try { - List props = propertyManager.findProperties(null, null, null, "classConfig", createPropertyName(this.getClass(), CONFIG_SYNCED_LEARNING_GROUPS)); - if (props.size() == 0) { - if (isSyncLearningGroups()) { - instantMessaging.synchronizeLearningGroupsWithIMServer(); - Property property = propertyManager.createPropertyInstance(null, null, null, "classConfig", createPropertyName(this.getClass(), CONFIG_SYNCED_LEARNING_GROUPS), null, null, Boolean.toString(true), null); - propertyManager.saveProperty(property); - } - } - database.commitAndCloseSession(); - success = true; - } finally { - if (!success) { - database.rollbackAndCloseSession(); - } - } - } + //fxdiff: FXOLAT-219 decrease the load for synching groups } } diff --git a/src/main/java/org/olat/instantMessaging/OpenInstantMessageEvent.java b/src/main/java/org/olat/instantMessaging/OpenInstantMessageEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..2d471c433e09860e99b80dfcfdb8392ff840616a --- /dev/null +++ b/src/main/java/org/olat/instantMessaging/OpenInstantMessageEvent.java @@ -0,0 +1,54 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.instantMessaging; + +import org.olat.core.gui.UserRequest; +import org.olat.core.util.event.MultiUserEvent; + +/** + * + * Description:<br> + * Message to open a new message window. Only use this event with + * the Single VM message bus with the SingleUserEventCenter!!! + * + * <P> + * Initial Date: 2 mar. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class OpenInstantMessageEvent extends MultiUserEvent { + + private final String jabberId; + private final UserRequest ureq; + + public OpenInstantMessageEvent(String jabberId, UserRequest ureq) { + super("openim"); + this.jabberId = jabberId; + this.ureq = ureq; + } + + public String getJabberId() { + return jabberId; + } + + public UserRequest getUreq() { + return ureq; + } +} diff --git a/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java b/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java index 5f413769810a45dd9459f134033dbaff38110d95..0cf6529232a5061592bedc38b954cf8cd689e10d 100644 --- a/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java +++ b/src/main/java/org/olat/instantMessaging/SmackInstantMessagingImpl.java @@ -23,15 +23,17 @@ package org.olat.instantMessaging; import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.RejectedExecutionException; +import org.apache.commons.lang.RandomStringUtils; import org.jivesoftware.smack.packet.Presence; +import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.IdentityShort; import org.olat.basesecurity.SecurityGroup; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.taskExecutor.TaskExecutorManager; @@ -43,12 +45,11 @@ import org.olat.core.gui.control.creator.ControllerCreator; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.id.Roles; +import org.olat.core.id.UserConstants; import org.olat.core.logging.LogDelegator; -import org.olat.course.CourseFactory; import org.olat.course.CourseModule; -import org.olat.course.ICourse; -import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.group.BusinessGroup; +import org.olat.group.context.BGContext; import org.olat.group.context.BGContextManager; import org.olat.group.context.BGContextManagerImpl; import org.olat.instantMessaging.groupchat.GroupChatManagerController; @@ -61,6 +62,7 @@ import org.olat.instantMessaging.syncservice.RemoteAccountCreation; import org.olat.instantMessaging.ui.ConnectedUsersListEntry; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.olat.resource.OLATResource; /** * @@ -131,10 +133,10 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe // we have to make sure the user has an account on the instant messaging // server // by calling this it gets created if not yet exists. - addedUsername = nameHelper.getIMUsernameByOlatUsername(addedUsername); + String imUsername = nameHelper.getIMUsernameByOlatUsername(addedUsername); groupOwnerUsername = nameHelper.getIMUsernameByOlatUsername(groupOwnerUsername); - boolean hasAccount = accountService.hasAccount(addedUsername); + boolean hasAccount = accountService.hasAccount(imUsername); if (!hasAccount) clientManager.getInstantMessagingCredentialsForUser(addedUsername); // we do not check whether a group already exists, we create it each time List<String> list = new ArrayList<String>(); @@ -283,28 +285,33 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe } return sessionCount; } - - /** - * @see org.olat.instantMessaging.InstantMessaging#synchonizeBuddyRoster(org.olat.group.BusinessGroup) - */ - public boolean synchonizeBuddyRoster(BusinessGroup group) { + + //fxdiff: FXOLAT-219 decrease the load for synching groups + private boolean synchonizeBuddyRoster(BusinessGroup group, Set<Long> checkedIdentities) { BaseSecurity securityManager = BaseSecurityManager.getInstance(); - SecurityGroup owners = group.getOwnerGroup(); - SecurityGroup participants = group.getPartipiciantGroup(); - List<Identity> users = securityManager.getIdentitiesOfSecurityGroup(owners); - users.addAll(securityManager.getIdentitiesOfSecurityGroup(participants)); - + List<SecurityGroup> secGroups = new ArrayList<SecurityGroup>(); + secGroups.add(group.getOwnerGroup()); + secGroups.add(group.getPartipiciantGroup()); + List<IdentityShort> users = securityManager.getIdentitiesShortOfSecurityGroups(secGroups); + int counter = 0; List<String> usernames = new ArrayList<String>(); - for (Iterator<Identity> iter = users.iterator(); iter.hasNext();) { - Identity ident = iter.next(); + for (IdentityShort ident:users) { logDebug("getting im credentials for user::" + ident.getName()); // as jive only adds users to a group that already exist we have to make // sure they have an account. - clientManager.getInstantMessagingCredentialsForUser(ident.getName()); + if(checkedIdentities == null || !checkedIdentities.contains(ident.getKey())) { + clientManager.checkInstantMessagingCredentialsForUser(ident.getKey()); + if(checkedIdentities != null) { + checkedIdentities.add(ident.getKey()); + } + + if (counter % 25 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + counter++; + } usernames.add(nameHelper.getIMUsernameByOlatUsername(ident.getName())); - if (counter %6 == 0) DBFactory.getInstance().intermediateCommit(); - counter++; } String groupId = InstantMessagingModule.getAdapter().createChatRoomString(group); if (users.size() > 0 ) { // only sync groups with users @@ -324,58 +331,60 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe * * @see org.olat.instantMessaging.InstantMessaging#synchronizeLearningGroupsWithIMServer() */ + + //fxdiff: FXOLAT-219 decrease the load for synching groups public boolean synchronizeLearningGroupsWithIMServer() { + if (!(adminConnecion != null && adminConnecion.getConnection() != null && adminConnecion.getConnection().isConnected())) { + return false; + } logInfo("Starting synchronisation of LearningGroups with IM server"); + long start = System.currentTimeMillis(); + RepositoryManager rm = RepositoryManager.getInstance(); BGContextManager contextManager = BGContextManagerImpl.getInstance(); //pull as admin Roles roles = new Roles(true, true, true, true, false, true, false); List<RepositoryEntry> allCourses = rm.queryByTypeLimitAccess(null, CourseModule.getCourseTypeName(), roles); + boolean syncLearn = InstantMessagingModule.getAdapter().getConfig().isSyncLearningGroups(); + Set<Long> checkedIdentities = new HashSet<Long>(); + int counter = 0; - for (Iterator<RepositoryEntry> iterator = allCourses.iterator(); iterator.hasNext();) { - RepositoryEntry entry = iterator.next(); - ICourse course = null; - try{ - course = CourseFactory.loadCourse(entry.getOlatResource()); - } catch (Exception e) { - logError("Could not load Course! OlatResourcable: "+entry.getOlatResource(), e); - continue; - } + for (RepositoryEntry entry: allCourses) { + OLATResource courseResource = entry.getOlatResource(); + List<BGContext> contexts = contextManager.findBGContextsForResource(courseResource, BusinessGroup.TYPE_LEARNINGROUP, true, true); - CourseGroupManager groupManager = course.getCourseEnvironment().getCourseGroupManager(); - List<BusinessGroup> groups = groupManager.getAllLearningGroupsFromAllContexts(); - for (Iterator<BusinessGroup> iter = groups.iterator(); iter.hasNext();) { - BusinessGroup group = iter.next(); - - boolean syncLearn = InstantMessagingModule.getAdapter().getConfig().isSyncLearningGroups(); + List<BusinessGroup> groups = new ArrayList<BusinessGroup>(); + for (BGContext bgContext:contexts) { + groups.addAll(contextManager.getGroupsOfBGContext(bgContext)); + } + + for (BusinessGroup group:groups) { boolean isLearn = group.getType().equals(BusinessGroup.TYPE_LEARNINGROUP); - if (isLearn && !syncLearn) { String groupID = InstantMessagingModule.getAdapter().createChatRoomString(group); if (deleteRosterGroup(groupID)) { logInfo("deleted unwanted group: "+group.getResourceableTypeName()+" "+groupID, null); } - counter++; - if (counter%6==0) { - DBFactory.getInstance(false).intermediateCommit(); - } - continue; - } - - if (!synchonizeBuddyRoster(group)) { + } else if (!synchonizeBuddyRoster(group, checkedIdentities)) { logError("couldn't sync group: "+group.getResourceableTypeName(), null); } counter++; if (counter%6==0) { DBFactory.getInstance(false).intermediateCommit(); } - } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } if (counter%6==0) { DBFactory.getInstance(false).intermediateCommit(); } } - logInfo("Ended synchronisation of LearningGroups with IM server: Synched "+counter+" groups"); + logInfo("Ended synchronisation of LearningGroups with IM server: Synched "+counter+" groups in " + (System.currentTimeMillis() - start) + " (ms)"); return true; } @@ -387,21 +396,25 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe * @return true if successfull, false if IM server is not running */ public boolean synchronizeAllBuddyGroupsWithIMServer() { + if (adminConnecion != null && adminConnecion.getConnection() != null && adminConnecion.getConnection().isConnected()) { logInfo("Started synchronisation of BuddyGroups with IM server."); BGContextManager cm = BGContextManagerImpl.getInstance(); //null as argument pulls all buddygroups List<BusinessGroup> groups = cm.getGroupsOfBGContext(null); int counter = 0; - for (Iterator<BusinessGroup> iter = groups.iterator(); iter.hasNext();) { - BusinessGroup group = iter.next(); - synchonizeBuddyRoster(group); - counter++; - if (counter%6==0) { - DBFactory.getInstance(false).intermediateCommit(); + //fxdiff: FXOLAT-219 decrease the load for synching groups + Set<Long> checkedIdentites = new HashSet<Long>(); + for (BusinessGroup group: groups) { + if(synchonizeBuddyRoster(group, checkedIdentites)) { + counter++; } + //make an intermediate commit already } logInfo("Ended synchronisation of BuddyGroups with IM server: Synched "+counter+" groups"); return true; + } else { + return false; + } } /** @@ -519,6 +532,11 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe this.adminConnecion = adminConnection; } + // use rarely, as this is normally linked by spring! + public AdminUserConnection getAdminUserConnection(){ + return this.adminConnecion; + } + /** * * @see org.olat.instantMessaging.InstantMessaging#resetAdminConnection() @@ -560,5 +578,74 @@ public class SmackInstantMessagingImpl extends LogDelegator implements InstantMe void setSessionCount(int sessionCount) { this.sessionCount = sessionCount; } + + public String synchronizeAllOLATUsers(){ + StringBuilder sb = new StringBuilder(); + InstantMessaging im = InstantMessagingModule.getAdapter(); + logInfo("Started synchronisation of all OLAT users with IM server."); + List<Identity> allIdentities = BaseSecurityManager.getInstance().getVisibleIdentitiesByPowerSearch(null, null, false, null, null, null, null, null); + int identCount = 0; + int authCount = 0; + int imDelCount = 0; + int imCreateCount = 0; + for (Identity identity : allIdentities) { + if (BaseSecurityManager.getInstance().getRoles(identity).isGuestOnly()) continue; + String userName = identity.getName(); + boolean hasIMAccount = im.hasAccount(userName); + // try to detect double wrapped users on IM-Server + String imUserName = im.getIMUsername(userName); + int index = imUserName.indexOf("_"); + if (index >= 0){ // only for names in multipleInstance-mode + String instanceID = imUserName.substring(index); + String badIMName = (imUserName + instanceID).toLowerCase(); + if (accountService.hasAccount(badIMName)) { + logWarn("found an invalid IM account with double encoded username in multi instance mode. will remove this account in IM-server: " + badIMName, null); + sb.append("removed IM account with double instanceID:").append(badIMName); + accountService.deleteAccount(badIMName); + } + } + + //TODO: try to connect with auth-token, if not working, resync IM-PW + + Authentication auth = BaseSecurityManager.getInstance().findAuthentication(identity, ClientManager.PROVIDER_INSTANT_MESSAGING); + if (auth != null && !hasIMAccount) { + // there is an invalid auth token, but no IM-account, remove auth + BaseSecurityManager.getInstance().deleteAuthentication(auth); + DBFactory.getInstance().intermediateCommit(); + auth = null; + authCount++; + } + if (auth == null) { + if (hasIMAccount) { + // remove existing account, to later have auth and im-pw in sync! + im.deleteAccount(userName); + imDelCount++; + } + String pw = RandomStringUtils.randomAlphanumeric(6); + String fullName = identity.getUser().getProperty(UserConstants.FIRSTNAME, null) + " " + identity.getUser().getProperty(UserConstants.LASTNAME, null); + boolean success = im.createAccount(userName, pw, fullName, identity.getUser().getProperty(UserConstants.EMAIL, null)); + if (success) { + auth = BaseSecurityManager.getInstance().createAndPersistAuthentication(identity, ClientManager.PROVIDER_INSTANT_MESSAGING, userName.toLowerCase(), pw); + imCreateCount++; + } + } + + // TODO: sync users buddylist!? + + identCount ++; + if (identCount % 20 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + } + sb.append(" looped over ").append(identCount).append(" identities to sync."); + sb.append(" removed invalid authentication tokens: ").append(authCount); + sb.append(" deleted IM-accounts before recreation: ").append(imDelCount); + sb.append(" recreated IM-accounts and authentications: ").append(imCreateCount); + String status = sb.toString(); + logInfo(" Sync status: " + status); + logInfo("Ended synchronisation of all OLAT users."); + return status; + } + } \ No newline at end of file diff --git a/src/main/java/org/olat/instantMessaging/_spring/instantMessagingContext.xml b/src/main/java/org/olat/instantMessaging/_spring/instantMessagingContext.xml index e6003458b569de10961f1faa0a892d0627ff163c..9cbd453a44c745a66dca10b952cdce5e419aa8d3 100644 --- a/src/main/java/org/olat/instantMessaging/_spring/instantMessagingContext.xml +++ b/src/main/java/org/olat/instantMessaging/_spring/instantMessagingContext.xml @@ -83,20 +83,20 @@ <bean id="org.olat.im.AdminConnection" class="org.olat.instantMessaging.AdminUserConnection" destroy-method="dispose"> - <constructor-arg index="0" value="${instantMessaging.server.name}" /> - <constructor-arg index="1" value="${instantMessaging.admin.username}" /> - <constructor-arg index="2" value="${instantMessaging.admin.password}" /> - <constructor-arg index="3" value="${node.id}" /> - <constructor-arg index="4" value="${instantMessaging.enable}"/> + <constructor-arg ref="org.olat.im.IMConfig" /> </bean> <bean id="org.olat.im.IMConfig" class="org.olat.instantMessaging.IMConfig"> <property name="enabled" value="${instantMessaging.enable}" /> <property name="servername" value="${instantMessaging.server.name}" /> + <property name="nodeId" value="${node.id}" /> <property name="multipleInstances" value="${instantMessaging.multipleInstances}"/> <property name="generateTestUsers" value="${instantMessaging.generateTestUsers}" /> + <property name="adminName" value="${instantMessaging.admin.username}" /> <property name="adminPassword" value="${instantMessaging.admin.password}" /> <property name="replaceStringForEmailAt" value="${instantMessaging.replaceStringForEmailAt}" /> + <!-- fxdiff: FXOLAT-46 --> + <property name="hideExternalClientInfo" value="${instantMessaging.hideExternalClientInfo}" /> <!-- adjust for you needs--> @@ -116,5 +116,23 @@ <constructor-arg index="1" value="${instance.id}" /> </bean> - <bean id="IMPrefsManager" class="org.olat.instantMessaging.ImPrefsManager" /> + <bean id="IMPrefsManager" class="org.olat.instantMessaging.ImPrefsManager"> + <property name="awarenessVisible" value="${instantMessaging.awarenessVisible}" /> + </bean> + + <!-- fxdiff FXOLAT-219 decrease the load on openfire while synching --> + <bean id="instantMessagingSyncTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> + <property name="jobDetail" ref="org.olat.instantMessaging.sync.learning.groups" /> + <!-- repeat every 10 seconds --> + <property name="repeatInterval" value="10000" /> + <!-- never repeat --> + <property name="repeatCount" value="0"/> + <!-- OLAT-5093 start delay ensures there's no conflict with server startup and db not being ready yet --> + <property name="startDelay" value="${instantMessaging.sync.learning.groups.start.delay}" /> + </bean> + + <bean id="org.olat.instantMessaging.sync.learning.groups" class="org.springframework.scheduling.quartz.JobDetailBean" lazy-init="true"> + <property name="jobClass" value="org.olat.instantMessaging.InstantMessagingJob"/> + </bean> + </beans> diff --git a/src/main/java/org/olat/instantMessaging/rosterandchat/ChangePresenceJob.java b/src/main/java/org/olat/instantMessaging/rosterandchat/ChangePresenceJob.java index 287277146dcd0a668a185e35cf8d92b16bb05311..9cc1e5ea0809ef37575a0ae31543bc4a85214dd0 100644 --- a/src/main/java/org/olat/instantMessaging/rosterandchat/ChangePresenceJob.java +++ b/src/main/java/org/olat/instantMessaging/rosterandchat/ChangePresenceJob.java @@ -123,10 +123,9 @@ public class ChangePresenceJob extends JobWithDB { } } else if ((timeNow - lastAccessTime) > idleWaitTime) { /** - * - * user is back on track - * send presence message available to inform - * + * uses makes a brake + * last access was more than five minutes ago + * so set instant messaging presence to away */ if (InstantMessagingModule.isEnabled()) { if ((client != null && client.isConnected()) @@ -140,12 +139,13 @@ public class ChangePresenceJob extends JobWithDB { } } } else { + /** + * + * user is back on track + * send presence message available to inform + * + */ if (InstantMessagingModule.isEnabled()) { - /** - * uses makes a brake - * last access was more than five minutes ago - * so set instant messaging presence to away - */ if ((client != null && client.isConnected()) && (client.getPresenceMode() == Presence.Mode.away || client.getPresenceMode() == Presence.Mode.xa)) { client.sendPresence(Presence.Type.available, null, 0, Presence.Mode.valueOf(client.getRecentPresenceStatusMode())); @@ -155,7 +155,6 @@ public class ChangePresenceJob extends JobWithDB { log.debug("change presence for user " + client.getUsername() + " back to recent presence."); } } - // check if we can switch user back to available mode } } } diff --git a/src/main/java/org/olat/instantMessaging/rosterandchat/ConnectToServerTask.java b/src/main/java/org/olat/instantMessaging/rosterandchat/ConnectToServerTask.java index 3831feb06c640dbc72a0391fcf403c2085492a7a..e9ffa64e8face3ebd9ed364ff9b98df6136b0a0f 100644 --- a/src/main/java/org/olat/instantMessaging/rosterandchat/ConnectToServerTask.java +++ b/src/main/java/org/olat/instantMessaging/rosterandchat/ConnectToServerTask.java @@ -63,8 +63,8 @@ public class ConnectToServerTask implements Runnable { } else { - if (!im.hasAccount(client.getChatUsername())) { - boolean success = im.createAccount(client.getChatUsername(), client.getPassword(), client.getFullName(), client.getEmail()); + if (!im.hasAccount(client.getUsername())) { + boolean success = im.createAccount(client.getUsername(), client.getPassword(), client.getFullName(), client.getEmail()); if (success) { log.audit("New instant messaging authentication account created for user:" + client.getChatUsername()); client.closeConnection(true); @@ -82,8 +82,8 @@ public class ConnectToServerTask implements Runnable { log.info("User is not authorized to connect to Instant Messaging server (username, server): " + client.getChatUsername() + ", " + client.getServerName()+ ". Make sure this users have an account on the IM server. I will try to recreate the account now"); - if (!im.hasAccount(client.getChatUsername())) { - boolean success = im.createAccount(client.getChatUsername(), client.getPassword(), client.getFullName(), client.getEmail()); + if (!im.hasAccount(client.getUsername())) { + boolean success = im.createAccount(client.getUsername(), client.getPassword(), client.getFullName(), client.getEmail()); if (success) { log.audit("New instant messaging account created for user:" + client.getChatUsername()); client.closeConnection(true); diff --git a/src/main/java/org/olat/instantMessaging/rosterandchat/InstantMessagingMainController.java b/src/main/java/org/olat/instantMessaging/rosterandchat/InstantMessagingMainController.java index 24ea89667a3ba237ae9d04b300b3f721ad94abaa..a7310b8442c49b715a3742c8f8456d90a72eb5d6 100644 --- a/src/main/java/org/olat/instantMessaging/rosterandchat/InstantMessagingMainController.java +++ b/src/main/java/org/olat/instantMessaging/rosterandchat/InstantMessagingMainController.java @@ -55,6 +55,7 @@ import org.olat.instantMessaging.ClientManager; import org.olat.instantMessaging.ConncectedUsersHelper; import org.olat.instantMessaging.InstantMessagingEvent; import org.olat.instantMessaging.InstantMessagingModule; +import org.olat.instantMessaging.OpenInstantMessageEvent; import org.olat.instantMessaging.ui.ConnectedClientsListController; /** @@ -98,7 +99,7 @@ public class InstantMessagingMainController extends BasicController implements G private Map<String, NewMessageIconInfo> showNewMessageHolder = new HashMap<String, NewMessageIconInfo>(2); private EventBus singleUserEventCenter; - private OLATResourceable ass; + public static final OLATResourceable ass = OresHelper.createOLATResourceableType(AssessmentEvent.class); public InstantMessagingMainController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); @@ -213,7 +214,6 @@ public class InstantMessagingMainController extends BasicController implements G newMsgIcon.put("chats", chatMgrCtrl.getInitialComponent()); - ass = OresHelper.createOLATResourceableType(AssessmentEvent.class); singleUserEventCenter = ureq.getUserSession().getSingleUserEventCenter(); singleUserEventCenter.registerFor(this, getIdentity(), ass); @@ -403,6 +403,13 @@ public class InstantMessagingMainController extends BasicController implements G } } + if(event instanceof OpenInstantMessageEvent) { + String jabberId = ((OpenInstantMessageEvent)event).getJabberId(); + UserRequest ureq = ((OpenInstantMessageEvent)event).getUreq(); + chatMgrCtrl.createChat(ureq, getWindowControl(), jabberId); + return; + } + InstantMessagingEvent imEvent = (InstantMessagingEvent)event; if (imEvent.getCommand().equals("presence")) { Presence presence = (Presence) imEvent.getPacket(); diff --git a/src/main/java/org/olat/instantMessaging/syncservice/RemoteAccountCreationOverXMPP.java b/src/main/java/org/olat/instantMessaging/syncservice/RemoteAccountCreationOverXMPP.java index 3bb09d913e57d913b5055d1e2c138bd97ecd8c07..cf44d46a0c664fc69c31885e27a321d5d5a3e372 100644 --- a/src/main/java/org/olat/instantMessaging/syncservice/RemoteAccountCreationOverXMPP.java +++ b/src/main/java/org/olat/instantMessaging/syncservice/RemoteAccountCreationOverXMPP.java @@ -70,6 +70,8 @@ public class RemoteAccountCreationOverXMPP implements RemoteAccountCreation { private boolean sendPacket(IQ packet) { XMPPConnection con = adminUser.getConnection(); + //fxdiff: FXOLAT-233 + if (con==null) return false; try { packet.setFrom(con.getUser()); PacketCollector collector = con.createPacketCollector(new PacketIDFilter(packet.getPacketID())); diff --git a/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java b/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java index 2c80ffcc3adb951c7ae208892ba3960a88447246..71617ba1f0aeb38d53d4c86ee502846b7f37bce4 100644 --- a/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java +++ b/src/main/java/org/olat/instantMessaging/syncservice/RemoteGroupCreationOverXMPP.java @@ -109,6 +109,12 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro } if (++cnt%8==0) { DBFactory.getInstance().intermediateCommit(); + //fxdiff: FXOLAT-219 decrease to load generated by synching learning groups + try { + Thread.sleep(100); + } catch (InterruptedException e) { + log.error("", e); + } } } } @@ -165,6 +171,8 @@ public class RemoteGroupCreationOverXMPP implements InstantMessagingGroupSynchro private boolean sendPacket(IQ packet) { XMPPConnection con = adminUser.getConnection(); + //fxdiff: FXOLAT-233 + if (con==null) return false; try { packet.setFrom(con.getUser()); PacketCollector collector = con.createPacketCollector(new PacketIDFilter(packet.getPacketID())); diff --git a/src/main/java/org/olat/instantMessaging/syncservice/RemoteSessionsOnIMServerOverXMPP.java b/src/main/java/org/olat/instantMessaging/syncservice/RemoteSessionsOnIMServerOverXMPP.java index c48329711115dad9a5006b36954868a4bee3740c..49f8502e394f0d73f2a47c488b1961a16a09a56e 100644 --- a/src/main/java/org/olat/instantMessaging/syncservice/RemoteSessionsOnIMServerOverXMPP.java +++ b/src/main/java/org/olat/instantMessaging/syncservice/RemoteSessionsOnIMServerOverXMPP.java @@ -110,6 +110,8 @@ public class RemoteSessionsOnIMServerOverXMPP implements InstantMessagingSessio private IQ sendPacket(IQ packet) { XMPPConnection con = adminUser.getConnection(); + //fxdiff: FXOLAT-233 + if (con==null) return null; try { packet.setFrom(con.getUser()); PacketCollector collector = con.createPacketCollector(new PacketIDFilter(packet.getPacketID())); diff --git a/src/main/java/org/olat/instantMessaging/ui/ChangeIMSettingsController.java b/src/main/java/org/olat/instantMessaging/ui/ChangeIMSettingsController.java index ba74176e6284f1e55bc0a623120aebbc2b192a23..46a65902858eff8da45fb6c98a5ad0c30779dbeb 100644 --- a/src/main/java/org/olat/instantMessaging/ui/ChangeIMSettingsController.java +++ b/src/main/java/org/olat/instantMessaging/ui/ChangeIMSettingsController.java @@ -77,6 +77,8 @@ public class ChangeIMSettingsController extends BasicController { rosterForm = new RosterForm(ureq, wControl, imPrefs); listenTo(rosterForm); myContent.put("rosterform", rosterForm.getInitialComponent()); + //fxdiff: hide external server info. see FXOLAT-46 + myContent.contextPut("hideExternalClientInfo", InstantMessagingModule.getAdapter().getConfig().isHideExternalClientInfo()); myContent.contextPut("chatusername", InstantMessagingModule.getAdapter().getUserJid(changeableIdentity.getName())); Authentication auth = BaseSecurityManager.getInstance().findAuthentication(changeableIdentity, ClientManager.PROVIDER_INSTANT_MESSAGING); diff --git a/src/main/java/org/olat/instantMessaging/ui/ConnectedUsersListEntry.java b/src/main/java/org/olat/instantMessaging/ui/ConnectedUsersListEntry.java index ed0cc1c56eeb2a2d302e2d18274185aad8f0debf..a3236a45a8b51eb6cb55819fd976c9114978a047 100644 --- a/src/main/java/org/olat/instantMessaging/ui/ConnectedUsersListEntry.java +++ b/src/main/java/org/olat/instantMessaging/ui/ConnectedUsersListEntry.java @@ -143,7 +143,8 @@ public class ConnectedUsersListEntry implements Serializable{ /** * @return Returns the username. */ - protected String getUsername() { + //fxdiff -> baks + public String getUsername() { return username; } @@ -191,8 +192,9 @@ public class ConnectedUsersListEntry implements Serializable{ /** * @return Returns the jabberId. + * fxdiff: public */ - protected String getJabberId() { + public String getJabberId() { return jabberId; } diff --git a/src/main/java/org/olat/instantMessaging/ui/_content/imsettings.html b/src/main/java/org/olat/instantMessaging/ui/_content/imsettings.html index f1df90a8e998f092a4bdd21ead5f21fc5bf80e88..621c700f0290ef8c7aaf6f27eb0982e2b1942cf1 100644 --- a/src/main/java/org/olat/instantMessaging/ui/_content/imsettings.html +++ b/src/main/java/org/olat/instantMessaging/ui/_content/imsettings.html @@ -1,6 +1,8 @@ $r.render("onlinelistform") $r.render("rosterform") +## fxdiff: FXOLAT-46 +#if (!$hideExternalClientInfo) <fieldset> <legend>$r.translate("title.imclient.extern")</legend> <p> @@ -12,3 +14,4 @@ $r.render("rosterform") $r.translate("imclient.extern.pw") <input type="text" value="$password" readonly="readonly"/> </p> </fieldset> +#end diff --git a/src/main/java/org/olat/ldap/LDAPEvent.java b/src/main/java/org/olat/ldap/LDAPEvent.java index aea572232914c185620c8f76496459bcb6b643fd..e60bdceccd62b1413069e361dca2c158ae3a561b 100644 --- a/src/main/java/org/olat/ldap/LDAPEvent.java +++ b/src/main/java/org/olat/ldap/LDAPEvent.java @@ -38,6 +38,7 @@ public class LDAPEvent extends MultiUserEvent { public static final String SYNCHING = "synching"; public static final String DO_SYNCHING = "do_synching"; + public static final String DO_FULL_SYNCHING = "do_full_synching"; public static final String SYNCHING_ENDED = "synching_ended"; private boolean success; diff --git a/src/main/java/org/olat/ldap/LDAPHelper.java b/src/main/java/org/olat/ldap/LDAPHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..11c54caac8abb525fb40820fd13fdee025a4910c --- /dev/null +++ b/src/main/java/org/olat/ldap/LDAPHelper.java @@ -0,0 +1,230 @@ +package org.olat.ldap; + +import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; + +/** + * Description: Helper methods for the LDAP package + * <p> + * LDAPHelper + * <ul> + * </ul> + * <p> + * + * @author Maurus Rohrer + */ + +public class LDAPHelper { + private static OLog log = Tracing.createLoggerFor(LDAPHelper.class); + + private LDAPHelper() { + // do nothing + } + + /** + * Maps LDAP Attributes to the OLAT Property + * + * Configuration: LDAP Attributes Map = olatextconfig.xml (property=userAttrs) + * + * @param attrID LDAP Attribute + * @return OLAT Property + */ + public static String mapLdapAttributeToOlatProperty(String attrID) { + Map<String, String> userAttrMapper = LDAPLoginModule.getUserAttributeMapper(); + String olatProperty = userAttrMapper.get(attrID); + return olatProperty; + } + + + /** + * Maps OLAT Property to the LDAP Attributes + * + * Configuration: LDAP Attributes Map = olatextconfig.xml (property=userAttrs) + * + * @param olatProperty OLAT PropertyattrID + * @return LDAP Attribute + */ + public static String mapOlatPropertyToLdapAttribute(String olatProperty) { + Map<String, String> userAttrMapper = LDAPLoginModule.getReqAttrs(); + if (userAttrMapper.containsValue(olatProperty)) { + Iterator<String> itr = userAttrMapper.keySet().iterator(); + while (itr.hasNext()) { + String key = itr.next(); + if (userAttrMapper.get(key).compareTo(olatProperty) == 0) return key; + } + } + return null; + } + + /** + * Extracts Value out of LDAP Attribute + * + * + * @param attribute LDAP Naming Attribute + * @return String value of Attribute, null on Exception + * + * @throws NamingException + */ + public static String getAttributeValue(Attribute attribute) { + try { + String attrValue = (String) attribute.get(); + return attrValue; + } catch (NamingException e) { + log.error("NamingException when trying to get attribute value for attribute::" + attribute, e); + return null; + } + } + + + /** + * Checks if Collection of naming Attributes contain defined required properties for OLAT + * + * * Configuration: LDAP Required Map = olatextconfig.xml (property=reqAttrs) + * + * @param attributes Collection of LDAP Naming Attribute + * @return null If all required Attributes are found, otherwise String[] of missing Attributes + * + */ + public static String[] checkReqAttr(Attributes attrs) { + Map<String, String> reqAttr = LDAPLoginModule.getReqAttrs(); + String[] missingAttr = new String[reqAttr.size()]; + int y = 0; + Iterator<String> reqItr = reqAttr.keySet().iterator(); + while (reqItr.hasNext()) { + String attKey = reqItr.next().trim(); + if (attrs.get(attKey) == null) { + missingAttr[y++] = attKey; + } + } + if (y == 0) return null; + else return missingAttr; + } + + /** + * Checks if defined OLAT Properties in olatextconfig.xml exist in OLAT. + * + * Configuration: LDAP Attributes Map = olatextconfig.xml (property=reqAttrs, property=userAttributeMapper) + * + * @param attrs Map of OLAT Properties from of the LDAP configuration + * @return true All exist OK, false Error + * + */ + public static boolean checkIfOlatPropertiesExists(Map<String, String> attrs) { + List<UserPropertyHandler> upHandler = UserManager.getInstance().getAllUserPropertyHandlers(); + for (String ldapAttribute : attrs.keySet()) { + boolean propertyExists = false; + String olatProperty = attrs.get(ldapAttribute); + if (olatProperty.equals(LDAPConstants.LDAP_USER_IDENTIFYER)) { + // LDAP user identifyer is not a user propery, it's the username + continue; + } + for (UserPropertyHandler userPropItr : upHandler) { + if (olatProperty.equals(userPropItr.getName())) { + // ok, this property exist, continue with next one + propertyExists = true; + break; + } + } + if ( ! propertyExists ) { + log + .error("Error in checkIfOlatPropertiesExists(): configured LDAP attribute::" + + ldapAttribute + + " configured to map to OLAT user property::" + + olatProperty + + " but no such user property configured in olat_userconfig.xml"); + return false; + } + } + return true; + } + + /** + * Checks if defined Static OLAT Property in olatextconfig.xml exist in OLAT. + * + * Configuration: olatextconfig.xml (property=staticUserProperties) + * + * @param olatProperties Set of OLAT Properties from of the LDAP configuration + * @return true All exist OK, false Error + * + */ + public static boolean checkIfStaticOlatPropertiesExists(Set<String> olatProperties) { + List<UserPropertyHandler> upHandler = UserManager.getInstance().getAllUserPropertyHandlers(); + for (String olatProperty : olatProperties) { + boolean propertyExists = false; + for (UserPropertyHandler userPropItr : upHandler) { + if (olatProperty.equals(userPropItr.getName())) { + // ok, this property exist, continue with next one + propertyExists = true; + break; + } + } + if ( ! propertyExists ) { + log + .error("Error in checkIfStaticOlatPropertiesExists(): configured static OLAT user property::" + + olatProperty + + " is not configured in olat_userconfig.xml"); + return false; + } + } + return true; + } + + /** + * Checks if SSL certification is know and accepted by Java JRE. + * + * + * @param dayFromNow Checks expiration + * @return true Certification accepted, false No valid certification + * + * @throws Exception + * + */ + public static boolean checkServerCertValidity(int daysFromNow) { + KeyStore keyStore; + try { + keyStore = KeyStore.getInstance(LDAPLoginModule.getTrustStoreType()); + keyStore.load(new FileInputStream(LDAPLoginModule.getTrustStoreLocation()), (LDAPLoginModule.getTrustStorePwd() != null) ? LDAPLoginModule.getTrustStorePwd().toCharArray() : null); + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + Certificate cert = keyStore.getCertificate(alias); + if (cert instanceof X509Certificate) { + return isCertificateValid((X509Certificate)cert, daysFromNow); + } + } + } catch (Exception e) { + return false; + } + return false; + } + + private static boolean isCertificateValid(X509Certificate x509Cert, int daysFromNow) { + try { + x509Cert.checkValidity(); + if (daysFromNow > 0) { + Date nowPlusDays = new Date(System.currentTimeMillis() + (new Long(daysFromNow).longValue() * 24l * 60l * 60l * 1000l)); + x509Cert.checkValidity(nowPlusDays); + } + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/olat/ldap/LDAPLoginManager.java b/src/main/java/org/olat/ldap/LDAPLoginManager.java index 8edeed2b97873ca221731ed61bc249f6e2f772c1..7d43dff32224f2ca834e1fd0a4120c3f1d8c65ea 100644 --- a/src/main/java/org/olat/ldap/LDAPLoginManager.java +++ b/src/main/java/org/olat/ldap/LDAPLoginManager.java @@ -34,11 +34,11 @@ import org.olat.core.manager.BasicManager; import org.olat.core.util.resource.OresHelper; public abstract class LDAPLoginManager extends BasicManager { - + public static final OLATResourceable ldapSyncLockOres = OresHelper.createOLATResourceableInstance(LDAPLoginManager.class, 0l); - + public abstract LdapContext bindSystem(); - + public abstract Attributes bindUser(String uid, String pwd, LDAPError errors); public abstract void changePassword(Identity identity, String pwd, LDAPError errors); @@ -64,5 +64,16 @@ public abstract class LDAPLoginManager extends BasicManager { public abstract boolean acquireSyncLock(); public abstract void freeSyncLock(); + + public abstract void doSyncSingleUser(Identity ident); + + public abstract void removeFallBackAuthentications(); + /** + * returns true, if the given identity is member of the LDAP-securitygroup + * + * @param ident + * @return + */ + public abstract boolean isIdentityInLDAPSecGroup(Identity ident); } diff --git a/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java b/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java index 076419d795c7ebd06e3d6b183eb4d2f9bfc90127..5041a54a0b0663e5b3aff4b8b14b388d1ed6fe43 100644 --- a/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java +++ b/src/main/java/org/olat/ldap/LDAPLoginManagerImpl.java @@ -57,6 +57,8 @@ import org.apache.commons.lang.ArrayUtils; import org.olat.admin.user.delete.service.UserDeletionManager; import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.BaseSecurityModule; import org.olat.basesecurity.Constants; import org.olat.basesecurity.SecurityGroup; import org.olat.core.commons.persistence.DBFactory; @@ -65,7 +67,6 @@ import org.olat.core.gui.control.Event; import org.olat.core.id.Identity; import org.olat.core.id.User; import org.olat.core.id.UserConstants; -import org.olat.core.util.WebappHelper; import org.olat.core.util.coordinate.Coordinator; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; @@ -97,6 +98,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve private BaseSecurity securityManager; private UserManager userManager; private UserDeletionManager userDeletionManager; + private boolean pagingSupportedAlreadyFound; static { UTC_TIME_ZONE = TimeZone.getTimeZone(TimeZones.UTC_ID); @@ -147,12 +149,16 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve lastSyncDate = ((LDAPEvent)event).getTimestamp(); } else if(LDAPEvent.DO_SYNCHING.equals(event.getCommand())) { doHandleBatchSync(); + } else if(LDAPEvent.DO_FULL_SYNCHING.equals(event.getCommand())) { + lastSyncDate = null; + doHandleBatchSync(); } } } private void doHandleBatchSync() { - if(WebappHelper.getNodeId() != 1) return; + //fxdiff: also run on nodes != 1 as nodeid = tomcat-id in fx-environment +// if(WebappHelper.getNodeId() != 1) return; Runnable batchSyncTask = new Runnable() { public void run() { @@ -374,12 +380,14 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve String objctClass = LDAPLoginModule.getLdapUserObjectClass(); StringBuilder filter = new StringBuilder(); if (syncTime == null) { + logDebug("LDAP get user attribs since never -> full sync!"); filter.append("(objectClass=").append(objctClass).append(")"); } else { String dateFormat = LDAPLoginModule.getLdapDateFormat(); SimpleDateFormat generalizedTimeFormatter = new SimpleDateFormat(dateFormat); generalizedTimeFormatter.setTimeZone(UTC_TIME_ZONE); String syncTimeForm = generalizedTimeFormatter.format(syncTime); + logDebug("LDAP get user attribs since " + syncTime + " -> means search with date restriction-filter: " + syncTimeForm); filter.append("(&(objectClass=").append(objctClass).append(")(|("); filter.append(LDAPLoginModule.getLdapUserLastModifiedTimestampAttribute()).append(">=").append(syncTimeForm); filter.append(")("); @@ -390,12 +398,17 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve searchInLdap(new LdapVisitor() { public void visit(SearchResult result) { - ldapUserList.add(result.getAttributes()); + Attributes resAttribs = result.getAttributes(); + logDebug(" found : " + resAttribs.size() + " attributes in result " + result.getName()); + ldapUserList.add(resAttribs); } }, filter.toString(), LDAPLoginModule.getUserAttrs(), ctx); + logDebug("attrib search returned " + ldapUserList.size() + " results"); + return ldapUserList; } + /** * Delete all Identities in List and removes them from LDAPSecurityGroup @@ -440,6 +453,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve String value = keyValuePair.getValue(); if(value == null) { if(user.getProperty(propName, null) != null) { + logDebug("removed property " + propName + " for identity " + identity); user.setProperty(propName, value); } } else { @@ -454,6 +468,8 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve user.setProperty(staticProperty.getKey(), staticProperty.getValue()); } } + //fxdiff: FXOLAT-228: update user + userManager.updateUser(user); } /** @@ -476,7 +492,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve String email = getAttributeValue(userAttributes.get(LDAPLoginModule.mapOlatPropertyToLdapAttribute(UserConstants.EMAIL))); // Lookup user if (securityManager.findIdentityByName(uid) != null) { - logError("Can't create user with username='" + uid + "', does already exist in OLAT database", null); + logError("Can't create user with username='" + uid + "', this username does already exist in OLAT database", null); return; } if (!MailHelper.isValidEmailAddress(email)) { @@ -484,8 +500,8 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve logError("Cannot try to lookup user " + uid + " by email with an invalid email::" + email, null); return; } - if (userManager.findIdentityByEmail(email) != null) { - logError("Can't create user with email='" + email + "', does already exist in OLAT database", null); + if (userManager.userExist(email) ) { + logError("Can't create user with email='" + email + "', a user with that email does already exist in OLAT database", null); return; } @@ -498,11 +514,11 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve while (neAttr.hasMore()) { Attribute attr = neAttr.next(); String olatProperty = mapLdapAttributeToOlatProperty(attr.getID()); - if (attr.get() != uid) { + if (!attr.getID().equalsIgnoreCase(LDAPLoginModule.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)) ) { String ldapValue = getAttributeValue(attr); if (olatProperty == null || ldapValue == null) continue; user.setProperty(olatProperty, ldapValue); - } + } } // Add static user properties from the configuration Map<String, String> staticProperties = LDAPLoginModule.getStaticUserProperties(); @@ -567,8 +583,13 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve } } } - if (olatPropertyMap.size() == 1 && olatPropertyMap.get(LDAPConstants.LDAP_USER_IDENTIFYER) != null) return null; - return olatPropertyMap; + if (olatPropertyMap.size() == 1 && olatPropertyMap.get(LDAPConstants.LDAP_USER_IDENTIFYER) != null) { + logDebug("propertymap for identity " + identity.getName() + " contains only userID, NOTHING TO SYNC!"); + return null; + } else { + logDebug("propertymap for identity " + identity.getName() + " contains " + olatPropertyMap.size() + " items (" + olatPropertyMap.keySet() + ") to be synced later on"); + return olatPropertyMap; + } } catch (NamingException e) { logError("NamingException when trying to prepare user properties for LDAP sync", e); @@ -727,15 +748,16 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve try { if(paging) { byte[] cookie = null; - ctx.setRequestControls(new Control[] { new PagedResultsControl(PAGE_SIZE, Control.NONCRITICAL) }); + ctx.setRequestControls(new Control[] { new PagedResultsControl(PAGE_SIZE, Control.CRITICAL) }); do { NamingEnumeration<SearchResult> enm = ctx.search(ldapBase, filter, ctls); while (enm.hasMore()) { visitor.visit(enm.next()); } - cookie = getCookie(ctx); + cookie = getCookie(ctx); } while (cookie != null); } else { + ctx.setRequestControls(null); // reset on failure, see FXOLAT-299 NamingEnumeration<SearchResult> enm = ctx.search(ldapBase, filter, ctls); while (enm.hasMore()) { visitor.visit(enm.next()); @@ -751,6 +773,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve } catch (Exception e) { logError("Exception when trying to fetch deleted users from LDAP using ldapBase::" + ldapBase + " on row::" + counter, e); } + logDebug("finished search for ldapBase:: " + ldapBase); } } @@ -772,6 +795,8 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve } private boolean isPagedResultControlSupported(LdapContext ctx) { + // FXOLAT-299, might return false on 2nd execution + if (pagingSupportedAlreadyFound == true) return true; try { SearchControls ctl = new SearchControls(); ctl.setReturningAttributes(new String[]{"supportedControl"}); @@ -789,6 +814,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve while (vals.hasMore()){ String value = (String) vals.next(); if(value.equals(PAGED_RESULT_CONTROL_OID)) + pagingSupportedAlreadyFound = true; return true; } } @@ -808,10 +834,11 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve * */ public boolean doBatchSync(LDAPError errors) { - if(WebappHelper.getNodeId() != 1) { - logWarn("Sync happens only on node 1", null); - return false; - } + //fxdiff: also run on nodes != 1 as nodeid = tomcat-id in fx-environment +// if(WebappHelper.getNodeId() != 1) { +// logWarn("Sync happens only on node 1", null); +// return false; +// } // o_clusterNOK // Synchronize on class so that only one thread can read the @@ -850,6 +877,9 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve // Get time before sync to have a save sync time when sync is successful String sinceSentence = (lastSyncDate == null ? " (full sync)" : " since last sync from " + lastSyncDate); doBatchSyncDeletedUsers(ctx, sinceSentence); + // fxdiff: see FXOLAT-299 + // bind again to use an initial unmodified context. lookup of server-properties might fail otherwise! + ctx = bindSystem(); doBatchSyncNewAndModifiedUsers(ctx, sinceSentence, errors); // update sync time and set running flag @@ -938,23 +968,29 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve List<Attributes> newLdapUserList = new ArrayList<Attributes>(); Map<Identity, Map<String, String>> changedMapIdentityMap = new HashMap<Identity, Map<String, String>>(); for (Attributes userAttrs: ldapUserList) { - String user = getAttributeValue(userAttrs.get(LDAPLoginModule.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER))); - Identity identity = findIdentyByLdapAuthentication(user, errors); - if (identity != null) { - Map<String, String> changedAttrMap = prepareUserPropertyForSync(userAttrs, identity); - if (changedAttrMap != null) changedMapIdentityMap.put(identity, changedAttrMap); - } else if (errors.isEmpty()) { - String[] reqAttrs = LDAPLoginModule.checkReqAttr(userAttrs); - if (reqAttrs == null) { - newLdapUserList.add(userAttrs); + String user = null; + try { + user = getAttributeValue(userAttrs.get(LDAPLoginModule.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER))); + Identity identity = findIdentyByLdapAuthentication(user, errors); + if (identity != null) { + Map<String, String> changedAttrMap = prepareUserPropertyForSync(userAttrs, identity); + if (changedAttrMap != null) changedMapIdentityMap.put(identity, changedAttrMap); + } else if (errors.isEmpty()) { + String[] reqAttrs = LDAPLoginModule.checkReqAttr(userAttrs); + if (reqAttrs == null) { + newLdapUserList.add(userAttrs); + } + else logWarn("Error in LDAP batch sync: can't create user with username::" + user + " : missing required attributes::" + + ArrayUtils.toString(reqAttrs), null); + } else { + logWarn(errors.get(), null); } - else logWarn("Error in LDAP batch sync: can't create user with username::" + user + " : missing required attributes::" - + ArrayUtils.toString(reqAttrs), null); - } else { - logWarn(errors.get(), null); - } - if(++count % 20 == 0) { - DBFactory.getInstance().intermediateCommit(); + if(++count % 20 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + } catch (Exception e) { + // catch here to go on with other users on exeptions! + logError("some error occured in looping over set of changed user-attributes, actual user " + user + ". Will still continue with others.", e); } } @@ -963,6 +999,7 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve logInfo("LDAP batch sync: no users to sync" + sinceSentence); } else { for (Identity ident : changedMapIdentityMap.keySet()) { + // sync user is exception save, no try/catch needed syncUser(changedMapIdentityMap.get(ident), ident); //REVIEW Identity are not saved??? if(++count % 20 == 0) { @@ -975,16 +1012,49 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve // create new users if (newLdapUserList.isEmpty()) { logInfo("LDAP batch sync: no users to create" + sinceSentence); - } else { - + } else { for (Attributes userAttrs: newLdapUserList) { - createAndPersistUser(userAttrs); - if(++count % 20 == 0) { - DBFactory.getInstance().intermediateCommit(); + try { + createAndPersistUser(userAttrs); + if(++count % 20 == 0) { + DBFactory.getInstance().intermediateCommit(); + } + } catch (Exception e) { + // catch here to go on with other users on exeptions! + logError("some error occured while creating new users, actual userAttribs " + userAttrs + ". Will still continue with others.", e); } } logInfo("LDAP batch sync: " + newLdapUserList.size() + " users created" + sinceSentence); } + + //fxdiff: FXOLAT-228: update user + DBFactory.getInstance().intermediateCommit(); + } + + + // TODO: not finished! + public void doSyncSingleUser(Identity ident){ + LdapContext ctx = bindSystem(); + if (ctx == null) { + logError("could not bind to ldap", null); + } + String userDN = searchUserDN(ident.getName(), ctx); + + final List<Attributes> ldapUserList = new ArrayList<Attributes>(); + // TODO: use userDN instead of filter to get users attribs + searchInLdap(new LdapVisitor() { + public void visit(SearchResult result) { + Attributes resAttribs = result.getAttributes(); + logDebug(" found : " + resAttribs.size() + " attributes in result " + result.getName()); + ldapUserList.add(resAttribs); + } + }, userDN, LDAPLoginModule.getUserAttrs(), ctx); + + Attributes attrs = ldapUserList.get(0); + Map<String, String> olatProToSync = prepareUserPropertyForSync(attrs, ident); + if (olatProToSync != null) { + syncUser(olatProToSync, ident); + } } /** @@ -1027,4 +1097,41 @@ public class LDAPLoginManagerImpl extends LDAPLoginManager implements GenericEve public interface LdapVisitor { public void visit(SearchResult searchResult) throws NamingException ; } + + /** + * remove all cached authentications for fallback-login. useful if users logged in first with a default pw and changed it outside in AD/LDAP, but OLAT doesn't know about. + * removing fallback-auths means login is only possible by AD/LDAP and if server is reachable! + * see FXOLAT-284 + */ + @Override + public void removeFallBackAuthentications() { + if (LDAPLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin()){ + BaseSecurity secMgr = BaseSecurityManager.getInstance(); + SecurityGroup ldapGroup = secMgr.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP); + if (ldapGroup == null) { + logError("Error getting user from OLAT security group '" + LDAPConstants.SECURITY_GROUP_LDAP + "' : group does not exist", null); + } + List<Identity> ldapIdents = secMgr.getIdentitiesOfSecurityGroup(ldapGroup); + logInfo("found " + ldapIdents.size() + " identies in ldap security group"); + int count=0; + for (Identity identity : ldapIdents) { + Authentication auth = secMgr.findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier()); + if (auth!=null){ + secMgr.deleteAuthentication(auth); + count++; + } + if (count % 20 == 0){ + DBFactory.getInstance().intermediateCommit(); + } + } + logInfo("removed cached authentications (fallback login provider: " + BaseSecurityModule.getDefaultAuthProviderIdentifier() + ") for " + count + " users."); + } + } + + @Override + public boolean isIdentityInLDAPSecGroup(Identity ident) { + BaseSecurity secMgr = BaseSecurityManager.getInstance(); + SecurityGroup ldapSecurityGroup = secMgr.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP); + return ldapSecurityGroup != null && secMgr.isIdentityInSecurityGroup(ident, ldapSecurityGroup); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/ldap/LDAPLoginModule.java b/src/main/java/org/olat/ldap/LDAPLoginModule.java index dd3e3d97cac8c4c2d10d0e4bcaff9e9829c98746..ec8abacd082032ab3763e0f39f0635bdc498c42e 100644 --- a/src/main/java/org/olat/ldap/LDAPLoginModule.java +++ b/src/main/java/org/olat/ldap/LDAPLoginModule.java @@ -26,6 +26,8 @@ import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; @@ -490,14 +492,35 @@ public class LDAPLoginModule implements Initializable { } public void setLdapBases(List<String> ldapBasesConfig) { - ldapBases = ldapBasesConfig; + // fxdiff: FXOLAT-141 allow setting in one line + ArrayList<String> listToUse = new ArrayList<String>(); + if (ldapBasesConfig != null) { + for (String baseEntry : ldapBasesConfig) { + if (StringHelper.containsNonWhitespace(baseEntry) && baseEntry.contains("!#")) { + String[] oneLineList = baseEntry.split("!#"); + List<String> oneLineListArr = Arrays.asList(oneLineList); + for (String oneLineEntry : oneLineListArr) { + if (StringHelper.containsNonWhitespace(oneLineEntry)) { + listToUse.add(oneLineEntry.trim()); + } + } + } else { + listToUse.add(baseEntry.trim()); + } + } + } + ldapBases = listToUse; } public void setUserAttributeMapper(Map<String, String> userAttributeMapper) { // trim map userAttrMap = new HashMap<String, String>(); for (Entry<String, String> entry : userAttributeMapper.entrySet()) { - userAttrMap.put(entry.getKey().trim(), entry.getValue().trim()); + String ldapAttrib = entry.getKey().trim(); + String olatProp = entry.getValue().trim(); + if (StringHelper.containsNonWhitespace(ldapAttrib) && StringHelper.containsNonWhitespace(olatProp)){ + userAttrMap.put(ldapAttrib, olatProp); + } } // optimizes for later usage userAttr = userAttrMap.keySet().toArray(new String[userAttrMap.size()]); @@ -514,8 +537,10 @@ public class LDAPLoginModule implements Initializable { public void setSyncOnlyOnCreateProperties(Set<String> syncOnlyOnCreatePropertiesConfig) { // trim map syncOnlyOnCreateProperties = new HashSet<String>(); - for (String value : syncOnlyOnCreatePropertiesConfig) { - syncOnlyOnCreateProperties.add(value.trim()); + for (String value : syncOnlyOnCreatePropertiesConfig) { + if (StringHelper.containsNonWhitespace(value)){ + syncOnlyOnCreateProperties.add(value.trim()); + } } } @@ -523,7 +548,11 @@ public class LDAPLoginModule implements Initializable { // trim map staticUserProperties = new HashMap<String, String>(); for (Entry<String, String> entry : staticUserPropertiesMap.entrySet()) { - staticUserProperties.put(entry.getKey().trim(), entry.getValue().trim()); + String olatPropKey = entry.getKey().trim(); + String staticValue = entry.getValue().trim(); + if (StringHelper.containsNonWhitespace(olatPropKey) && StringHelper.containsNonWhitespace(staticValue)){ + staticUserProperties.put(olatPropKey, staticValue); + } } } diff --git a/src/main/java/org/olat/ldap/_spring/ldapContext.xml b/src/main/java/org/olat/ldap/_spring/ldapContext.xml index 30a49a114adc0bf418ef332e810fa87fab73e236..53c9794232cf658da3b7676be3b2580b2c01c477 100644 --- a/src/main/java/org/olat/ldap/_spring/ldapContext.xml +++ b/src/main/java/org/olat/ldap/_spring/ldapContext.xml @@ -114,9 +114,23 @@ <entry key='${ldap.attributename.email}' value='email' /> <entry key='${ldap.attributename.firstName}' value='firstName' /> <entry key='${ldap.attributename.lastName}' value='lastName' /> + <!-- example for another mapping <entry key='description' value='orgUnit' /> --> + + <!-- some generic usable mappings to allow config by properties --> + <entry key='${ldap.attrib.gen.map.ldapkey1}' value='${ldap.attrib.gen.map.olatkey1}' /> + <entry key='${ldap.attrib.gen.map.ldapkey2}' value='${ldap.attrib.gen.map.olatkey2}' /> + <entry key='${ldap.attrib.gen.map.ldapkey3}' value='${ldap.attrib.gen.map.olatkey3}' /> + <entry key='${ldap.attrib.gen.map.ldapkey4}' value='${ldap.attrib.gen.map.olatkey4}' /> + <entry key='${ldap.attrib.gen.map.ldapkey5}' value='${ldap.attrib.gen.map.olatkey5}' /> + <entry key='${ldap.attrib.gen.map.ldapkey6}' value='${ldap.attrib.gen.map.olatkey6}' /> + <entry key='${ldap.attrib.gen.map.ldapkey7}' value='${ldap.attrib.gen.map.olatkey7}' /> + <entry key='${ldap.attrib.gen.map.ldapkey8}' value='${ldap.attrib.gen.map.olatkey8}' /> + <entry key='${ldap.attrib.gen.map.ldapkey9}' value='${ldap.attrib.gen.map.olatkey9}' /> + <entry key='${ldap.attrib.gen.map.ldapkey10}' value='${ldap.attrib.gen.map.olatkey10}' /> + </map> </property> <!-- @@ -128,6 +142,9 @@ <property name="staticUserProperties"> <map> <!-- <entry key='institutionalName' value='MyInstitution' /> --> + <entry key='${ldap.attrib.static.olatkey1}' value='${ldap.attrib.static.value1}' /> + <entry key='${ldap.attrib.static.olatkey2}' value='${ldap.attrib.static.value2}' /> + <entry key='${ldap.attrib.static.olatkey3}' value='${ldap.attrib.static.value3}' /> </map> </property> <!-- @@ -140,6 +157,9 @@ <property name="syncOnlyOnCreateProperties"> <set> <!-- <value>email</value> --> + <value>${ldap.attrib.sync.once.olatkey1}</value> + <value>${ldap.attrib.sync.once.olatkey2}</value> + <value>${ldap.attrib.sync.once.olatkey3}</value> </set> </property> </bean> diff --git a/src/main/java/org/olat/ldap/ui/LDAPAdminController.java b/src/main/java/org/olat/ldap/ui/LDAPAdminController.java index aa8d77e6823b6ecf99bece479300d7a47134b679..618186dc393af5a3f965231cc3b5b63d165e5c0a 100644 --- a/src/main/java/org/olat/ldap/ui/LDAPAdminController.java +++ b/src/main/java/org/olat/ldap/ui/LDAPAdminController.java @@ -29,6 +29,8 @@ import javax.naming.NamingException; import javax.naming.ldap.LdapContext; import org.apache.log4j.Level; +import org.olat.admin.user.UserSearchController; +import org.olat.basesecurity.events.SingleIdentityChosenEvent; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -39,6 +41,7 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController; import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; @@ -50,6 +53,7 @@ import org.olat.core.util.event.GenericEventListener; import org.olat.ldap.LDAPError; import org.olat.ldap.LDAPEvent; import org.olat.ldap.LDAPLoginManager; +import org.olat.ldap.LDAPLoginModule; /** * Description:<br> @@ -72,6 +76,11 @@ public class LDAPAdminController extends BasicController implements GenericEvent private Integer amountUsersToDelete; private List<Identity> identitiesToDelete; private LDAPLoginManager ldapLoginManager; + private Link fullSyncStartLink; + private UserSearchController userSearchCtrl; + private CloseableCalloutWindowController calloutCtr; + private Link syncOneUserLink; + private Link removeFallBackAuthsLink; protected LDAPAdminController(UserRequest ureq, WindowControl control) { super(ureq, control); @@ -82,10 +91,18 @@ public class LDAPAdminController extends BasicController implements GenericEvent updateLastSyncDateInVC(); // Create start LDAP sync link syncStartLink = LinkFactory.createButton("sync.button.start", ldapAdminVC, this); + fullSyncStartLink = LinkFactory.createButton("full.sync.button.start", ldapAdminVC, this); + // sync one user only +// syncOneUserLink = LinkFactory.createButton("one.user.sync.button.start", ldapAdminVC, this); + + // remove olat-fallback authentications for ldap-users, see FXOLAT-284 + if (LDAPLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin()){ + removeFallBackAuthsLink = LinkFactory.createButton("remove.fallback.auth", ldapAdminVC, this); + } // Create start delete User link deletStartLink = LinkFactory.createButton("delete.button.start", ldapAdminVC, this); // Create real-time log viewer - LogRealTimeViewerController logViewController = new LogRealTimeViewerController(ureq, control, "org.olat.ldap", Level.INFO, true); + LogRealTimeViewerController logViewController = new LogRealTimeViewerController(ureq, control, "org.olat.ldap", Level.DEBUG, true); listenTo(logViewController); ldapAdminVC.put("logViewController", logViewController.getInitialComponent()); // @@ -125,11 +142,28 @@ public class LDAPAdminController extends BasicController implements GenericEvent // Start sync job // Disable start link during sync syncStartLink.setEnabled(false); + fullSyncStartLink.setEnabled(false); LDAPEvent ldapEvent = new LDAPEvent(LDAPEvent.DO_SYNCHING); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ldapEvent, LDAPLoginManager.ldapSyncLockOres); showInfo("admin.synchronize.started"); } - + else if (source == fullSyncStartLink){ + // Start sync job + // Disable start link during sync + syncStartLink.setEnabled(false); + fullSyncStartLink.setEnabled(false); + LDAPEvent ldapEvent = new LDAPEvent(LDAPEvent.DO_FULL_SYNCHING); + CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ldapEvent, LDAPLoginManager.ldapSyncLockOres); + showInfo("admin.synchronize.started"); + } + else if (source == syncOneUserLink){ + userSearchCtrl = new UserSearchController(ureq, getWindowControl(), false); + listenTo(userSearchCtrl); + calloutCtr = new CloseableCalloutWindowController(ureq, getWindowControl(), userSearchCtrl.getInitialComponent(), syncOneUserLink, null, true, null); + calloutCtr.addDisposableChildController(userSearchCtrl); + calloutCtr.activate(); + listenTo(calloutCtr); + } else if (source == deletStartLink) { // cancel if some one else is making sync or delete job if (!ldapLoginManager.acquireSyncLock()) { @@ -140,7 +174,7 @@ public class LDAPAdminController extends BasicController implements GenericEvent // check and get LDAP connection LdapContext ctx = ldapLoginManager.bindSystem(); if (ctx == null) { - showError("LDAP connection ERROR"); + showError("delete.error.connection"); return; } // get deleted users @@ -148,7 +182,7 @@ public class LDAPAdminController extends BasicController implements GenericEvent try { ctx.close(); } catch (NamingException e) { - showError("Could not close LDAP connection on manual delete sync"); + showError("delete.error.connection.close"); logError("Could not close LDAP connection on manual delete sync", e); } if (identitiesToDelete != null && identitiesToDelete.size() != 0) { @@ -189,6 +223,10 @@ public class LDAPAdminController extends BasicController implements GenericEvent ldapLoginManager.freeSyncLock(); } } + } else if (source == removeFallBackAuthsLink){ + removeFallBackAuthsLink.setEnabled(false); + ldapLoginManager.removeFallBackAuthentications(); + showInfo("opsuccess"); } } @@ -217,6 +255,10 @@ public class LDAPAdminController extends BasicController implements GenericEvent } ldapLoginManager.freeSyncLock(); deletStartLink.setEnabled(true); + } else if (source == userSearchCtrl){ + calloutCtr.deactivate(); + Identity choosenIdent = ((SingleIdentityChosenEvent)event).getChosenIdentity(); + ldapLoginManager.doSyncSingleUser(choosenIdent); } } } @@ -238,6 +280,7 @@ public class LDAPAdminController extends BasicController implements GenericEvent } // re-enable start link syncStartLink.setEnabled(true); + fullSyncStartLink.setEnabled(true); // update last sync date updateLastSyncDateInVC(); } diff --git a/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java b/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java index a9b971cd4efbecede25b2506c7af5d560346e8a2..1a5cb657c7130c228d8d1918cccbd4840e8cf490 100644 --- a/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java +++ b/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java @@ -1,5 +1,6 @@ package org.olat.ldap.ui; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -23,6 +24,8 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.id.Identity; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLATSecurityException; import org.olat.core.util.Encoder; @@ -41,8 +44,9 @@ import org.olat.registration.RegistrationManager; import org.olat.user.UserModule; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; -public class LDAPAuthenticationController extends AuthenticationController{ +public class LDAPAuthenticationController extends AuthenticationController implements Activateable2 { public static final String PROVIDER_LDAP = "LDAP"; private VelocityContainer loginComp; @@ -62,7 +66,7 @@ public class LDAPAuthenticationController extends AuthenticationController{ loginComp = createVelocityContainer("ldaplogin"); - if(UserModule.isPwdchangeallowed() && LDAPLoginModule.isPropagatePasswordChangedOnLdapServer()) { + if(UserModule.isPwdchangeallowed(null) && LDAPLoginModule.isPropagatePasswordChangedOnLdapServer()) { pwLink = LinkFactory.createLink("menu.pw", loginComp, this); pwLink.setCustomEnabledLinkCSS("o_login_pwd"); } @@ -89,26 +93,29 @@ public class LDAPAuthenticationController extends AuthenticationController{ @Override protected void event(UserRequest ureq, Component source, Event event) { if (source == pwLink) { - // double-check if allowed first - if (!UserModule.isPwdchangeallowed() || !LDAPLoginModule.isPropagatePasswordChangedOnLdapServer()) - throw new OLATSecurityException("chose password to be changed, but disallowed by config"); - - - removeAsListenerAndDispose(subController); - subController = new PwChangeController(ureq, getWindowControl()); - listenTo(subController); - - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); - listenTo(cmc); - - cmc.activate(); - + openChangePassword(ureq, null); //fxdiff FXOLAT-113: business path in DMZ } else if (source == anoLink) { if (AuthHelper.doAnonymousLogin(ureq, ureq.getLocale()) == AuthHelper.LOGIN_OK) return; else showError("login.error", WebappHelper.getMailConfig("mailSupport")); } } + //fxdiff FXOLAT-113: business path in DMZ + protected void openChangePassword(UserRequest ureq, String initialEmail) { + // double-check if allowed first + if (!UserModule.isPwdchangeallowed(ureq.getIdentity()) || !LDAPLoginModule.isPropagatePasswordChangedOnLdapServer()) + throw new OLATSecurityException("chose password to be changed, but disallowed by config"); + + + removeAsListenerAndDispose(subController); + subController = new PwChangeController(ureq, getWindowControl(), initialEmail); + listenTo(subController); + + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); + listenTo(cmc); + + cmc.activate(); + } protected void event(UserRequest ureq, Controller source, Event event) { @@ -186,6 +193,21 @@ protected void event(UserRequest ureq, Component source, Event event) { } } + @Override + //fxdiff FXOLAT-113: business path in DMZ + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("changepw".equals(type)) { + String email = null; + if(entries.size() > 1) { + email = entries.get(1).getOLATResourceable().getResourceableTypeName(); + } + openChangePassword(ureq, email); + } + } + public static Identity authenticate(String username, String pwd, LDAPError ldapError) { LDAPLoginManager ldapManager = (LDAPLoginManager) CoreSpringFactory.getBean(LDAPLoginManager.class); @@ -210,10 +232,10 @@ protected void event(UserRequest ureq, Component source, Event event) { BaseSecurity secMgr = BaseSecurityManager.getInstance(); Authentication auth = secMgr.findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier()); if (auth == null) { - // Reuse exising authentication token + // Create new authentication token secMgr.createAndPersistAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), username, Encoder.encrypt(pwd)); } else { - // Create new authenticaten token + // Reuse existing authentication token auth.setCredential(Encoder.encrypt(pwd)); DBFactory.getInstance().updateObject(auth); } diff --git a/src/main/java/org/olat/ldap/ui/_content/ldapadmin.html b/src/main/java/org/olat/ldap/ui/_content/ldapadmin.html index af11f12181f28bb22b159b81865668fde1bd8412..c3e61bb5ce38c69fe66b6d095e6e40de178a53f4 100644 --- a/src/main/java/org/olat/ldap/ui/_content/ldapadmin.html +++ b/src/main/java/org/olat/ldap/ui/_content/ldapadmin.html @@ -26,7 +26,25 @@ <p> $r.render("sync.button.start") </p> + <p> + $r.render("full.sync.button.start") + </p> + #if ($r.available("one.user.sync.button.start")) + <p> + $r.render("one.user.sync.button.start") + </p> + #end +</fieldset> + +#if($r.available("remove.fallback.auth")) +<fieldset> + <legend>$r.translate("ldap.maintenance")</legend> + $r.translate("remove.fallback.auth.info") + <p> + $r.render("remove.fallback.auth") + </p> </fieldset> +#end <fieldset> <legend>$r.translate("admin.logview.title")</legend> diff --git a/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_de.properties index 25357fea23815a54cc6d4873c6916b126ce5d480..f2071b3be6390ab5a554952ec36b01c4570e214f 100644 --- a/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_de.properties @@ -17,6 +17,8 @@ authentication.provider.linkText=Weiter checkall=Alle ausw\u00E4hlen delete.button.start=Start delete.error.lock=Ein anderer Adminstrationsprozess ist bereits am laufen. Bitte später nochmals versuchen... +delete.error.connection = Ein LDAP Verbindungsfehler ist aufgetreten. Bitte prüfen Sie die Konfiguration und probieren Sie es später noch einmal. +delete.error.connection.close=Die LDAP Verbindung konnte nicht geschlossen werden nach dem manuellen Synchronizationsprozess. delete.setp1.content.confirm=Folgende Ben\u00FCtzer werden gel\u00F6scht. delete.step.cancel=Abbruch, es wurden keine Benutzer gel\u00F6scht\! delete.step.finish.noUsers=Keine Benutzer gel\u00F6scht\! @@ -33,5 +35,10 @@ lf.pass=LDAP-Passwort login.error=<b>LDAP Benutzername oder Passwort ungültig.</b><p>Mögliche Gründe\:<ul><li><b>Sie haben sich vertippt\:</b><br>Versuchen Sie es bitte nochmals\!<li><b>Sie haben den falschen Benutzernamen und Passwort benutzt\:</b><br /><br/>LDAP ERROR\: {0}</ul></p> login.form=OLAT Anmeldung \u00FCber LDAP login.intro=Bitte melden Sie sich mit Ihrem pers\u00F6nlichen LDAP-Benutzernamen und Passwort an. -sync.button.start=Start +sync.button.start=Starte inkrementelle Synchronisation +full.sync.button.start=Starte volle Synchronisation uncheckall=Auswahl l\u00F6schen +one.user.sync.button.start=Einen Benutzer synchronisieren +remove.fallback.auth=Authentifizierungen aus Cache löschen +remove.fallback.auth.info=Wenn die Authentifizierungen des Fallback-Logins entfernt werden, ist ein Login nur noch über LDAP möglich. Bei Ausfall der Verbindung zum LDAP-Server ist ein Login bis zum erneuten Anlegen des Cache-Passwortes nicht mehr möglich! +ldap.maintenance=Wartungsfunktionen \ No newline at end of file diff --git a/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_en.properties index b304ab6ed707d43e33bbce1bfb63e5be1e3561ee..60f5ba014fcfb418a1c4ccedc7e9787ff32efbd4 100644 --- a/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ldap/ui/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Sun Jan 23 13:19:30 CET 2011 +#Thu May 26 10:41:06 CEST 2011 admin.deleteUser.intro=Delete all users who were deleted from the LDAP directory admin.deleteUser.title=Delete users admin.logview.title=Logging @@ -17,6 +17,8 @@ authentication.provider.linkText=Next checkall=Select all delete.button.start=Start delete.error.lock=Another adminisstration process is still running. Please try again later. +delete.error.connection = An error occured during connection with LDAP server. Please check your LDAP settings and try it again later. +delete.error.connection.close=The LDAP connection could not be closed after manual sync process. delete.setp1.content.confirm=The following users will be deleted\: delete.step.cancel=Abort; no users deleted\! delete.step.finish.noUsers=No users deleted\! @@ -26,6 +28,7 @@ delete.step0.content=Select users to be deleted. delete.step0.description=Select users delete.step1.content.nothingToDelete=No users selected. Please go back to select some users or click on "Finish" to abort deletion. delete.step1.description=Confirm +full.sync.button.start=Start full sync lf.error.loginempty=Please insert your LDAP user name. lf.error.passempty=Please insert your LDAP password. lf.login=LDAP user name @@ -33,5 +36,9 @@ lf.pass=LDAP password login.error=<b>LDAP user name or password invalid.</b><p>Possible reasons may be\:<ul><li><b>You mistyped\:</b><br>Please try again\!<li><b>Wrong user name or password\:</b><br /><br />LDAP ERROR\: {0}</ul></p> login.form=OLAT login via LDAP login.intro=Please log in with your LDAP user name and password. -sync.button.start=Start +one.user.sync.button.start=Synchronize user account +sync.button.start=Start incremental sync uncheckall=Deselect all +remove.fallback.auth=Delete cached fallback-authentications +remove.fallback.auth.info=By removing the fallback-authentications a login is not possible anymore should the connection to the LDAP-server fail. The cache works again after a successful login. +ldap.maintenance=Maintenance \ No newline at end of file diff --git a/src/main/java/org/olat/login/AfterLoginInterceptionController.java b/src/main/java/org/olat/login/AfterLoginInterceptionController.java index 8ca5dcdbf7b13099108a1192993816313c6def53..b8fc10b24508cb2ba13b127da8841fca08225e17 100644 --- a/src/main/java/org/olat/login/AfterLoginInterceptionController.java +++ b/src/main/java/org/olat/login/AfterLoginInterceptionController.java @@ -22,6 +22,7 @@ package org.olat.login; import java.util.ArrayList; import java.util.Calendar; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -34,7 +35,9 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.creator.AutoCreator; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.creator.ControllerCreator; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalWindowController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalWindowWrapperController; import org.olat.core.gui.control.generic.wizard.WizardInfoController; import org.olat.properties.Property; import org.olat.properties.PropertyManager; @@ -54,7 +57,7 @@ import org.olat.properties.PropertyManager; */ public class AfterLoginInterceptionController extends BasicController { - private CloseableModalController cmc; + private CloseableModalWindowWrapperController cmc; private WizardInfoController wiz; private VelocityContainer vC; private Panel actualPanel; @@ -66,10 +69,13 @@ public class AfterLoginInterceptionController extends BasicController { private boolean actualForceUser; // must match with keys in XML private static final String CONTROLLER_KEY = "controller"; + private static final String CONTROLLER = "controller-instance"; private static final String FORCEUSER_KEY = "forceUser"; private static final String REDOTIMEOUT_KEY = "redoTimeout"; private static final String I18NINTRO_KEY = "i18nIntro"; - + private static final String SIZE_KEY = "size"; + protected static final String ORDER_KEY = "order"; + private static final String PROPERTY_CAT = "afterLogin"; public AfterLoginInterceptionController(UserRequest ureq, WindowControl wControl) { @@ -87,32 +93,59 @@ public class AfterLoginInterceptionController extends BasicController { // loop over possible controllers and check if user already did it before // configured timeout - int initialSize = aftctrls.size(); - int j = 0; - for (int i = 0; i < initialSize; i++) { - int correction = 0; - Map<String, Object> ctrlMap = aftctrls.get(j); + for(Iterator<Map<String,Object>> mapInfosIt=aftctrls.iterator(); mapInfosIt.hasNext(); ) { + Map<String,Object> ctrlMap = mapInfosIt.next(); String ctrlName = ((AutoCreator) ctrlMap.get(CONTROLLER_KEY)).getClassName(); // checking for recurring entries if (ctrlMap.containsKey(REDOTIMEOUT_KEY)) { // redo-timeout not yet over, so don't do again Long redoTimeout = Long.parseLong((String) ctrlMap.get(REDOTIMEOUT_KEY)); if (((Calendar.getInstance().getTimeInMillis() / 1000) - redoTimeout) < getLastRunTimeForController(ctrlName)) { - aftctrls.remove(ctrlMap); - correction = -1; + mapInfosIt.remove(); } } else { // check if run already for non-recurring entries if (getRunStateForController(ctrlName)) { - aftctrls.remove(ctrlMap); - correction = -1; + mapInfosIt.remove(); } } - j = i + correction + 1; } // break if nothing survived cleanup or invalid configuration - if (aftctrls == null || aftctrls.size() == 0) { return; } + if (aftctrls == null || aftctrls.size() == 0) { + fireEvent(ureq, Event.DONE_EVENT); + return; + } + + //fxdiff BAKS-20 instantiate controllers in advance to allow back + for(Iterator<Map<String,Object>> mapInfosIt=aftctrls.iterator(); mapInfosIt.hasNext(); ) { + Map<String,Object> mapInfos = mapInfosIt.next(); + if (mapInfos.containsKey(CONTROLLER_KEY)) { + ControllerCreator creator = (ControllerCreator) mapInfos.get(CONTROLLER_KEY); + Controller ctrl = creator.createController(ureq, wControl); + if(ctrl instanceof SupportsAfterLoginInterceptor) { + SupportsAfterLoginInterceptor loginInterceptor = (SupportsAfterLoginInterceptor)ctrl; + if(loginInterceptor.isInterceptionRequired(ureq)) { + mapInfos.put(CONTROLLER, ctrl); + } else { + mapInfosIt.remove(); + } + } else if (ctrl != null ){ + mapInfos.put(CONTROLLER, ctrl); + } else { + mapInfosIt.remove(); + } + } else { + mapInfosIt.remove(); + } + } + if (aftctrls.isEmpty()) { + fireEvent(ureq, Event.DONE_EVENT); + return; + } + // sort controllers according to config + aftctrls = AfterLoginInterceptionManager.sortControllerListByOrder(aftctrls); + wiz = new WizardInfoController(ureq, aftctrls.size()); vC.put("wizard", wiz.getInitialComponent()); vC.contextPut("ctrlCount", aftctrls.size()); @@ -121,9 +154,14 @@ public class AfterLoginInterceptionController extends BasicController { putControllerToPanel(ureq, wControl, 0); vC.put("actualPanel", actualPanel); - cmc = new CloseableModalController(getWindowControl(), translate("close"), vC, true, translate("runonce.title")); + cmc = new CloseableModalWindowWrapperController(ureq, getWindowControl(), translate("runonce.title"), vC, "interceptionPopup"); + cmc.setCloseable(false); + cmc.setIgnoreCookie(true); cmc.activate(); + setPopupSizeForCtr(0); listenTo(cmc); + // if controller could not be created, go to the next one ore close the wizzard + if (actCtrl == null) activateNextOrCloseModal(ureq); } private Long getLastRunTimeForController(String ctrlName) { @@ -139,7 +177,22 @@ public class AfterLoginInterceptionController extends BasicController { } return false; } - + + /** + * + */ + private void setPopupSizeForCtr(int ctrNr){ + if (cmc != null){ + Map<String, Object> mapEntry = aftctrls.get(ctrNr); + if (mapEntry.containsKey(SIZE_KEY)) { + String[] size = mapEntry.get(SIZE_KEY).toString().split("x"); + cmc.resizeWindow(Integer.parseInt(size[0]), Integer.parseInt(size[1])); + }else{ + cmc.setInitialWindowSize(300, 400); + } + } + } + private void saveOrUpdatePropertyForController(UserRequest ureq, String ctrlName) { for (Property prop : ctrlPropList) { if (prop.getName().equals(ctrlName)) { @@ -165,25 +218,24 @@ public class AfterLoginInterceptionController extends BasicController { actualCtrNr = ctrNr; wiz.setCurStep(ctrNr + 1); Map<String, Object> mapEntry = aftctrls.get(ctrNr); - AutoCreator ctrCreator = null; - if (mapEntry.containsKey(CONTROLLER_KEY)) { - ctrCreator = (AutoCreator) mapEntry.get(CONTROLLER_KEY); - } else { - throw new RuntimeException("at least a controller must be defined"); - } + actualForceUser = false; if (mapEntry.containsKey(FORCEUSER_KEY)) { actualForceUser = Boolean.valueOf(mapEntry.get(FORCEUSER_KEY).toString()); } - actCtrl = ctrCreator.createController(ureq, wControl); + removeAsListenerAndDispose(actCtrl); + actCtrl = (Controller)mapEntry.get(CONTROLLER); + if (actCtrl == null) { + return; + } listenTo(actCtrl); if (mapEntry.containsKey(I18NINTRO_KEY)) { String[] introComb = ((String) mapEntry.get(I18NINTRO_KEY)).split(":"); vC.contextPut("introPkg", introComb[0]); vC.contextPut("introKey", introComb[1]); } - + setPopupSizeForCtr(ctrNr); actualPanel.setContent(actCtrl.getInitialComponent()); } @@ -193,6 +245,7 @@ public class AfterLoginInterceptionController extends BasicController { @Override protected void doDispose() { // nothing to dispose as we listen to actCtrl + removeAsListenerAndDispose(actCtrl); aftctrls = null; } @@ -213,7 +266,7 @@ public class AfterLoginInterceptionController extends BasicController { */ @Override protected void event(UserRequest ureq, Controller source, Event event) { - if (source == cmc && event == cmc.CLOSE_MODAL_EVENT && actualForceUser) { + if (source == cmc && event == CloseableModalWindowController.CLOSE_WINDOW_EVENT && actualForceUser) { // show warning if this is a task, where user is forced to do it showWarning("runonce.forced"); cmc.activate(); @@ -224,13 +277,24 @@ public class AfterLoginInterceptionController extends BasicController { // save state of this controller String ctrlName = actCtrl.getClass().getName(); saveOrUpdatePropertyForController(ureq, ctrlName); - + // go to next - if ((actualCtrNr + 1) < aftctrls.size()) { - putControllerToPanel(ureq, getWindowControl(), actualCtrNr + 1); - } else { - cmc.deactivate(); - } + activateNextOrCloseModal(ureq); + } else if (source == actCtrl && event == Event.CANCELLED_EVENT && !actualForceUser){ + // do not persist state. controller sent an cancel event + activateNextOrCloseModal(ureq); + } + } + + + // fxdiff: failsafe continueing + private void activateNextOrCloseModal(UserRequest ureq){ + if ((actualCtrNr + 1) < aftctrls.size()) { + putControllerToPanel(ureq, getWindowControl(), actualCtrNr + 1); + } else { + removeAsListenerAndDispose(actCtrl); + cmc.deactivate(); + fireEvent(ureq, Event.DONE_EVENT); } } diff --git a/src/main/java/org/olat/login/AfterLoginInterceptionManager.java b/src/main/java/org/olat/login/AfterLoginInterceptionManager.java index 58c29741c175089391b3338d0d907ca8a4513eb3..617ee1e884102a9a62c4767e5105b510033d1af6 100644 --- a/src/main/java/org/olat/login/AfterLoginInterceptionManager.java +++ b/src/main/java/org/olat/login/AfterLoginInterceptionManager.java @@ -21,9 +21,13 @@ package org.olat.login; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; +import org.olat.core.manager.BasicManager; + /** * Used to manage a list of all Controllers to be presented afterLogin. * @@ -32,7 +36,7 @@ import java.util.Map; * * @author Roman Haag, roman.haag@frentix.com, www.frentix.com */ -public class AfterLoginInterceptionManager { +public class AfterLoginInterceptionManager extends BasicManager{ private static AfterLoginInterceptionManager INSTANCE; private List<Map<String, Object>> afterLoginControllerList; @@ -51,6 +55,36 @@ public class AfterLoginInterceptionManager { INSTANCE = this; } + + /** + * + */ + protected static List<Map<String,Object>> sortControllerListByOrder(List<Map<String,Object>> list2order){ + + int n = list2order.size(); + for (int pass=1; pass < n; pass++) { // count how many times + // This next loop becomes shorter and shorter + for (int i=0; i < n-pass; i++) { + Map<String,Object> currentCtrConfig_1 = list2order.get(i); + Map<String,Object> currentCtrConfig_2 = list2order.get(i+1); + + int order_1 = 1; + int order_2 = 1; + if (currentCtrConfig_1.containsKey(AfterLoginInterceptionController.ORDER_KEY)) { + order_1 = Integer.parseInt(currentCtrConfig_1.get(AfterLoginInterceptionController.ORDER_KEY).toString()); + } + if (currentCtrConfig_2.containsKey(AfterLoginInterceptionController.ORDER_KEY)) { + order_2 = Integer.parseInt(currentCtrConfig_2.get(AfterLoginInterceptionController.ORDER_KEY).toString()); + } + if (order_1 > order_2) { + Collections.swap(list2order,i,i+1); + } + } + } + return list2order; + } + + /** * @return Returns the afterLoginControllerList. */ @@ -81,6 +115,15 @@ public class AfterLoginInterceptionManager { if (afterLoginControllerList == null) { afterLoginControllerList = new ArrayList<Map<String, Object>>(); } + logInfo("added one or more afterLoginControllers to the list."); + List<Map<String, Object>> ctrlList = aLConf.getAfterLoginControllerList(); + for (Iterator<Map<String, Object>> iterator = ctrlList.iterator(); iterator.hasNext();) { + Map<String, Object> map = iterator.next(); + if (map.containsKey("controller-instance")) logInfo(" controller-instance: " + map.get("controller-instance")); + if (map.containsKey("controller")) logInfo(" controller-key to instantiate: " + map.get("controller")); + if (map.containsKey("forceUser")) logInfo(" force User: " + map.get("forceUser")); + if (map.containsKey("redoTimeout")) logInfo(" redo-Timeout: " + map.get("redoTimeout")); + } afterLoginControllerList.addAll(aLConf.getAfterLoginControllerList()); } diff --git a/src/main/java/org/olat/login/LoginAuthprovidersController.java b/src/main/java/org/olat/login/LoginAuthprovidersController.java index ce146125b1ef83e0fcc1d5f8883f269be3f380ae..fba2d2fcace831405c96e13b717cbfbf83b9d1cb 100644 --- a/src/main/java/org/olat/login/LoginAuthprovidersController.java +++ b/src/main/java/org/olat/login/LoginAuthprovidersController.java @@ -70,7 +70,7 @@ import org.olat.login.auth.AuthenticationProvider; * Initial Date: 02.09.2007 <br> * @author patrickb */ -public class LoginAuthprovidersController extends MainLayoutBasicController { +public class LoginAuthprovidersController extends MainLayoutBasicController implements Activateable2 { private static final String ACTION_LOGIN = "login"; @@ -84,6 +84,9 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { private VelocityContainer content; private Controller authController; private Panel dmzPanel; + private GenericTreeNode checkNode; + private GenericTreeNode accessibilityNode; + private GenericTreeNode aboutNode; private MenuTree olatMenuTree; private LayoutMain3ColsController columnLayoutCtr; @@ -121,8 +124,26 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { listenTo(columnLayoutCtr); // for later autodisposing putInitialPanel(columnLayoutCtr.getInitialComponent()); } - - + + @Override + //fxdiff FXOLAT-113: business path in DMZ + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("browsercheck".equals(type)) { + showBrowserCheckPage(ureq); + olatMenuTree.setSelectedNodeId(checkNode.getIdent()); + } else if ("accessibility".equals(type)) { + showAccessibilityPage(); + olatMenuTree.setSelectedNodeId(accessibilityNode.getIdent()); + } else if ("about".equals(type)) { + showAboutPage(ureq); + olatMenuTree.setSelectedNodeId(aboutNode.getIdent()); + } else if(authController instanceof Activateable2) { + ((Activateable2)authController).activate(ureq, entries, state); + } + } private VelocityContainer initLoginContent(UserRequest ureq, String provider) { // in every case we build the container for pages to fill the panel @@ -206,39 +227,13 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { getWindowControl().setError(translate("login.error", WebappHelper.getMailConfig("mailSupport"))); } } else if (cmd.equals(ACTION_BROWSERCHECK)) { - VelocityContainer browserCheck = createVelocityContainer("browsercheck"); - browserCheck.contextPut("isBrowserAjaxReady", Boolean.valueOf(!Settings.isBrowserAjaxBlacklisted(ureq))); - dmzPanel.pushContent(browserCheck); + showBrowserCheckPage(ureq);//fxdiff FXOLAT-113: business path in DMZ } else if (cmd.equals(ACTION_COOKIES)) { dmzPanel.pushContent(createVelocityContainer("cookies")); } else if (cmd.equals(ACTION_ABOUT)) { - VelocityContainer aboutVC = createVelocityContainer("about"); - // Add version info and licenses - aboutVC.contextPut("version", Settings.getFullVersionInfo()); - aboutVC.contextPut("license", WebappHelper.getOlatLicense()); - // Add translator and languages info - I18nManager i18nMgr = I18nManager.getInstance(); - Set<String> enabledKeysSet = I18nModule.getEnabledLanguageKeys(); - Map<String, String> langNames = new HashMap<String, String>(); - Map<String, String> langTranslators = new HashMap<String, String>(); - String[] enabledKeys = ArrayHelper.toArray(enabledKeysSet); - String[] names = new String[enabledKeys.length]; - for (int i = 0; i < enabledKeys.length; i++) { - String key = enabledKeys[i]; - String langName = i18nMgr.getLanguageInEnglish(key, I18nModule.isOverlayEnabled()); - langNames.put(key, langName); - names[i] = langName; - String author = i18nMgr.getLanguageAuthor(key); - langTranslators.put(key, author); - } - ArrayHelper.sort(enabledKeys, names, true, true, true); - aboutVC.contextPut("enabledKeys", enabledKeys); - aboutVC.contextPut("langNames", langNames); - aboutVC.contextPut("langTranslators", langTranslators); - dmzPanel.pushContent(aboutVC); + showAboutPage(ureq);//fxdiff FXOLAT-113: business path in DMZ } else if (cmd.equals(ACTION_ACCESSIBILITY)) { - VelocityContainer accessibilityVC = createVelocityContainer("accessibility"); - dmzPanel.pushContent(accessibilityVC); + showAccessibilityPage();//fxdiff FXOLAT-113: business path in DMZ } } } else if (event.getCommand().equals(ACTION_LOGIN)) { @@ -248,6 +243,45 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { dmzPanel.pushContent(content); } } + //fxdiff FXOLAT-113: business path in DMZ + protected void showAccessibilityPage() { + VelocityContainer accessibilityVC = createVelocityContainer("accessibility"); + dmzPanel.pushContent(accessibilityVC); + } + //fxdiff FXOLAT-113: business path in DMZ + protected void showBrowserCheckPage(UserRequest ureq) { + VelocityContainer browserCheck = createVelocityContainer("browsercheck"); + browserCheck.contextPut("isBrowserAjaxReady", Boolean.valueOf(!Settings.isBrowserAjaxBlacklisted(ureq))); + dmzPanel.pushContent(browserCheck); + } + //fxdiff FXOLAT-113: business path in DMZ + protected void showAboutPage(UserRequest ureq) { + //fxdiff FXOLAT-139 + VelocityContainer aboutVC = createVelocityContainer("aboutpro"); + // Add version info and licenses + aboutVC.contextPut("version", Settings.getFullVersionInfo()); + aboutVC.contextPut("license", WebappHelper.getOlatLicense()); + // Add translator and languages info + I18nManager i18nMgr = I18nManager.getInstance(); + Set<String> enabledKeysSet = I18nModule.getEnabledLanguageKeys(); + Map<String, String> langNames = new HashMap<String, String>(); + Map<String, String> langTranslators = new HashMap<String, String>(); + String[] enabledKeys = ArrayHelper.toArray(enabledKeysSet); + String[] names = new String[enabledKeys.length]; + for (int i = 0; i < enabledKeys.length; i++) { + String key = enabledKeys[i]; + String langName = i18nMgr.getLanguageInEnglish(key, I18nModule.isOverlayEnabled()); + langNames.put(key, langName); + names[i] = langName; + String author = i18nMgr.getLanguageAuthor(key); + langTranslators.put(key, author); + } + ArrayHelper.sort(enabledKeys, names, true, true, true); + aboutVC.contextPut("enabledKeys", enabledKeys); + aboutVC.contextPut("langNames", langNames); + aboutVC.contextPut("langTranslators", langTranslators); + dmzPanel.pushContent(aboutVC); + } @Override protected void event(UserRequest ureq, Controller source, Event event) { @@ -265,7 +299,8 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { //getWindowControl().setError(translate("login.notavailable", OLATContext.getSupportaddress())); DispatcherAction.redirectToServiceNotAvailable( ureq.getHttpResp() ); } else { - getWindowControl().setError(translate("login.error", WebappHelper.getMailConfig("mailSupport"))); + // fxdiff: show useradmin-mail for pw-requests + getWindowControl().setError(translate("login.error", WebappHelper.getMailConfig("mailReplyTo"))); } } @@ -297,19 +332,19 @@ public class LoginAuthprovidersController extends MainLayoutBasicController { root.addChild(gtn); } - gtn = new GenericTreeNode(); + gtn = checkNode = new GenericTreeNode();//fxdiff FXOLAT-113: business path in DMZ gtn.setTitle(translate("menu.check")); gtn.setUserObject(ACTION_BROWSERCHECK); gtn.setAltText(translate("menu.check.alt")); root.addChild(gtn); - gtn = new GenericTreeNode(); + gtn = accessibilityNode = new GenericTreeNode();//fxdiff FXOLAT-113: business path in DMZ gtn.setTitle(translate("menu.accessibility")); gtn.setUserObject(ACTION_ACCESSIBILITY); gtn.setAltText(translate("menu.accessibility.alt")); root.addChild(gtn); - gtn = new GenericTreeNode(); + gtn = aboutNode = new GenericTreeNode();//fxdiff FXOLAT-113: business path in DMZ gtn.setTitle(translate("menu.about")); gtn.setUserObject(ACTION_ABOUT); gtn.setAltText(translate("menu.about.alt")); diff --git a/src/main/java/org/olat/login/OLATAuthenticationController.java b/src/main/java/org/olat/login/OLATAuthenticationController.java index d0d7158ad381abc038e100b84f1c31e31cfc5922..c22b631820fda068f2d6ddb2d71e0e77e33ca876 100644 --- a/src/main/java/org/olat/login/OLATAuthenticationController.java +++ b/src/main/java/org/olat/login/OLATAuthenticationController.java @@ -61,8 +61,6 @@ import org.olat.user.UserModule; import com.thoughtworks.xstream.XStream; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; - /** * Initial Date: 04.08.2004 * @@ -93,7 +91,7 @@ public class OLATAuthenticationController extends AuthenticationController imple loginComp = createVelocityContainer("olatlogin"); - if(UserModule.isPwdchangeallowed()) { + if(UserModule.isPwdchangeallowed(ureq.getIdentity())) { pwLink = LinkFactory.createLink("menu.pw", loginComp, this); pwLink.setCustomEnabledLinkCSS("o_login_pwd"); } @@ -117,11 +115,9 @@ public class OLATAuthenticationController extends AuthenticationController imple // Check if form is triggered by external loginworkflow that has been failed if (ureq.getParameterSet().contains(PARAM_LOGINERROR)) { - showError(translate("login.error", WebappHelper.getMailConfig("mailSupport"))); + showError(translate("login.error", WebappHelper.getMailConfig("mailReplyTo"))); } - - // support email - loginComp.contextPut("supportmailaddress", WebappHelper.getMailConfig("mailSupport")); + putInitialPanel(loginComp); } @@ -138,43 +134,50 @@ public class OLATAuthenticationController extends AuthenticationController imple public void event(UserRequest ureq, Component source, Event event) { if (source == registerLink) { - removeAsListenerAndDispose(subController); - subController = new RegistrationController(ureq, getWindowControl()); - listenTo(subController); - - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); - listenTo(cmc); - - cmc.activate(); - + //fxdiff FXOLAT-113: business path in DMZ + openRegistration(ureq); } else if (source == pwLink) { - - // double-check if allowed first - if (!UserModule.isPwdchangeallowed()) throw new OLATSecurityException("chose password to be changed, but disallowed by config"); - - removeAsListenerAndDispose(subController); - subController = new PwChangeController(ureq, getWindowControl()); - listenTo(subController); - - removeAsListenerAndDispose(cmc); - cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); - listenTo(cmc); - - cmc.activate(); - + //fxdiff FXOLAT-113: business path in DMZ + openChangePassword(ureq, null); } else if (source == anoLink){ int loginStatus = AuthHelper.doAnonymousLogin(ureq, ureq.getLocale()); if (loginStatus == AuthHelper.LOGIN_OK) { return; } else if (loginStatus == AuthHelper.LOGIN_NOTAVAILABLE){ - showError("login.notavailable", WebappHelper.getMailConfig("mailSupport")); + showError("login.notavailable", null); } else { - showError("login.error", WebappHelper.getMailConfig("mailSupport")); + showError("login.error", WebappHelper.getMailConfig("mailReplyTo")); } } } + //fxdiff FXOLAT-113: business path in DMZ + protected void openRegistration(UserRequest ureq) { + removeAsListenerAndDispose(subController); + subController = new RegistrationController(ureq, getWindowControl()); + listenTo(subController); + + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); + listenTo(cmc); + + cmc.activate(); + } + //fxdiff FXOLAT-113: business path in DMZ + protected void openChangePassword(UserRequest ureq, String initialEmail) { + // double-check if allowed first + if (!UserModule.isPwdchangeallowed(ureq.getIdentity())) throw new OLATSecurityException("chose password to be changed, but disallowed by config"); + + removeAsListenerAndDispose(subController); + subController = new PwChangeController(ureq, getWindowControl(), initialEmail); + listenTo(subController); + + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController(getWindowControl(), translate("close"), subController.getInitialComponent()); + listenTo(cmc); + + cmc.activate(); + } /** * @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) @@ -191,7 +194,7 @@ public class OLATAuthenticationController extends AuthenticationController imple showError("login.blocked", LoginModule.getAttackPreventionTimeoutMin().toString()); return; } else { - showError("login.error", WebappHelper.getMailConfig("mailSupport")); + showError("login.error", WebappHelper.getMailConfig("mailReplyTo")); return; } } @@ -233,9 +236,20 @@ public class OLATAuthenticationController extends AuthenticationController imple } @Override + //fxdiff FXOLAT-113: business path in DMZ public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { if(entries == null || entries.isEmpty()) return; + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("changepw".equals(type)) { + String email = null; + if(entries.size() > 1) { + email = entries.get(1).getOLATResourceable().getResourceableTypeName(); + } + openChangePassword(ureq, email); + } else if("registration".equals(type)) { + openRegistration(ureq); + } } /** diff --git a/src/main/java/org/olat/login/SupportsAfterLoginInterceptor.java b/src/main/java/org/olat/login/SupportsAfterLoginInterceptor.java index 679f2d2af940e6ede9241d83a94b03758f3ba198..0e17660b1c2c4b3f8816ac4e354d8cc196cf1013 100644 --- a/src/main/java/org/olat/login/SupportsAfterLoginInterceptor.java +++ b/src/main/java/org/olat/login/SupportsAfterLoginInterceptor.java @@ -20,12 +20,15 @@ */ package org.olat.login; +import org.olat.core.gui.UserRequest; + /** * Description:<br> * Marker Interface only. Controllers which are intended for use with * AfterLoginInterceptor-mechanism, implement this. * * They have to send an Event.DONE_EVENT in order to let the workflow continue. + * Its allowed to send an Event.CANCELLED_EVENT which doesn't persist a state of the actual controller in wizzard, but still continues. * * <P> * Initial Date: 25.11.2009 <br> @@ -33,5 +36,7 @@ package org.olat.login; * @author Roman Haag, roman.haag@frentix.com, www.frentix.com */ public interface SupportsAfterLoginInterceptor { - // marker only + + public boolean isInterceptionRequired(UserRequest ureq); + } diff --git a/src/main/java/org/olat/login/_content/browsercheck.html b/src/main/java/org/olat/login/_content/browsercheck.html index b2b4bf115c34bfaaad933be7c8688ce12a57f7f9..857c7e3a7dde18afa429e2b2fb7b20fcdf6ef85a 100644 --- a/src/main/java/org/olat/login/_content/browsercheck.html +++ b/src/main/java/org/olat/login/_content/browsercheck.html @@ -13,22 +13,6 @@ function check() { ); } -function check2() { - //dom is calulated by functions.js - - document.write("<tr class='b_table_odd'><td>$r.translate("browsercheck.comfortfunctions.learninggroup")</td><td>"+ (true ? "$r.translate("browsercheck.yes")" : "$r.translate("browsercheck.no")") + "</td></tr>"); - document.write("<tr><td>$r.translate("browsercheck.comfortfunctions.learningarea")</td><td>" + (true ? "$r.translate("browsercheck.yes")" : "$r.translate("browsercheck.no")") + "</td></tr>"); - document.write("<tr class='b_table_odd'><td>$r.translate("browsercheck.comfortfunctions.datechooser")</td><td>" + (true ? "$r.translate("browsercheck.yes")" : "$r.translate("browsercheck.no")") + "</td></tr>"); - document.write("<tr><td>$r.translate("browsercheck.comfortfunctions.htmleditor")</td><td>" + (htmlEditorEnabled ? "$r.translate("browsercheck.yes")" : "$r.translate("browsercheck.no")") + "</td></tr>"); - document.write("<tr class='b_table_odd'><td>$r.translate("browsercheck.comfortfunctions.ajax")</td><td>"); - - #if ($isBrowserAjaxReady) - document.write("$r.translate("browsercheck.yes")"); - #else - document.write("$r.translate("browsercheck.no")"); - #end - document.write("</td></tr>"); -} /* ]]> */ </script> @@ -38,36 +22,30 @@ function check2() { <li>$r.translate("browsercheck.minimum.first")</li> <li>$r.translate("browsercheck.minimum.second")</li> </ul> -</p> - -<p> <h4>$r.translate("browsercheck.bestresults.title")</h4> +<p class="b_float_right"> + <a href="http://www.spreadfirefox.com/?q=affiliates&id=36288&t=55" target="_blank"><img border="0" alt="Get Firefox!" title="Get Firefox!" src="$r.staticLink("images/getfirefox.gif")"/></a> +</p> <ul> - <li><a class="b_link_extern" href="http://www.mozilla.org/products/firefox/" target="_blank">Firefox 3.0</a> $r.translate("browsercheck.bestresults.recommended") $r.translate("browsercheck.bestresults.newerversion") </li> - <!-- safari is disabled as html editor does not fully work, with tiny 3.0 safari should be fine again --> - <!-- <li><a class="b_link_extern" href="http://www.apple.com/safari/" target="_blank">Safari 3.2</a> $r.translate("browsercheck.bestresults.newerversion") </li> --> - <li><a class="b_link_extern" href="http://www.microsoft.com" target="_blank">Microsoft Internet Explorer 8</a> $r.translate("browsercheck.bestresults.newerversion")</li> + <li><a class="b_link_extern" href="http://www.mozilla.org/products/firefox/" target="_blank">Firefox 4</a> $r.translate("browsercheck.bestresults.recommended") $r.translate("browsercheck.bestresults.newerversion") </li> + <li><a class="b_link_extern" href="http://www.apple.com/safari/" target="_blank">Apple Safari 5</a> $r.translate("browsercheck.bestresults.newerversion") </li> + <li><a class="b_link_extern" href="http://www.google.com/chrome/" target="_blank">Google Chrome 11</a> $r.translate("browsercheck.bestresults.newerversion") </li> + <li><a class="b_link_extern" href="http://www.microsoft.com/internetexplorer/" target="_blank">Microsoft Internet Explorer 9</a> $r.translate("browsercheck.bestresults.newerversion")</li> </ul> -$r.translate("browsercheck.osx") + +<h4>$r.translate("browsercheck.others.title")</h4> +<p> + $r.translate("browsercheck.others.others") </p> -<p align="center"> - <a href="http://www.spreadfirefox.com/?q=affiliates&id=36288&t=55" target="_blank"><img border="0" alt="Get Firefox!" title="Get Firefox!" src="$r.staticLink("images/getfirefox.gif")"/></a> +<p> + $r.translate("browsercheck.others.IE6") + <a href="http://ie6countdown.com/">IE6 countdown</a> </p> -<p> +<h4>$r.translate("browsercheck.yourbrowser.title")</h4> <table class="b_table b_grid b_full"><tbody> - <tr> - <td colspan="2"><h4>$r.translate("browsercheck.yourbrowser.title") </h4></td> - </tr> <script type="text/javascript"> check(); </script> - <tr> - <td colspan="2"><br /><h4>$r.translate("browsercheck.comfortfunctions.title")</h4></td> - </tr> - <script type="text/javascript"> - check2(); - </script> </tbody></table> -</p> diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties index 0b8c960fe7c53c3cd7f083361fd09d3946f5c89b..b1abfa5293ef3e9f1f8ba08aaf7c597482369de7 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties @@ -1,4 +1,11 @@ #Mon Mar 02 09:54:04 CET 2009 +about.pro.slogan=lernen Sie los! +about.pro.subtitle=OpenSource Power mit professionellem Service. Dauerhaft, garantiert! +about.pro.intro=OLAT<sup>pro</sup> von frentix vereint die Vorzüge des preisgekrönten Open Source Learning Management Systems OLAT mit den professionellen Dienstleistungen von frentix. +about.pro.intro2=Das Produkt OLAT<sup>pro</sup> von frentix bietet diverse Erweiterungen und Verbesserungen zum Open Source Standard Release, damit OLAT auch in Schulen, Verwaltungen und Unternehmen optimal genutzt werden kann. +about.pro.award=Das Produkt OLAT<sup>pro</sup> und die Dienstleistungen von frentix wurden mit dem Swiss Open Source Award 2011 als "Best Business Case" ausgezeichnet. +about.pro.release=Release Information + about.copyright=© 1999-2011 bei den folgenden Entwicklern about.date=Z\u00FCrich, 8. Februar 2011 about.guigroup=Mit der wertvollen Unterst\u00FCtzung der GUI-Arbeitsgruppe @@ -57,6 +64,9 @@ browsercheck.yourbrowser.name=Browser\: browsercheck.yourbrowser.os=Betriebssystem\: browsercheck.yourbrowser.title=Sie verwenden folgenden Browser\: browsercheck.yourbrowser.usragent=User agent\: +browsercheck.others.title=Andere Browser +browsercheck.others.others=In der Regel ist OLAT auch mit anderen Browsern wie dem mobile Safari in iPhone/iPad oder Opera problemlos nutzbar. Für Administratoren und Autoren ist es dennoch empfehlenswert einen der oben genannten Browsern zu verwenden. +browsercheck.others.IE6=Der Internet Explorer 6 von Microsoft wird nicht mehr durchgängig unterstützt. Lesen Sie mehr dazu hier: chelp.about1=Hier finden Sie die Lizenzbestimmungen f\u00FCr den Einsatz von OLAT. chelp.check1=Testen Sie, ob Ihr Browser so eingestellt ist, dass die Funktionali\u00E4t von OLAT einwandfrei genutzt werden kann. chelp.dmz.title=OLAT-Einstieg diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties index 0b14b4304ad2794dbf64c884de43646830f10a7f..2f4f15185975838f78dc591eeae5b9cc3b85e00b 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties @@ -1,4 +1,11 @@ #Tue Feb 01 15:31:29 CET 2011 +about.pro.slogan=start learning! +about.pro.subtitle=OpenSource power with professional service. Durable, guaranteed! +about.pro.intro=OLAT<sup>pro</sup> by frentix combines the advantages of the award winning open source learning management system OLAT with the professional services of frentix. +about.pro.intro2=The product OLAT<sup>pro</sup> by frentix offers various extensions and improvements compared to the open source standard release to make OLAT perfectly fit for using in schools, an administrations and coorporate environments. +about.pro.award=The product OLAT<sup>pro</sup> and the services by frentix have been awarded with the Swiss Open Source Award 2011 as the "Best Business Case". +about.pro.release=Release information + about.copyright=© 1999-2011 by the following contributers about.date=Zurich, February 8, 2011 about.guigroup=With the valuable support of the GUI group @@ -56,6 +63,10 @@ browsercheck.yourbrowser.name=Browser\: browsercheck.yourbrowser.os=Operating system\: browsercheck.yourbrowser.title=You are using the following browser\: browsercheck.yourbrowser.usragent=User agent\: +browsercheck.others.title=Other browsers +browsercheck.others.others=Generally OLAT can also be used with other browsers like mobile Safari for iPhone/iPad or Opera. For administrators and authors it is nevertheless recommended to use one of the above mentioned browsers. +browsercheck.others.IE6=Internet Explorer 6 by Microsoft is not fully supported anymore. Read more about this here: + chelp.about1=Here you will get information on licence conditions when using OLAT. chelp.check1=Please make sure that your browser is set in a way that OLAT's functionality can be used properly. chelp.dmz.title=Access to OLAT diff --git a/src/main/java/org/olat/login/auth/OLATAuthManager.java b/src/main/java/org/olat/login/auth/OLATAuthManager.java index d29d5bca134f11e96fdb22c330376bc1c0aa6a54..49dff9c099b2ef5deb1b0192d7083efd93926d01 100644 --- a/src/main/java/org/olat/login/auth/OLATAuthManager.java +++ b/src/main/java/org/olat/login/auth/OLATAuthManager.java @@ -50,10 +50,18 @@ public class OLATAuthManager extends BasicManager { private static OLog log = Tracing.createLoggerFor(OLATAuthenticationController.class); /** - * Change the password of an identity - * @param doer Identity who is changing the password - * @param identity Identity who's password is beeing changed. - * @param newPwd New password. + * Change the password of an identity. if the given identity is a LDAP-User, + * the pw-change is propagated to LDAP (according to config) NOTE: caller of + * this method should check if identity is allowed to change it's own pw [ + * UserModule.isPwdchangeallowed(Identity ident) ], applies only if doer + * equals identity + * + * @param doer + * Identity who is changing the password + * @param identity + * Identity who's password is beeing changed. + * @param newPwd + * New password. * @return True upon success. */ public static boolean changePassword(Identity doer, Identity identity, String newPwd) { diff --git a/src/main/java/org/olat/modules/cp/CPDisplayController.java b/src/main/java/org/olat/modules/cp/CPDisplayController.java index 155ceda539ada81a5e71f3ae19d1afe43075bf8c..af5612897255a88ccb6a9317916d370c6f44ce2b 100644 --- a/src/main/java/org/olat/modules/cp/CPDisplayController.java +++ b/src/main/java/org/olat/modules/cp/CPDisplayController.java @@ -25,12 +25,15 @@ package org.olat.modules.cp; import java.util.List; import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.search.ui.SearchController; import org.olat.core.commons.services.search.ui.SearchServiceUIFactory; import org.olat.core.commons.services.search.ui.SearchServiceUIFactory.DisplayOption; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.htmlsite.HtmlStaticPageComponent; import org.olat.core.gui.components.htmlsite.NewInlineUriEvent; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.tree.MenuTree; import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.components.tree.TreeNode; @@ -39,9 +42,11 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.iframe.IFrameDisplayController; import org.olat.core.gui.control.generic.iframe.NewIframeUriEvent; +import org.olat.core.gui.control.winmgr.JSCommand; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.NotFoundMediaResource; import org.olat.core.id.OLATResourceable; @@ -81,7 +86,16 @@ public class CPDisplayController extends BasicController implements Activateable private String selNodeId; private HtmlStaticPageComponent cpComponent; private IFrameDisplayController cpContentCtr; - private Controller searchCtrl; + private SearchController searchCtrl; + private Link nextLink; + private Link previousLink; + //fxdiff VCRP-14: print cp + private Link printLink; + private String mapperBaseURL; + private CPPrintMapper printMapper; + + private CPSelectPrintPagesController printController; + private CloseableModalController printPopup; /** * @param ureq @@ -89,8 +103,8 @@ public class CPDisplayController extends BasicController implements Activateable * @param showMenu * @param activateFirstPage */ - CPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean activateFirstPage, - String initialUri, OLATResourceable ores) { + CPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean showNavigation, + boolean activateFirstPage, boolean showPrint, String initialUri, OLATResourceable ores) { super(ureq, wControl); this.rootContainer = rootContainer; @@ -132,6 +146,33 @@ public class CPDisplayController extends BasicController implements Activateable cpTree.setTreeModel(ctm); cpTree.addListener(this); } + + //fxdiff VCRP-14: print cp + if(showPrint) { + printLink = LinkFactory.createLink("print", myContent, this); + printLink.setCustomEnabledLinkCSS("b_small_icon o_cp_print_icon"); + printLink.setCustomDisplayText("Print"); + printLink.setTooltip("print", false); + + String themeBaseUri = wControl.getWindowBackOffice().getWindow().getGuiTheme().getBaseURI(); + printMapper = new CPPrintMapper(ctm, rootContainer, themeBaseUri); + mapperBaseURL = registerMapper(printMapper); + printMapper.setBaseUri(mapperBaseURL); + } + + //fxdiff VCRP-13: cp navigation + if(showNavigation) { + nextLink = LinkFactory.createLink("next", myContent, this); + nextLink.setCustomEnabledLinkCSS("b_small_icon o_cp_next_icon"); + nextLink.setCustomDisplayText(" "); + nextLink.setTooltip("next", false); + previousLink = LinkFactory.createLink("previous", myContent, this); + previousLink.setCustomEnabledLinkCSS("b_small_icon o_cp_previous_icon"); + previousLink.setCustomDisplayText(" "); + previousLink.setTooltip("next", false); + myContent.put("next", nextLink); + myContent.put("previous", previousLink); + } LoggingResourceable nodeInfo = null; if (activateFirstPage) { @@ -155,6 +196,8 @@ public class CPDisplayController extends BasicController implements Activateable selNodeId = node.getIdent(); nodeInfo = LoggingResourceable.wrapCpNode(nodeUri); + //fxdiff VCRP-13: cp navigation + updateNextPreviousLink(node); //fxdiff BAKS-7 Resume function if(node.getUserObject() != null) { String identifierRes = (String)node.getUserObject(); @@ -176,6 +219,8 @@ public class CPDisplayController extends BasicController implements Activateable } else { selNodeId = newNode.getIdent(); } + //fxdiff VCRP-13: cp navigation + updateNextPreviousLink(newNode); //fxdiff BAKS-7 Resume function if(newNode.getUserObject() != null) { String identifierRes = (String)newNode.getUserObject(); @@ -203,12 +248,20 @@ public class CPDisplayController extends BasicController implements Activateable if(cpContentCtr != null) { cpContentCtr.setContentEncoding(encoding); } + //fxdiff VCRP-14: print cp + if(printMapper != null) { + printMapper.setContentEncoding(encoding); + } } public void setJSEncoding(String encoding) { if(cpContentCtr != null) { cpContentCtr.setJSEncoding(encoding); } + //fxdiff VCRP-14: print cp + if(printMapper != null) { + printMapper.setJSEncoding(encoding); + } } /** @@ -238,6 +291,25 @@ public class CPDisplayController extends BasicController implements Activateable // adjust the tree selection to the current choice if found selectTreeNode(ureq, nue.getNewUri()); } + //fxdiff VCRP-13: cp navigation + } else if (source == nextLink) { + TreeNode nextUri = (TreeNode)nextLink.getUserObject(); + switchToPage(ureq, nextUri); + if(cpTree != null) { + cpTree.setSelectedNode(nextUri); + } + fireEvent(ureq, new TreeNodeEvent(nextUri)); + //fxdiff VCRP-13: cp navigation + } else if (source == previousLink) { + TreeNode previousUri = (TreeNode)previousLink.getUserObject(); + if(cpTree != null) { + cpTree.setSelectedNode(previousUri); + } + switchToPage(ureq, previousUri); + fireEvent(ureq, new TreeNodeEvent(previousUri)); + //fxdiff VCRP-14: print cp + } else if (source == printLink) { + selectPagesToPrint(ureq); } } @@ -253,6 +325,24 @@ public class CPDisplayController extends BasicController implements Activateable selectTreeNode(ureq, nue.getNewUri()); }// else ignore (e.g. misplaced olatcmd event (inner olat link found in a // contentpackaging file) + //fxdiff VCRP-14: print cp + } else if (source == printPopup) { + removeAsListenerAndDispose(printPopup); + removeAsListenerAndDispose(printController); + printController = null; + printPopup = null; + //fxdiff VCRP-14: print cp + } else if (source == printController) { + if(Event.DONE_EVENT == event) { + List<String> nodeToPrint = printController.getSelectedNodeIdents(); + printPages(nodeToPrint); + } + + printPopup.deactivate(); + removeAsListenerAndDispose(printPopup); + removeAsListenerAndDispose(printController); + printController = null; + printPopup = null; } } @@ -276,6 +366,27 @@ public class CPDisplayController extends BasicController implements Activateable } } + //fxdiff VCRP-14: print cp + private void printPages(final List<String> selectedNodeIds) { + StringBuilder sb = new StringBuilder(); + sb.append("window.open('" + mapperBaseURL + "/print.html', '_print','height=800,left=100,top=100,width=800,toolbar=no,titlebar=0,status=0,menubar=yes,location= no,scrollbars=1');"); + printMapper.setSelectedNodeIds(selectedNodeIds); + getWindowControl().getWindowBackOffice().sendCommandTo(new JSCommand(sb.toString())); + } + + //fxdiff VCRP-14: print cp + private void selectPagesToPrint(UserRequest ureq) { + removeAsListenerAndDispose(printController); + removeAsListenerAndDispose(printPopup); + + printController = new CPSelectPrintPagesController(ureq, getWindowControl(), ctm); + listenTo(printController); + + printPopup = new CloseableModalController(getWindowControl(), "cancel", printController.getInitialComponent(), true, translate("print.node.list.title")); + listenTo(printPopup); + printPopup.activate(); + } + /** * adjust the cp menu tree with the page selected with a link clicked in the content * @param ureq @@ -298,6 +409,21 @@ public class CPDisplayController extends BasicController implements Activateable // course), we fire an event with the chosen node) fireEvent(ureq, new TreeNodeEvent(newNode)); } + updateNextPreviousLink(newNode); + } + } + + //fxdiff VCRP-13: cp navigation + private void updateNextPreviousLink(TreeNode currentNode) { + if(nextLink != null) { + TreeNode nextNode = ctm.getNextNodeWithContent(currentNode); + nextLink.setEnabled(nextNode != null); + nextLink.setUserObject(nextNode); + } + if(previousLink != null) { + TreeNode previousNode = ctm.getPreviousNodeWithContent(currentNode); + previousLink.setEnabled(previousNode != null); + previousLink.setUserObject(previousNode); } } @@ -313,6 +439,12 @@ public class CPDisplayController extends BasicController implements Activateable // switch to the new page String nodeId = te.getNodeId(); TreeNode tn = ctm.getNodeById(nodeId); + if(tn != null) { + switchToPage(ureq, tn); + } + } + + public void switchToPage(UserRequest ureq, TreeNode tn) { String identifierRes = (String) tn.getUserObject(); //fxdiff BAKS-7 Resume function Long id = Long.parseLong(tn.getIdent()); @@ -350,6 +482,8 @@ public class CPDisplayController extends BasicController implements Activateable cpTree.setDirty(false); } } + + updateNextPreviousLink(tn); ThreadLocalUserActivityLogger.log(CourseLoggingAction.CP_GET_FILE, getClass(), LoggingResourceable.wrapCpNode(identifierRes)); } diff --git a/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java b/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java index 62e918693b000c852c6c5f07afac9f355f61e0b6..2f507ac540d3b210cb841845357b7cdcb08ee38b 100644 --- a/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java +++ b/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -38,7 +39,6 @@ import org.olat.core.gui.components.tree.GenericTreeNode; import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; -import org.olat.core.util.Formatter; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.xml.XMLParser; import org.olat.ims.resources.IMSEntityResolver; @@ -50,9 +50,10 @@ import org.olat.ims.resources.IMSEntityResolver; public class CPManifestTreeModel extends GenericTreeModel { private Element rootElement; - private Map nsuris = new HashMap(2); - private Map hrefToTreeNode = new HashMap(); - private Map resources; // keys: resource att 'identifier'; values: resource att 'href' + private final Map<String,String> nsuris = new HashMap<String,String>(2); + private final Map<String,TreeNode> hrefToTreeNode = new HashMap<String,TreeNode>(); + private Map<String,String> resources; // keys: resource att 'identifier'; values: resource att 'href' + private final List<TreeNode> treeNodes = new ArrayList<TreeNode>(); /** * Constructor of the content packaging tree model @@ -76,7 +77,7 @@ public class CPManifestTreeModel extends GenericTreeModel { if (elResources == null) throw new AssertException("could not find element resources"); List resourcesList = elResources.elements("resource"); - resources = new HashMap(resourcesList.size()); + resources = new HashMap<String,String>(resourcesList.size()); for (Iterator iter = resourcesList.iterator(); iter.hasNext();) { Element elRes = (Element) iter.next(); String identVal = elRes.attributeValue("identifier"); @@ -101,11 +102,47 @@ public class CPManifestTreeModel extends GenericTreeModel { * @return TreeNode representing this href */ public TreeNode lookupTreeNodeByHref(String href) { - return (TreeNode) hrefToTreeNode.get(href); + return hrefToTreeNode.get(href); + } + + //fxdiff VCRP-13: cp navigation + public List<TreeNode> getFlattedTree() { + return new ArrayList<TreeNode>(treeNodes); + } + + //fxdiff VCRP-13: cp navigation + public TreeNode getNextNodeWithContent(TreeNode node) { + if(node == null) return null; + int index = treeNodes.indexOf(node); + + for(int i=index+1; i<treeNodes.size(); i++) { + TreeNode nextNode = treeNodes.get(i); + if(nextNode.getUserObject() != null) { + return nextNode; + } + } + + return null; + } + + public TreeNode getPreviousNodeWithContent(TreeNode node) { + if(node == null) return null; + int index = treeNodes.indexOf(node); + + for(int i=index; i-->0; ) { + TreeNode nextNode = treeNodes.get(i); + if(nextNode.getUserObject() != null) { + return nextNode; + } + } + + return null; } private GenericTreeNode buildNode(Element item) { GenericTreeNode gtn = new GenericTreeNode(); + //fxdiff VCRP-13: cp navigation + treeNodes.add(gtn); // extract title String title = item.elementText("title"); @@ -122,7 +159,7 @@ public class CPManifestTreeModel extends GenericTreeModel { String identifierref = item.attributeValue("identifierref"); XPath meta = rootElement.createXPath("//ns:resource[@identifier='" + identifierref + "']"); meta.setNamespaceURIs(nsuris); - String href = (String) resources.get(identifierref); + String href = resources.get(identifierref); if (href != null) { gtn.setUserObject(href); // allow lookup of a treenode given a href so we can quickly adjust the menu if the user clicks on hyperlinks within the text diff --git a/src/main/java/org/olat/modules/cp/CPPrintMapper.java b/src/main/java/org/olat/modules/cp/CPPrintMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..2ad81219a2ddc0c3dd734c37f65ec085d6205adc --- /dev/null +++ b/src/main/java/org/olat/modules/cp/CPPrintMapper.java @@ -0,0 +1,529 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.modules.cp; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.batik.css.parser.ParseException; +import org.apache.batik.css.parser.Parser; +import org.cyberneko.html.parsers.SAXParser; +import org.olat.core.defaults.dispatcher.StaticMediaDispatcher; +import org.olat.core.dispatcher.mapper.Mapper; +import org.olat.core.gui.components.tree.TreeNode; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; +import org.olat.core.gui.media.StringMediaResource; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; +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.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * + * Description:<br> + * Deliver the CP as a single page. All the HTML Pages are parser and the + * attribute href/src are rewritten to absolute /olat/m/xxxx urls. The HTML parser + * used is NekoHTML. For CSS there is the same process. We use the Batik CSS + * parser for SAC. + * + * <P> + * Initial Date: 18 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-14: print cp +public class CPPrintMapper implements Mapper { + + private static final OLog log = Tracing.createLoggerFor(CPPrintMapper.class); + + private static final String DEFAULT_ENCODING = "iso-8859-1"; + private static final String UNICODE_ENCODING = "unicode"; + private static final String DEFAULT_CONTENT_TYPE = "text/html"; + private static final String XHTML_EXTENSION = "xhtml"; + private static final String XHTML_CONTENT_TYPE = "application/xhtml+xml"; + private static final Pattern PATTERN_ENCTYPE = Pattern.compile("<meta.*charset=([^\"\']*)[\"\']", Pattern.CASE_INSENSITIVE); + private static final Pattern PATTERN_XML_ENCTYPE = Pattern.compile("<\\?xml.*encoding=[\"\']([^\"\']*)[\"\']", Pattern.CASE_INSENSITIVE); + private static final Pattern PATTERN_CONTTYPE = Pattern.compile("<meta.*content-type\"?\\s*content\\s*=\\s*[\"]?+(.+?)([\"]?+\\s*/>)", Pattern.CASE_INSENSITIVE); + private static final Pattern PATTERN_DOCTYPE = Pattern.compile("<!DOCTYPE\\s*html\\s*PUBLIC\\s*[\"\']\\s*-//W3C//DTD\\s*(.+?)(//EN)", Pattern.CASE_INSENSITIVE); + private static final String FILE_SUFFIX_HTM = "htm"; + private static final String FILE_SUFFIX_CSS = "css"; + private static final String TAG_FRAMESET = "<frameset"; + private static final String TAG_FRAMESET_UPPERC = "<FRAMESET"; + private static final String FILE_SUFFIX_JS = ".js"; + + private String g_encoding; + + private List<String> selectedNodeIds; + + private String baseUri; + private final String themeBaseUri; + private final CPManifestTreeModel ctm; + private final VFSContainer rootDir; + + private String contentEncoding; + private String jsEncoding; + + public CPPrintMapper(CPManifestTreeModel ctm, VFSContainer rootContainer, String themeBaseUri) { + this.themeBaseUri = themeBaseUri; + this.rootDir = rootContainer; + this.ctm = ctm; + } + + public void setBaseUri(String baseUri) { + this.baseUri = baseUri; + } + + public void setSelectedNodeIds(List<String> selectedNodeIds) { + this.selectedNodeIds = selectedNodeIds; + } + + public void setContentEncoding(String encoding) { + this.contentEncoding = encoding; + } + + public void setJSEncoding(String encoding) { + this.jsEncoding = encoding; + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + if(relPath.endsWith("print.html")) { + return deliverCompositePage(request); + } + return deliverFile(request, relPath); + } + + private MediaResource deliverCompositePage(HttpServletRequest request) { + List<NekoHtmlPageHandler> parsedPages = composePrintPage(selectedNodeIds); + + StringBuilder sb = new StringBuilder(); + sb.append("<html><head>"); + for(NekoHtmlPageHandler page:parsedPages) { + sb.append("<!-- Header of -->"); + sb.append(page.getHeader()).append("\n\n"); + } + injectJavascriptAndCss(sb); + + sb.append("</head><body onload='window.focus();window.print()'>"); + for(NekoHtmlPageHandler page:parsedPages) { + if(page.isEmpty()) { + String title = page.getTitle(); + if(StringHelper.containsNonWhitespace(title)) { + int level = page.getLevel() + 1; + level = Math.min(level, 6); + sb.append("<h").append(level).append(">").append(page.getTitle()).append("</h").append(level).append(">"); + } + } else { + bodyDecorator(page, sb); + } + } + sb.append("</body></html>"); + + MediaResource mr = prepareMediaResource(request, sb.toString(), g_encoding, "text/html"); + return mr; + } + + private void injectJavascriptAndCss(StringBuilder output) { + StringOutput sb = new StringOutput(); + sb.append("\n<script type=\"text/javascript\" src=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/prototype/prototype.js"); + sb.append("\"></script><script type=\"text/javascript\" src=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/ext/adapter/prototype/ext-prototype-adapter.js"); + sb.append("\"></script><link rel=\"stylesheet\" type=\"text/css\" href=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/ext/resources/css/ext-all.css"); + sb.append("\" /><script type=\"text/javascript\" src=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/ext-core.js"); + sb.append("\"></script><script type=\"text/javascript\" src=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/ext-foundation.js"); + sb.append("\"></script><script type=\"text/javascript\" src=\""); + StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/cmp-foundation.js"); + sb.append("\"></script>"); + output.append(sb.toString()); + + /* + output.append("<script>") + .append("window.onLoad(function() { window.focus(); window.print() });") + .append("</script>"); +*/ + + output.append("<link href=\"").append(themeBaseUri).append("all/content.css\" rel=\"stylesheet\" type=\"text/css\" />\n"); + } + + protected void bodyDecorator(NekoHtmlPageHandler page, StringBuilder sb) { + sb.append("<!-- Body of ").append(page.getDocument().getName()).append("-->"); + sb.append("<div class=\"o_cp_print_page\" style='clear:both; position:relative;page-break-after:always;'>\n"); + sb.append(page.getBody()); + sb.append("\n</div>"); + } + + private List<NekoHtmlPageHandler> composePrintPage(List<String> nodeIds) { + List<NekoHtmlPageHandler> pages = new ArrayList<NekoHtmlPageHandler>(); + + for(String nodeId:nodeIds) { + NekoHtmlPageHandler parsedPage = null; + TreeNode treeNode = ctm.getNodeById(nodeId); + String identifierRes = (String)treeNode.getUserObject(); + if(StringHelper.containsNonWhitespace(identifierRes)) { + VFSItem currentItem = rootDir.resolve(identifierRes); + if(currentItem instanceof VFSLeaf) { + String extension = FileUtils.getFileSuffix(currentItem.getName()); + if("htm".equalsIgnoreCase(extension) || "html".equalsIgnoreCase(extension) || "xhtml".equalsIgnoreCase(extension)) { + VFSLeaf currentLeaf = (VFSLeaf)currentItem; + parsedPage = parsePage(identifierRes, currentLeaf, treeNode); + } + } + } + if(parsedPage == null) { + parsedPage = new NekoHtmlPageHandler(treeNode, null, rootDir, baseUri); + } + pages.add(parsedPage); + } + return pages; + } + + private NekoHtmlPageHandler parsePage(String identifierRes, VFSLeaf document, TreeNode node) { + NekoHtmlPageHandler page = new NekoHtmlPageHandler(node, document, rootDir, baseUri); + int index = identifierRes.lastIndexOf('/'); + if(index > 0) { + String relativePath = identifierRes.substring(0, index+1); + page.setRelativePath(relativePath); + } + + try { + Page content = loadPageWithGuess(document); + if(g_encoding == null) { + g_encoding = content.getEncoding(); + } + + String rawContent; + if (content.isUseLoadedPageString()) { + rawContent = content.getPage(); + } else { + // found a new charset other than iso-8859-1, load string with proper encoding + rawContent = FileUtils.load(document.getInputStream(), content.getEncoding()); + } + + SAXParser parser = new SAXParser(); + parser.setContentHandler(page); + parser.parse(new InputSource(new StringReader(rawContent))); + return page; + } catch (SAXException e) { + log.error("", e); + return null; + } catch (IOException e) { + log.error("", e); + return null; + } catch (Exception e) { + log.error("", e); + return null; + } + } + + protected MediaResource deliverCssFile(VFSLeaf cssFile, HttpServletRequest request) { + Page page = loadPageWithGuess(cssFile); + String encoding = page.getEncoding(); + String content = page.getPage(); + + SACCSSHandler handler = new SACCSSHandler(cssFile, rootDir, baseUri); + try { + Parser parser = new Parser(); + parser.setDocumentHandler(handler); + parser.parseStyleSheet(new org.w3c.css.sac.InputSource(new StringReader(content))); + } catch (IOException ioe) { + log.error("", ioe); + return null; + } catch (ParseException pe) { + log.error("", pe); + return null; + } + + String cleanStyleSheet = handler.getCleanStylesheet(); + return prepareMediaResource(request, cleanStyleSheet, encoding, "text/css"); + } + + protected MediaResource deliverFile(HttpServletRequest httpRequest, String path) { + //if directory gets renamed root becomes null + if (rootDir == null) { + return new NotFoundMediaResource("directory not found"+path); + } + + VFSLeaf vfsLeaf = null; + VFSItem vfsItem = rootDir.resolve(path); + //only files are allowed, but somehow it happened that folders showed up here + if (vfsItem instanceof VFSLeaf) { + vfsLeaf = (VFSLeaf)vfsItem; + } else { + return new NotFoundMediaResource(path); + } + + MediaResource mr; + // check if path ends with .html, .htm or .xhtml. We do this by searching for "htm" + // and accept positions of this string at length-3 or length-4 + if (path.toLowerCase().lastIndexOf(FILE_SUFFIX_HTM) >= (path.length()-4)) { + // set the http content-type and the encoding + Page page = loadPageWithGuess(vfsLeaf); + g_encoding = page.getEncoding(); + if (page.isUseLoadedPageString()) { + mr = prepareMediaResource(httpRequest, page.getPage(), g_encoding, page.getContentType()); + } else { + // found a new charset other than iso-8859-1, load string with proper encoding + String content = FileUtils.load(vfsLeaf.getInputStream(), g_encoding); + mr = prepareMediaResource(httpRequest, content, g_encoding, page.getContentType()); + } + } else if (path.toLowerCase().lastIndexOf(FILE_SUFFIX_CSS) >= (path.length()-4)) { + // set the http content-type and the encoding + mr = deliverCssFile(vfsLeaf, httpRequest); + } else if (path.endsWith(FILE_SUFFIX_JS)) { // a javascript library + VFSMediaResource vmr = new VFSMediaResource(vfsLeaf); + // set the encoding; could be null if this page starts with .js file + // (not very common...). + // if we set no header here, apache sends the default encoding + // together with the mime-type, which is wrong. + // so we assume the .js file has the same encoding as the html file + // that loads the .js file + if (jsEncoding != null) vmr.setEncoding(jsEncoding); + else if (g_encoding != null) vmr.setEncoding(g_encoding); + mr = vmr; + } else { + // binary data: not .html, not .htm, not .js -> treated as is + mr = new VFSMediaResource(vfsLeaf); + } + + return mr; + } + + private Page loadPageWithGuess(VFSLeaf vfsPage) { + if(contentEncoding != null && isCharsetSupported(contentEncoding)) { + Page page = new Page(); + page.setExtension(FileUtils.getFileSuffix(vfsPage.getName())); + page.setEncoding(contentEncoding); + page.setUseLoadedPageString(true); + String content = FileUtils.load(vfsPage.getInputStream(), contentEncoding); + page.setContentType(guessContentType(page, content)); + page.setPage(content); + return page; + } + + Page page = new Page(); + page.setExtension(FileUtils.getFileSuffix(vfsPage.getName())); + page.setEncoding(DEFAULT_ENCODING); + String content = FileUtils.load(vfsPage.getInputStream(), DEFAULT_ENCODING); + page.setContentType(guessContentType(page, content)); + // <meta.*charset=([^"]*)" + + //extract only the charset attribute without the overhead of creating an htmlparser + boolean guessed = loadPageWithGuess(page, content, DEFAULT_ENCODING); + if(!guessed) { + //try opening it with utf-8 + String contentUnicode = FileUtils.load(vfsPage.getInputStream(), UNICODE_ENCODING); + guessed = loadPageWithGuess(page, contentUnicode, UNICODE_ENCODING); + if(!guessed) { + //take default + page.setPage(content); + page.setUseLoadedPageString(true); + } + } + return page; + } + + private boolean loadPageWithGuess(Page page, String content, String encoding) { + //default encoding for xhtml + if(XHTML_CONTENT_TYPE.equals(page.getContentType())) { + page.setEncoding("utf-8"); + } + + String guessedEncoding = guessEncoding(content); + if (guessedEncoding != null) { + // use found char set + //if longer than 50 the regexp did fail + if (isCharsetSupported(guessedEncoding)) { + page.setEncoding(guessedEncoding); + } else { + return false; + } + + // reuse already loaded page when page uses the default encoding + if (page.getEncoding().equalsIgnoreCase(encoding) || page.getEncoding().contains(encoding) + || page.getEncoding().toLowerCase().contains(encoding)) { + page.setUseLoadedPageString(true); + page.setPage(content); + } + return true; + } + return false; + } + + private String guessContentType(Page page, String content) { + String cType = null; + if(XHTML_EXTENSION.equals(page.getExtension())) { + Matcher dm = PATTERN_DOCTYPE.matcher(content); + if (dm.find()) { + String doctype = dm.group(1).toLowerCase(); + //default settings for XHTML-documents, should be taken if no <meta http-equiv="content-type" .../> is given + if (doctype.indexOf("xhtml") == 0 && doctype.indexOf("mathml") > 0) { + cType = XHTML_CONTENT_TYPE; + } + } + } + + Matcher cm = PATTERN_CONTTYPE.matcher(content); + if (cm.find()) { + //use found content-type + String contentType = cm.group(1); + String[] types=contentType.split(";"); + for (int i=0;i<types.length;i++) { + if (!(types[i].contains("charset"))) { + contentType=types[i].trim(); + break; + } + } + //if longer than 50 the regexp did fail + if (contentType.length() < 50) { + cType = contentType; + } + } + + if(cType == null) { + return DEFAULT_CONTENT_TYPE; + } + return cType; + } + + private String guessEncoding(String content) { + Matcher m = PATTERN_ENCTYPE.matcher(content); + if (m.find()) { + // use found char set + String htmlcharset = m.group(1); + //if longer than 50 the regexp did fail + if (htmlcharset.length() < 50 ) { + return htmlcharset; + } + } + + Matcher xmlDeclaration = PATTERN_XML_ENCTYPE.matcher(content); + if (xmlDeclaration.find()) { + // use found char set + String xmlcharset = xmlDeclaration.group(1); + //if longer than 50 the regexp did fail + if (xmlcharset.length() < 50 ) { + return xmlcharset; + } + } + + return null; + } + + private boolean isCharsetSupported(String enc) { + try { + return Charset.isSupported(enc); + } catch (IllegalCharsetNameException e) { + return false; + } + } + + private StringMediaResource prepareMediaResource(HttpServletRequest httpRequest, String page, String enc, String contentType) { + StringMediaResource smr = new StringMediaResource(); + if(XHTML_CONTENT_TYPE.equals(contentType)) { + //check if the application/xhtml+xml is supported (not supported by IEs) + //if not, replace the content type by text/html for compatibility + String accept = httpRequest.getHeader("Accept"); + if(accept == null || accept.indexOf(XHTML_CONTENT_TYPE) < 0) { + contentType = DEFAULT_CONTENT_TYPE; + } + } + + String mimetype = contentType + ";charset=" + StringHelper.check4xMacRoman(enc); + smr.setContentType(mimetype); + smr.setEncoding(enc); + //inject some javascript code to size iframe to proper height, but only when not a page with framesets + if (page.indexOf(TAG_FRAMESET) != -1 || page.indexOf(TAG_FRAMESET_UPPERC) != -1) { + //is frameset -> deliver unparsed + smr.setData(page); + } else { + smr.setData(page); + } + return smr; + } + + public class Page { + private String encoding; + private String contentType; + private String extension; + private String page; + private boolean useLoadedPageString = false; + + public String getExtension() { + return extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public boolean isUseLoadedPageString() { + return useLoadedPageString; + } + + public void setUseLoadedPageString(boolean useLoadedPageString) { + this.useLoadedPageString = useLoadedPageString; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/CPSelectPrintPagesController.java b/src/main/java/org/olat/modules/cp/CPSelectPrintPagesController.java new file mode 100644 index 0000000000000000000000000000000000000000..a6d462c4587f1a8dc7927c9af1a01a11d0ef1838 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/CPSelectPrintPagesController.java @@ -0,0 +1,217 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.modules.cp; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.tree.TreeNode; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.nodes.INode; + +/** + * + * Description:<br> + * Controller to select to list of nodes to print + * VCRP-14 + * + * <P> + * Initial Date: 9 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-14: print cp +public class CPSelectPrintPagesController extends FormBasicController { + + private final Map<String,MultipleSelectionElement> identToSelectionMap = new HashMap<String,MultipleSelectionElement>(); + private final List<MultipleSelectionElement> nodeSelections = new ArrayList<MultipleSelectionElement>(); + + private final CPManifestTreeModel ctm; + + private FormLink selectAll; + private FormLink deselectAll; + private FormSubmit submit; + + public CPSelectPrintPagesController(UserRequest ureq, WindowControl wControl, CPManifestTreeModel ctm) { + super(ureq, wControl, "cpprint"); + + this.ctm = ctm; + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("print.node.list.title"); + setFormDescription("print.node.list.desc"); + + if(formLayout instanceof FormLayoutContainer) { + FormLayoutContainer layoutContainer = (FormLayoutContainer)formLayout; + TreeNode rootNode = ctm.getRootNode(); + initTreeRec(0, rootNode, layoutContainer); + layoutContainer.contextPut("nodeSelections", nodeSelections); + } + + selectAll = uifactory.addFormLink("checkall", "form.checkall", null, formLayout, Link.LINK); + deselectAll = uifactory.addFormLink("uncheckall", "form.uncheckall", null, formLayout, Link.LINK); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("print-cancel", getTranslator()); + formLayout.add(buttonLayout); + buttonLayout.setRootForm(mainForm); + submit = uifactory.addFormSubmitButton("print-button", "print.node", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + } + + private void initTreeRec(int level, TreeNode node, FormLayoutContainer layoutcont) { + String[] cssClass = new String[]{"b_tree_l" + level}; + String[] singleKey = new String[]{node.getIdent()}; + String[] singleValue = new String[]{node.getTitle()}; + MultipleSelectionElement nodeSelection = uifactory.addCheckboxesVertical("print.node.list." + nodeSelections.size(), layoutcont, singleKey, singleValue, cssClass, 1); + nodeSelection.setLabel("print.node.list", null); + nodeSelection.setUserObject(new SelectNodeObject(node, level)); + nodeSelection.addActionListener(this, FormEvent.ONCLICK); + nodeSelection.select(node.getIdent(), true); + nodeSelections.add(nodeSelection); + identToSelectionMap.put(node.getIdent(), nodeSelection); + layoutcont.add(nodeSelection.getComponent().getComponentName(), nodeSelection); + + int numOfChildren = node.getChildCount(); + for(int i=0; i<numOfChildren; i++) { + initTreeRec(level + 1, (TreeNode)node.getChildAt(i), layoutcont); + } + } + + public List<String> getSelectedNodeIdents() { + if(nodeSelections == null || nodeSelections.isEmpty()) { + return Collections.emptyList(); + } + + List<String> selectedNodeIdents = new ArrayList<String>(); + for(MultipleSelectionElement nodeSelection:nodeSelections) { + if(nodeSelection.isMultiselect() &&nodeSelection.isSelected(0)) { + SelectNodeObject treeNode = (SelectNodeObject)nodeSelection.getUserObject(); + String ident = treeNode.getNode().getIdent(); + selectedNodeIdents.add(ident); + } + } + return selectedNodeIdents; + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(nodeSelections.contains(source)) { + MultipleSelectionElement nodeSelection = (MultipleSelectionElement)source; + if(nodeSelection.isMultiselect()) { + selectRec(nodeSelection, nodeSelection.isSelected(0)); + } + // check for at least one selected node + submit.setEnabled(false); + for(MultipleSelectionElement selection:nodeSelections) { + if (selection.isSelected(0)) { + submit.setEnabled(true); + break; + } + } + } else if (source == selectAll) { + for(MultipleSelectionElement nodeSelection:nodeSelections) { + if(nodeSelection.isMultiselect() && !nodeSelection.isSelected(0)) { + SelectNodeObject treeNode = (SelectNodeObject)nodeSelection.getUserObject(); + String ident = treeNode.getNode().getIdent(); + nodeSelection.select(ident, true); + } + } + submit.setEnabled(true); + } else if (source == deselectAll) { + for(MultipleSelectionElement nodeSelection:nodeSelections) { + if(nodeSelection.isMultiselect() && nodeSelection.isSelected(0)) { + SelectNodeObject treeNode = (SelectNodeObject)nodeSelection.getUserObject(); + String ident = treeNode.getNode().getIdent(); + nodeSelection.select(ident, false); + } + } + submit.setEnabled(false); + } else { + super.formInnerEvent(ureq, source, event); + } + } + + private void selectRec(MultipleSelectionElement nodeSelection, boolean select) { + SelectNodeObject userObject = (SelectNodeObject)nodeSelection.getUserObject(); + TreeNode node = userObject.getNode(); + if(nodeSelection.isMultiselect()) { + nodeSelection.select(node.getIdent(), select); + } + + for(int i=node.getChildCount(); i-->0; ) { + INode child = node.getChildAt(i); + String ident = child.getIdent(); + MultipleSelectionElement childNodeSelection = identToSelectionMap.get(ident); + selectRec(childNodeSelection, select); + } + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + public class SelectNodeObject { + private final int indentation; + private final TreeNode node; + + public SelectNodeObject(TreeNode node, int indentation) { + this.node = node; + this.indentation = indentation; + } + + public String getIndentation() { + return Integer.toString(indentation); + } + + public TreeNode getNode() { + return node; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/CPUIFactory.java b/src/main/java/org/olat/modules/cp/CPUIFactory.java index af701302066453300a046934d4662c6009f8e983..e140fef797dd7e62b367bd6acba41915e55a02cc 100644 --- a/src/main/java/org/olat/modules/cp/CPUIFactory.java +++ b/src/main/java/org/olat/modules/cp/CPUIFactory.java @@ -67,8 +67,8 @@ public class CPUIFactory { * @param initialUri can be NULL, will use first page then * @return a CPDisplayController */ - public CPDisplayController createContentOnlyCPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean activateFirstPage, String initialUri, OLATResourceable ores) { - return new CPDisplayController(ureq, wControl, rootContainer, false, activateFirstPage, initialUri, ores); + public CPDisplayController createContentOnlyCPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean activateFirstPage, boolean showNavigation, String initialUri, OLATResourceable ores) { + return new CPDisplayController(ureq, wControl, rootContainer, false, showNavigation, activateFirstPage, true, initialUri, ores); } /** @@ -110,7 +110,7 @@ public class CPUIFactory { * @return A main layout controller */ public MainLayout3ColumnsController createMainLayoutController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, String contentEncoding, String jsEncoding, boolean activateFirstPage, String initialUri, OLATResourceable ores) { - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, activateFirstPage, initialUri, ores); + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, activateFirstPage, true, initialUri, ores); cpCtr.setContentEncoding(contentEncoding); cpCtr.setJSEncoding(jsEncoding); MainLayout3ColumnsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, cpCtr.getMenuComponent(), null, cpCtr.getInitialComponent(), rootContainer.getName()); @@ -156,7 +156,7 @@ public class CPUIFactory { */ public OLATResourceableListeningWrapperController createMainLayoutResourceableListeningWrapperController(OLATResourceable res, UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean activateFirstPage, String initialUri) { //fxdiff BAKS-7 Resume function - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, activateFirstPage, initialUri, res); + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, activateFirstPage, true, initialUri, res); MainLayout3ColumnsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, cpCtr.getMenuComponent(), null, cpCtr.getInitialComponent(), rootContainer.getName()); layoutCtr.addDisposableChildController(cpCtr); return new OLATResourceableListeningWrapperController(ureq, wControl, res, layoutCtr, cpCtr, ureq.getIdentity()); @@ -197,7 +197,7 @@ public class CPUIFactory { * @return A main layout preview controller */ public LayoutMain3ColsPreviewController createMainLayoutPreviewController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu) { - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, null, null); + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, null, null); LayoutMain3ColsPreviewController layoutCtr = new LayoutMain3ColsPreviewController(ureq, wControl, cpCtr.getMenuComponent(), null, cpCtr.getInitialComponent(), rootContainer.getName()); layoutCtr.addDisposableChildController(cpCtr); // cascade disposing requests return layoutCtr; diff --git a/src/main/java/org/olat/modules/cp/NekoHtmlPageHandler.java b/src/main/java/org/olat/modules/cp/NekoHtmlPageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..dda99d05bf38dc307e765ff0546bb7c7ba9058a3 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/NekoHtmlPageHandler.java @@ -0,0 +1,215 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.modules.cp; + +import org.olat.core.gui.components.tree.TreeNode; +import org.olat.core.util.StringHelper; +import org.olat.core.util.WebappHelper; +import org.olat.core.util.nodes.INode; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * Description:<br> + * SAX handler for the NekoParser + * + * <P> + * Initial Date: 18 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-14: print cp +public class NekoHtmlPageHandler extends DefaultHandler { + private final StringBuilder header = new StringBuilder(); + private final StringBuilder body = new StringBuilder(); + + private final String HEAD = "HEAD"; + private final String BODY = "BODY"; + private final String SCRIPT = "SCRIPT"; + + private boolean pauseOutput; + private StringBuilder output; + private String relativePath; + + private final TreeNode node; + private final String baseUri; + private final VFSLeaf document; + private final VFSContainer rootContainer; + + public NekoHtmlPageHandler(TreeNode node, VFSLeaf document, VFSContainer rootDir, String baseUri) { + this.document = document; + this.rootContainer = rootDir; + this.baseUri = baseUri; + this.node = node; + } + + public boolean isEmpty() { + return document == null; + } + + public String getTitle() { + return node.getTitle(); + } + + public int getLevel() { + int count = 0; + INode currentNode = node; + while(currentNode != null) { + currentNode = currentNode.getParent(); + count++; + } + return count; + } + + public VFSLeaf getDocument() { + return document; + } + + public StringBuilder getHeader() { + return header; + } + + public StringBuilder getBody() { + return body; + } + + public String getRelativePath() { + return relativePath; + } + + public void setRelativePath(String relativePath) { + this.relativePath = relativePath; + } + + @Override + public final void startDocument() { + // + } + + @Override + public final void startElement(String uri, String localName, String qName, Attributes attributes) { + if (HEAD.equals(localName)) { + output = header; + } else if (BODY.equals(localName)) { + output = body; + } else if (SCRIPT.equals(localName)) { + pauseOutput = true; + } else if (output != null) { + pauseOutput = false; + output.append("<").append(localName); + int numOfAttributes = attributes.getLength(); + for(int i=0; i<numOfAttributes; i++) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i); + output.append(' ').append(attrName).append('='); + boolean useSingle = (attrValue.indexOf('"') > 0); + if(useSingle) { + output.append('\''); + } else { + output.append('"'); + } + + if(attrName.equalsIgnoreCase("href") || attrName.equalsIgnoreCase("src")) { + output.append(normalizeUri(attrValue)); + } else { + output.append(attrValue); + } + + if(useSingle) { + output.append('\''); + } else { + output.append('"'); + } + } + output.append(">"); + } + } + + private final String normalizeUri(String uri) { + if(uri.indexOf("://") > 0 || uri.startsWith("/")) { + return uri;//absolute link, nothing to do + } + + String contextPath = WebappHelper.getServletContextPath(); + if(uri.startsWith(contextPath)) { + return uri;//absolute within olat + } + + if(uri.startsWith("..")) { + + VFSContainer startDir; + if(relativePath == null) { + startDir = rootContainer; + } else { + startDir = (VFSContainer)rootContainer.resolve(relativePath); + } + + String tmpUri = uri; + VFSContainer tmpDir = startDir; + while(tmpUri.startsWith("../")) { + tmpDir = tmpDir.getParentContainer(); + tmpUri = tmpUri.substring(3); + } + + String diffPath = getRelativeResultingPath(tmpDir); + if(StringHelper.containsNonWhitespace(diffPath)) { + return diffPath + tmpUri; + } + return tmpUri; + } + if (relativePath != null) { + uri = relativePath + uri; + } + return baseUri + "/" + uri; + } + + private String getRelativeResultingPath(VFSContainer tmpDir) { + String diffPath = ""; + while(!tmpDir.isSame(rootContainer)) { + diffPath = tmpDir.getName() + "/" + diffPath; + tmpDir = tmpDir.getParentContainer(); + } + return diffPath; + } + + @Override + public final void characters(char[] ch, int start, int length) { + if(output != null && !pauseOutput) { + output.append(ch, start, length); + } + } + + @Override + public final void endElement(String uri, String localName, String qName) { + if(HEAD.equals(localName)) { + output = null; + } else if (BODY.equals(localName)) { + output = null; + } else if (SCRIPT.equals(localName)) { + pauseOutput = false; + } else if (output != null) { + output.append("</").append(localName).append(">"); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/SACCSSHandler.java b/src/main/java/org/olat/modules/cp/SACCSSHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..2213bd3e12462fcdd8d7ba2c236c91248b5eb305 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/SACCSSHandler.java @@ -0,0 +1,324 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.modules.cp; + +import org.olat.core.util.StringHelper; +import org.olat.core.util.WebappHelper; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.w3c.css.sac.CSSException; +import org.w3c.css.sac.DocumentHandler; +import org.w3c.css.sac.InputSource; +import org.w3c.css.sac.LexicalUnit; +import org.w3c.css.sac.SACMediaList; +import org.w3c.css.sac.Selector; +import org.w3c.css.sac.SelectorList; + +/** + * + * Description:<br> + * Rewrite relative path in the CSS. this is a document handler for + * a SAC Parser + * + * <P> + * Initial Date: 18 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff VCRP-14: print cp +public class SACCSSHandler implements DocumentHandler { + + private boolean isInline; + private boolean selectorOpen; + private StringBuilder styleSheet = new StringBuilder(); + + private final String relativePath; + private final String baseUri; + private final VFSContainer rootContainer; + + public SACCSSHandler(VFSLeaf document, VFSContainer rootContainer, String baseUri) { + this.baseUri = baseUri; + this.rootContainer = rootContainer; + relativePath = getRelativeResultingPath(document.getParentContainer()); + } + + public String getCleanStylesheet() { + // Always ensure results contain most recent generation of stylesheet + return styleSheet.toString(); + } + + @Override + public void comment(String comment) throws CSSException { + // + } + + @Override + public void startDocument(InputSource source) throws CSSException { + // no-op + } + + @Override + public void endDocument(InputSource source) throws CSSException { + // no-op + } + + @Override + public void importStyle(String uri, SACMediaList media, String defaultNamespaceURI) throws CSSException { + // + } + + @Override + public void startFontFace() throws CSSException { + // CSS2 Font Face declaration - ignore this for now + } + + @Override + public void endFontFace() throws CSSException { + // CSS2 Font Face declaration - ignore this for now + } + + @Override + public void startMedia(SACMediaList media) throws CSSException { + // CSS2 Media declaration - ignore this for now + } + + @Override + public void endMedia(SACMediaList media) throws CSSException { + // CSS2 Media declaration - ignore this for now + } + + @Override + public void startPage(String name, String pseudo_page) throws CSSException { + // CSS2 Page declaration - ignore this for now + } + + @Override + public void endPage(String name, String pseudo_page) throws CSSException { + // CSS2 Page declaration - ignore this for now + } + + @Override + public void startSelector(SelectorList selectors) throws CSSException { + // keep track of number of valid selectors from this rule + int selectorCount = 0; + + // check each selector + for (int i = 0; i < selectors.getLength(); i++) { + Selector selector = selectors.item(i); + if (selector != null) { + String selectorName = selector.toString(); + if (selectorCount > 0) { + styleSheet.append(','); + styleSheet.append(' '); + } + styleSheet.append(selectorName); + selectorCount++; + } + } + + // if and only if there were selectors that were valid, append + // appropriate open brace and set state to within selector + if (selectorCount > 0) { + styleSheet.append(' '); + styleSheet.append('{'); + styleSheet.append('\n'); + selectorOpen = true; + } + } + + @Override + public void endSelector(SelectorList selectors) throws CSSException { + // if we are in a state within a selector, close brace + if (selectorOpen) { + styleSheet.append('}'); + styleSheet.append('\n'); + } + + // reset state + selectorOpen = false; + } + + @Override + public void property(String name, LexicalUnit value, boolean important) throws CSSException { + if (!selectorOpen && !isInline) { + return; + } + + // validate the property + if (!isInline) { + styleSheet.append('\t'); + } + styleSheet.append(name); + styleSheet.append(':'); + + // append all values + while (value != null) { + styleSheet.append(' '); + styleSheet.append(lexicalValueToString(value)); + value = value.getNextLexicalUnit(); + } + styleSheet.append(';'); + if (!isInline) { + styleSheet.append('\n'); + } + } + + @Override + public void ignorableAtRule(String atRule) throws CSSException { + // this method is called when the parser hits an unrecognized + // @-rule. Like the page/media/font declarations, this is + // CSS2+ stuff + } + + @Override + public void namespaceDeclaration(String prefix, String uri) throws CSSException { + // CSS3 - Namespace declaration - ignore for now + } + + /** + * Converts the given lexical unit to a <code>String</code> + * representation. This method does not perform any validation - it is meant + * to be used in conjunction with the validator/logging methods. + * + * @param lu + * the lexical unit to convert + * @return a <code>String</code> representation of the given lexical unit + */ + public String lexicalValueToString(LexicalUnit lu) { + switch (lu.getLexicalUnitType()) { + case LexicalUnit.SAC_PERCENTAGE: + case LexicalUnit.SAC_DIMENSION: + case LexicalUnit.SAC_EM: + case LexicalUnit.SAC_EX: + case LexicalUnit.SAC_PIXEL: + case LexicalUnit.SAC_INCH: + case LexicalUnit.SAC_CENTIMETER: + case LexicalUnit.SAC_MILLIMETER: + case LexicalUnit.SAC_POINT: + case LexicalUnit.SAC_PICA: + case LexicalUnit.SAC_DEGREE: + case LexicalUnit.SAC_GRADIAN: + case LexicalUnit.SAC_RADIAN: + case LexicalUnit.SAC_MILLISECOND: + case LexicalUnit.SAC_SECOND: + case LexicalUnit.SAC_HERTZ: + case LexicalUnit.SAC_KILOHERTZ: + // these are all measurements + return lu.getFloatValue() + lu.getDimensionUnitText(); + case LexicalUnit.SAC_INTEGER: + // just a number + return String.valueOf(lu.getIntegerValue()); + case LexicalUnit.SAC_REAL: + // just a number + return String.valueOf(lu.getFloatValue()); + case LexicalUnit.SAC_STRING_VALUE: + case LexicalUnit.SAC_IDENT: + // just a string/identifier + String stringValue = lu.getStringValue(); + if(stringValue.indexOf(" ") != -1) + stringValue = "\""+stringValue+"\""; + return stringValue; + case LexicalUnit.SAC_URI: + // this is a URL + return "url(" + normalizeUri(lu.getStringValue()) + ")"; + case LexicalUnit.SAC_RGBCOLOR: + // this is a rgb encoded color + StringBuffer sb = new StringBuffer("rgb("); + LexicalUnit param = lu.getParameters(); + sb.append(param.getIntegerValue()); // R value + sb.append(','); + param = param.getNextLexicalUnit(); // comma + param = param.getNextLexicalUnit(); // G value + sb.append(param.getIntegerValue()); + sb.append(','); + param = param.getNextLexicalUnit(); // comma + param = param.getNextLexicalUnit(); // B value + sb.append(param.getIntegerValue()); + sb.append(')'); + + return sb.toString(); + case LexicalUnit.SAC_INHERIT: + // constant + return "inherit"; + case LexicalUnit.SAC_OPERATOR_COMMA: + return ","; + case LexicalUnit.SAC_ATTR: + case LexicalUnit.SAC_COUNTER_FUNCTION: + case LexicalUnit.SAC_COUNTERS_FUNCTION: + case LexicalUnit.SAC_FUNCTION: + case LexicalUnit.SAC_RECT_FUNCTION: + case LexicalUnit.SAC_SUB_EXPRESSION: + case LexicalUnit.SAC_UNICODERANGE: + default: + // these are properties that shouldn't be necessary for most run + // of the mill HTML/CSS + return null; + } + } + + private final String normalizeUri(String uri) { + if(uri.indexOf("://") > 0) { + return uri;//absolute link, nothing to do + } + + String contextPath = WebappHelper.getServletContextPath(); + if(uri.startsWith(contextPath)) { + return uri;//absolute within olat + } + + if(uri.startsWith("..")) { + + VFSContainer startDir; + if(relativePath == null) { + startDir = rootContainer; + } else { + startDir = (VFSContainer)rootContainer.resolve(relativePath); + } + + String tmpUri = uri; + VFSContainer tmpDir = startDir; + while(tmpUri.startsWith("../")) { + tmpDir = tmpDir.getParentContainer(); + tmpUri = tmpUri.substring(3); + } + + String diffPath = getRelativeResultingPath(tmpDir); + if(StringHelper.containsNonWhitespace(diffPath)) { + return diffPath + tmpUri; + } + return tmpUri; + } + if (relativePath != null) { + uri = relativePath + uri; + } + return baseUri + "/" + uri; + } + + private String getRelativeResultingPath(VFSItem tmpDir) { + String diffPath = ""; + while(!tmpDir.isSame(rootContainer)) { + diffPath = tmpDir.getName() + "/" + diffPath; + tmpDir = tmpDir.getParentContainer(); + } + return diffPath; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/_content/cpcontent.html b/src/main/java/org/olat/modules/cp/_content/cpcontent.html index b7a69065bc3652bf13b40331a0deb9b91e59c07c..0224037cc611e925258989d567b7682f0371dbc6 100644 --- a/src/main/java/org/olat/modules/cp/_content/cpcontent.html +++ b/src/main/java/org/olat/modules/cp/_content/cpcontent.html @@ -1,8 +1,20 @@ <div class="o_module_cp_wrapper b_clearfix"> - #if($r.available("search_input")) - <div id="o_local_fulltextsearch">$r.render("search_input")</div> - #end - + #if($r.available("search_input") || $r.available("previous") || $r.available("next") || $r.available("print")) + <div id="o_local_fulltextsearch"><div class="o_cp_navigation"> + #if($r.available("previous")) + $r.render("previous") + #end + #if($r.available("next")) + $r.render("next") + #end + #if($r.available("print")) + $r.render("print") + #end + #if($r.available("search_input")) + $r.render("search_input") + #end + </div></div> + #end <div class="b_floatscrollbox"> $r.render("cpContent") </div> diff --git a/src/main/java/org/olat/modules/cp/_content/cpprint.html b/src/main/java/org/olat/modules/cp/_content/cpprint.html new file mode 100644 index 0000000000000000000000000000000000000000..03ebc86b547fe2f1031d37b9d22dda3346fa4905 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/_content/cpprint.html @@ -0,0 +1,25 @@ +<div class="b_form #if ($off_css_class) $off_css_class #end b_clearfix"> +#if ($off_title) <fieldset><legend>$off_title</legend> #end +#if ($off_chelp_package) $r.contextHelpWithWrapper("$off_chelp_package","$off_chelp_page","$off_chelp_hover") #end +#if ($off_desc) <div class="b_form_desc">$off_desc</div> #end + + #foreach($nodeSelection in $nodeSelections) + <div style="margin-left:${nodeSelection.getUserObject().getIndentation()}0px; "> + $r.render($nodeSelection.getComponent().getComponentName()) + </div> + #end + + <br/> + <div class="b_togglecheck"> + <input type="checkbox" checked="checked" disabled="disabled" />$r.render("checkall") + <input type="checkbox" disabled="disabled" />$r.render("uncheckall") + </div> + + <div class="b_form_element_wrapper"> + <div class="b_form_element"> + $r.render("print-cancel") + </div> + </div> + +#if ($off_title) </fieldset> #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_de.properties index d9b380a8abb838c9043201beca5ba51dd66c7ef8..d81af75d872720b143c92430cb589b19c04d9e83 100644 --- a/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_de.properties @@ -1,2 +1,7 @@ #Mon Mar 02 09:54:04 CET 2009 -error.manifest.missing=Dieser CP-Lerninhalt ist ung\u00FCltig\: Fehlendes imsmanifest.xml. Mehr Informationen dazu finden Sie unter http\://www.imsglobal.org. +previous=Zur\u00FCck +print.node=Drucken +print.node.list=Seite +print.node.list.title=Druckkonfiguration +print.node.list.desc=W\u00E4hlen Sie eine oder mehrere Seiten f\u00FCr den Ausdruck aus +error.manifest.missing=Dieser CP-Lerninhalt ist ung\u00FCltig\: Fehlendes imsmanifest.xml. Mehr Informationen dazu finden Sie unter http\://www.imsglobal.org. \ No newline at end of file diff --git a/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_en.properties index 756861f63ac5ce2f3f768912b923b10beb3030af..bdeef23e04a3a725a1e7799439b9107ecb63fcfa 100644 --- a/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/cp/_i18n/LocalStrings_en.properties @@ -1,2 +1,7 @@ -#Mon Mar 02 09:54:18 CET 2009 +#Mon May 16 17:33:59 CEST 2011 error.manifest.missing=This CP learning content is invalid\: missing imsmanifest.xml. For more information see http\://www.imsglobal.org. +previous=Back +print.node=Print +print.node.list=Page +print.node.list.desc=Select one or multiple pages for printing +print.node.list.title=Printing configuration diff --git a/src/main/java/org/olat/modules/dialog/DialogElementsController.java b/src/main/java/org/olat/modules/dialog/DialogElementsController.java index 11105b39d96e35384e27d77e4696ac3c0c7178de..28690a11316f39292a93853177b6766c22cb2a04 100644 --- a/src/main/java/org/olat/modules/dialog/DialogElementsController.java +++ b/src/main/java/org/olat/modules/dialog/DialogElementsController.java @@ -27,6 +27,8 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import org.olat.core.commons.controllers.linkchooser.LinkChooserController; +import org.olat.core.commons.controllers.linkchooser.URLChoosenEvent; import org.olat.core.commons.modules.bc.FileUploadController; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.FolderEvent; @@ -53,13 +55,16 @@ import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.activity.CourseLoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; import org.olat.core.util.notifications.ContextualSubscriptionController; import org.olat.core.util.notifications.NotificationsManager; import org.olat.core.util.notifications.PublisherData; import org.olat.core.util.notifications.SubscriptionContext; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.Quota; +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.VFSMediaResource; import org.olat.core.util.vfs.filters.VFSLeafFilter; import org.olat.course.CourseFactory; @@ -100,6 +105,7 @@ public class DialogElementsController extends BasicController { private TableController tableCtr; private CourseNode courseNode; private FileUploadController fileUplCtr; + private LinkChooserController fileCopyCtr; private Panel dialogPanel; private ForumManager forumMgr; private DialogElement recentDialogElement, selectedElement; @@ -286,6 +292,50 @@ public class DialogElementsController extends BasicController { throw new OLATRuntimeException(DialogElementsController.class, "Error while adding new 'file discussion' element with filename: "+filename, e); } } + } else if (source == fileCopyCtr) { + if (event == Event.DONE_EVENT || event == Event.CANCELLED_EVENT) { + // reset recent element + recentDialogElement = null; + showOverviewTable(ureq, forumCallback); + } else if (event instanceof URLChoosenEvent) { + URLChoosenEvent choosenEvent = (URLChoosenEvent)event; + String fileUrl = choosenEvent.getURL(); + if(fileUrl.indexOf("://") < 0) { + //copy file + VFSContainer courseContainer = userCourseEnv.getCourseEnvironment().getCourseFolderContainer(); + VFSLeaf vl = (VFSLeaf) courseContainer.resolve(fileUrl); + OlatRootFolderImpl forumContainer = getForumContainer(recentDialogElement.getForumKey()); + VFSLeaf copyVl = forumContainer.createChildLeaf(vl.getName()); + if(copyVl == null) { + copyVl = (VFSLeaf)forumContainer.resolve(vl.getName()); + } + VFSManager.copyContent(vl, copyVl); + + // get size of file + String fileSize = StringHelper.formatMemory(copyVl.getSize()); + + DialogElement element = new DialogElement(); + element.setAuthor(recentDialogElement.getAuthor()); + element.setDate(new Date()); + element.setFilename(vl.getName()); + element.setForumKey(recentDialogElement.getForumKey()); + element.setFileSize(fileSize); + + // do logging + //ThreadLocalUserActivityLogger.log(CourseLoggingAction.DIALOG_ELEMENT_FILE_UPLOADED, getClass(), LoggingResourceable.wrapUploadFile(filename)); + + // inform subscription manager about new element + if (subsContext != null) { + NotificationsManager.getInstance().markPublisherNews(subsContext, ureq.getIdentity()); + } + //everything when well so save the property + dialogElmsMgr.addDialogElement(coursePropMgr, courseNode, element); + } + + //not supported + recentDialogElement = null; + showOverviewTable(ureq, forumCallback); + } } else if (source == confirmDeletionCtr) { if (DialogBoxUIFactory.isYesEvent(event)) { DialogCourseNode node = (DialogCourseNode) courseNode; @@ -339,6 +389,16 @@ public class DialogElementsController extends BasicController { recentDialogElement.setForumKey(forum.getKey()); recentDialogElement.setAuthor(ureq.getIdentity().getName()); dialogPanel.setContent(fileUplCtr.getInitialComponent()); + } else if (source == copyButton) { + Forum forum = forumMgr.addAForum(); + VFSContainer courseContainer = userCourseEnv.getCourseEnvironment().getCourseFolderContainer(); + fileCopyCtr = new MyLinkChooserController(ureq, getWindowControl(), courseContainer, null); + listenTo(fileCopyCtr); + + recentDialogElement = new DialogElement(); + recentDialogElement.setForumKey(forum.getKey()); + recentDialogElement.setAuthor(ureq.getIdentity().getName()); + dialogPanel.setContent(fileCopyCtr.getInitialComponent()); } } @@ -373,5 +433,23 @@ public class DialogElementsController extends BasicController { protected void doDispose() { // } + + private class MyLinkChooserController extends LinkChooserController { + public MyLinkChooserController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, String uploadRelPath) { + super(ureq, wControl, rootDir, uploadRelPath, null, "", null); + } + + @Override + //this is a hack to overwrite the package used by the BasicController + protected VelocityContainer createVelocityContainer(String page) { + setTranslator(Util.createPackageTranslator(LinkChooserController.class, getLocale())); + velocity_root = Util.getPackageVelocityRoot(LinkChooserController.class); + return super.createVelocityContainer(page); + } + @Override + public void event(UserRequest ureq, Controller source, Event event) { + fireEvent(ureq, event); + } + } } diff --git a/src/main/java/org/olat/modules/dialog/_content/dialog.html b/src/main/java/org/olat/modules/dialog/_content/dialog.html index 868db2a6f102f0eee0681de853f5a216374d6339..5614a22394833d5ac656100459686bb234c44902 100644 --- a/src/main/java/org/olat/modules/dialog/_content/dialog.html +++ b/src/main/java/org/olat/modules/dialog/_content/dialog.html @@ -4,6 +4,9 @@ #end #if ($security.mayOpenNewThread()) $r.render("dialog.upload.file") + #if ($r.available("dialog.copy.file")) + $r.render("dialog.copy.file") + #end #end </div> <hr /> diff --git a/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_de.properties index 13a1d49c52837fa28d939653f22abae82a579586..eb44c04c972fb30dd605ce910ed4672314a04bba 100644 --- a/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_de.properties @@ -2,6 +2,7 @@ dialog.selected.element=Diskussion zur Datei dialog.start=Anzeigen dialog.upload.file=Datei hochladen +dialog.copy.file=Datei kopieren element.already.deleted=Das gew\u00E4hle Element wurde in der Zwischenzeit von einem anderen Benutzer gel\u00F6scht. Bitte die Tabellenansicht neu laden. element.delete=Wollen Sie die Datei (<b>{0}</b>) inkl. der Diskussionsbeitr\u00E4ge l\u00F6schen? Die Daten werden automatisch in Ihrem pers\u00F6nlichen Ordner unter 'Archiv' abgelegt. notifications.entry=Neue Datei mit dem Namen\:{0} von {1} erstellt am {2} diff --git a/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_en.properties index 0a5dbc26fa9f18954fb5e0a50c3398426253bfe7..c1a2cd04d473382c187a92a536c716284ed2919c 100644 --- a/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/dialog/_i18n/LocalStrings_en.properties @@ -1,4 +1,5 @@ -#Wed Jan 26 19:07:16 CET 2011 +#Mon May 16 17:34:03 CEST 2011 +dialog.copy.file=Copy file dialog.selected.element=Dialog regarding file dialog.start=Show dialog.upload.file=Upload file diff --git a/src/main/java/org/olat/modules/fo/restapi/ForumCourseNodeWebService.java b/src/main/java/org/olat/modules/fo/restapi/ForumCourseNodeWebService.java index 5cec1e358109ee2a54b1b4b5f6b5a9ddf89813f4..7747abbcc4cf7e377129332cc9bfe01fa2218fd8 100644 --- a/src/main/java/org/olat/modules/fo/restapi/ForumCourseNodeWebService.java +++ b/src/main/java/org/olat/modules/fo/restapi/ForumCourseNodeWebService.java @@ -21,6 +21,7 @@ import javax.ws.rs.core.Response.Status; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.ICourse; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.FOCourseNode; @@ -31,7 +32,6 @@ import org.olat.modules.fo.ForumManager; import org.olat.modules.fo.Message; import org.olat.properties.Property; import org.olat.restapi.repository.course.AbstractCourseNodeWebService; -import org.olat.restapi.repository.course.AbstractCourseNodeWebService.CustomConfigDelegate; import org.olat.restapi.security.RestSecurityHelper; /** @@ -74,8 +74,10 @@ public class ForumCourseNodeWebService extends AbstractCourseNodeWebService { @FormParam("position") Integer position, @FormParam("shortTitle") @DefaultValue("undefined") String shortTitle, @FormParam("longTitle") @DefaultValue("undefined") String longTitle, @FormParam("objectives") @DefaultValue("undefined") String objectives, @FormParam("visibilityExpertRules") String visibilityExpertRules, @FormParam("accessExpertRules") String accessExpertRules, - @Context HttpServletRequest request) { - return attachForum(courseId, parentNodeId, position, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, request); + @FormParam("moderatorExpertRules") String moderatorExpertRules, @FormParam("posterExpertRules") String posterExpertRules, + @FormParam("readerExpertRules") String readerExpertRules, @Context HttpServletRequest request) { + return attachForum(courseId, parentNodeId, position, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, + moderatorExpertRules, posterExpertRules, readerExpertRules, request); } /** @@ -105,8 +107,9 @@ public class ForumCourseNodeWebService extends AbstractCourseNodeWebService { @QueryParam("position") Integer position, @QueryParam("shortTitle") @DefaultValue("undefined") String shortTitle, @QueryParam("longTitle") @DefaultValue("undefined") String longTitle, @QueryParam("objectives") @DefaultValue("undefined") String objectives, @QueryParam("visibilityExpertRules") String visibilityExpertRules, @QueryParam("accessExpertRules") String accessExpertRules, - @Context HttpServletRequest request) { - ForumCustomConfig config = new ForumCustomConfig(); + @QueryParam("moderatorExpertRules") String moderatorExpertRules, @QueryParam("posterExpertRules") String posterExpertRules, + @QueryParam("readerExpertRules") String readerExpertRules, @Context HttpServletRequest request) { + ForumCustomConfig config = new ForumCustomConfig(moderatorExpertRules, posterExpertRules, readerExpertRules); return attach(courseId, parentNodeId, "fo", position, shortTitle, longTitle, objectives, visibilityExpertRules, accessExpertRules, config, request); } @@ -260,24 +263,44 @@ public class ForumCourseNodeWebService extends AbstractCourseNodeWebService { return Response.ok(vo).build(); } - -} - -class ForumCustomConfig implements CustomConfigDelegate { - - @Override - public boolean isValid() { - return true; - } + //fxdiff: RESTAPI add special expert rules for forums + private class ForumCustomConfig implements CustomConfigDelegate { + + private final String preConditionModerator; + private final String preConditionPoster; + private final String preConditionReader; + + public ForumCustomConfig(String preConditionModerator, String preConditionPoster, String preConditionReader) { + this.preConditionModerator = preConditionModerator; + this.preConditionPoster = preConditionPoster; + this.preConditionReader = preConditionReader; + } + + @Override + public boolean isValid() { + return true; + } - @Override - public void configure(ICourse course, CourseNode newNode, ModuleConfiguration moduleConfig) { - // create the forum - ForumManager fom = ForumManager.getInstance(); - CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); - Forum forum = fom.addAForum(); - Long forumKey = forum.getKey(); - Property forumKeyProperty = cpm.createCourseNodePropertyInstance(newNode, null, null, FOCourseNode.FORUM_KEY, null, forumKey, null, null); - cpm.saveProperty(forumKeyProperty); + @Override + public void configure(ICourse course, CourseNode newNode, ModuleConfiguration moduleConfig) { + // create the forum + ForumManager fom = ForumManager.getInstance(); + CoursePropertyManager cpm = course.getCourseEnvironment().getCoursePropertyManager(); + Forum forum = fom.addAForum(); + Long forumKey = forum.getKey(); + Property forumKeyProperty = cpm.createCourseNodePropertyInstance(newNode, null, null, FOCourseNode.FORUM_KEY, null, forumKey, null, null); + cpm.saveProperty(forumKeyProperty); + + // special rules + if(StringHelper.containsNonWhitespace(preConditionModerator)) { + ((FOCourseNode)newNode).setPreConditionModerator(createExpertCondition("moderator", preConditionModerator)); + } + if(StringHelper.containsNonWhitespace(preConditionPoster)) { + ((FOCourseNode)newNode).setPreConditionPoster(createExpertCondition("poster", preConditionPoster)); + } + if(StringHelper.containsNonWhitespace(preConditionReader)) { + ((FOCourseNode)newNode).setPreConditionReader(createExpertCondition("reader", preConditionReader)); + } + } } -} +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/glossary/GlossaryEditSettingsController.java b/src/main/java/org/olat/modules/glossary/GlossaryEditSettingsController.java new file mode 100644 index 0000000000000000000000000000000000000000..5a93ca574e28f5bb3df8669f45feb9664d4af8c5 --- /dev/null +++ b/src/main/java/org/olat/modules/glossary/GlossaryEditSettingsController.java @@ -0,0 +1,108 @@ +/** +* 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) 1999-2008 at frentix GmbH, Switzerland, http://www.frentix.com +* <p> +*/ +package org.olat.modules.glossary; + +import java.util.Properties; + +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.commons.modules.glossary.GlossaryItemManager; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.resource.OLATResource; + +/** + * + * Description:<br> + * Allow to change the edit permission on the glossary only owners / all users + * + * <P> + * Initial Date: 15 mars 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class GlossaryEditSettingsController extends FormBasicController { + + private OLATResource olatresource; + private MultipleSelectionElement editByUserEnabled; + private OlatRootFolderImpl glossaryFolder; + + public GlossaryEditSettingsController(UserRequest ureq, WindowControl control, OLATResource resource) { + super(ureq, control); + this.olatresource = resource; + glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(olatresource); + + 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) { + setFormTitle("edit.title"); + setFormDescription("edit.intro"); + String[] regKeys = {"true"}; + String[] regValues = {""}; + + editByUserEnabled = uifactory.addCheckboxesHorizontal("edit.onoff", formLayout, regKeys, regValues, null); + editByUserEnabled.addActionListener(listener, FormEvent.ONCLICK); + + Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); + String configuredStatus = glossProps.getProperty(GlossaryItemManager.EDIT_USERS); + if (configuredStatus != null){ + editByUserEnabled.select(configuredStatus, true); + } + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#doDispose() + */ + @Override + protected void doDispose() { + // nothing + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest, org.olat.core.gui.components.form.flexible.FormItem, org.olat.core.gui.components.form.flexible.impl.FormEvent) + */ + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == editByUserEnabled){ + boolean editByUserChecked = editByUserEnabled.isSelected(0); + GlossaryItemManager gIM = GlossaryItemManager.getInstance(); + Properties glossProps = gIM.getGlossaryConfig(glossaryFolder); + glossProps.put(GlossaryItemManager.EDIT_USERS, String.valueOf(editByUserChecked)); + gIM.setGlossaryConfig(glossaryFolder, glossProps); + } + } + + /** + * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) + */ + @Override + protected void formOK(UserRequest ureq) { + // saved in innerEvent + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_de.properties index 204dbff6baa5759ad6396b524cda5032396ee912..52f6aeaa6266eea26f256d85c14d4c0473161f23 100644 --- a/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_de.properties @@ -1,3 +1,6 @@ register.title = Register konfigurieren register.intro = Das alphabetische Register ist für ein lateinisches Alphabet ausgelegt und beeinhaltet die Zeichen von A-Z. Für ein Glossar mit anderem Zeichensatz, sollte das Register ausgeschaltet werden. register.onoff = Register einschalten +edit.title = Schreibberechtigung +edit.intro = Standardmässig können nur die Besitzer dieses Glossars Beiträge erstellen und editieren. Die Auswahl "Schreibberechtigung für alle Benutzer" erlaubt es allen Systembenutzern neue Glossarbeiträge zu erstellen, ihre selbst erstellten Beiträge anschliessend zu ändern und auch wieder zu löschen. Besitzer können die Beiträge die von anderen Benutzern erstellt wurden jederzeit ändern oder löschen.<br /<br />Wenn diese Funktion eingeschaltet ist, so wird der jeweilige Urheber und die letzte Person die Änderungen vorgenommen hat neben dem Glossarbeitrag angezeigt. +edit.onoff = Schreibberechtigung für alle Benutzer zulassen diff --git a/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_en.properties index c69dc97c15b13dc8d168d931d8343ab1ea3845bc..706cc03fb91e7a5b133436b4e9acddd0c7ffef65 100644 --- a/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/glossary/_i18n/LocalStrings_en.properties @@ -1,4 +1,7 @@ -#Thu Jan 20 18:52:49 CET 2011 +#Mon May 16 16:00:54 CEST 2011 +edit.intro=By default only owners of this glossary can create glossary items. By selecting "Enable write permissions for all users" this glossary becomes a collaborative glossary that allows every user on this system to add new glossary items and to edit or delete their own items. Owner of the glossary can manage other users items as well at any time. <br /><br />If this feature is enabled, the author of each glossary item and the person who last modified the item is displayed next to the glossary term. +edit.onoff=Enable write permissions for all users +edit.title=Write permissions register.intro=This index is based on the Latin alphabet and therefore contains characters from A to Z. For a glossary based on another character set you should deactivate it. register.onoff=Activate index register.title=Configure index diff --git a/src/main/java/org/olat/modules/iq/IQComponentRenderer.java b/src/main/java/org/olat/modules/iq/IQComponentRenderer.java index ddc74ae11484b34106bc244b1a40d3b88b48f4a5..5b45b4979fbfcdd0f1db1ac3c6fd772a0fb3bb39 100644 --- a/src/main/java/org/olat/modules/iq/IQComponentRenderer.java +++ b/src/main/java/org/olat/modules/iq/IQComponentRenderer.java @@ -225,8 +225,6 @@ public class IQComponentRenderer implements ComponentRenderer { if (memoTx == null) { isDefaultMemo = true; memoTx = translator.translate("qti.memofield.text"); - } else { - memoTx = unescape(memoTx); } } @@ -643,75 +641,4 @@ public class IQComponentRenderer implements ComponentRenderer { public void renderBodyOnLoadJSFunctionCall(Renderer renderer, StringOutput sb, Component source, RenderingState rstate) { // } - - - - /* - * Created: 17 April 1997 - * Author: Bert Bos <bert@w3.org> - * - * unescape: http://www.w3.org/International/unescape.java - * - * Copyright © 1997 World Wide Web Consortium, (Massachusetts - * Institute of Technology, European Research Consortium for - * Informatics and Mathematics, Keio University). All Rights Reserved. - * This work is distributed under the W3C® Software License [1] in the - * hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. - * - * [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 - */ - - private static String unescape(String s) { - StringBuffer sbuf = new StringBuffer () ; - int l = s.length() ; - int ch = -1 ; - int b, sumb = 0; - for (int i = 0, more = -1 ; i < l ; i++) { - /* Get next byte b from URL segment s */ - switch (ch = s.charAt(i)) { - case '%': - ch = s.charAt (++i) ; - int hb = (Character.isDigit ((char) ch) - ? ch - '0' - : 10+Character.toLowerCase((char) ch) - 'a') & 0xF ; - ch = s.charAt (++i) ; - int lb = (Character.isDigit ((char) ch) - ? ch - '0' - : 10+Character.toLowerCase ((char) ch)-'a') & 0xF ; - b = (hb << 4) | lb ; - break ; - case '+': - b = ' ' ; - break ; - default: - b = ch ; - } - /* Decode byte b as UTF-8, sumb collects incomplete chars */ - if ((b & 0xc0) == 0x80) { // 10xxxxxx (continuation byte) - sumb = (sumb << 6) | (b & 0x3f) ; // Add 6 bits to sumb - if (--more == 0) sbuf.append((char) sumb) ; // Add char to sbuf - } else if ((b & 0x80) == 0x00) { // 0xxxxxxx (yields 7 bits) - sbuf.append((char) b) ; // Store in sbuf - } else if ((b & 0xe0) == 0xc0) { // 110xxxxx (yields 5 bits) - sumb = b & 0x1f; - more = 1; // Expect 1 more byte - } else if ((b & 0xf0) == 0xe0) { // 1110xxxx (yields 4 bits) - sumb = b & 0x0f; - more = 2; // Expect 2 more bytes - } else if ((b & 0xf8) == 0xf0) { // 11110xxx (yields 3 bits) - sumb = b & 0x07; - more = 3; // Expect 3 more bytes - } else if ((b & 0xfc) == 0xf8) { // 111110xx (yields 2 bits) - sumb = b & 0x03; - more = 4; // Expect 4 more bytes - } else /*if ((b & 0xfe) == 0xfc)*/ { // 1111110x (yields 1 bit) - sumb = b & 0x01; - more = 5; // Expect 5 more bytes - } - /* We don't test if the UTF-8 encoding is well-formed */ - } - return sbuf.toString() ; - } } diff --git a/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java b/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java index c247e63b0b4fab87f86497f74760e08adf16cd88..29a47aafb289278d0c1f8ac6801296f53e8c82e5 100644 --- a/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java +++ b/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java @@ -43,6 +43,7 @@ import org.olat.modules.scorm.manager.ScormManager; import org.olat.modules.scorm.server.beans.LMSDataFormBean; import org.olat.modules.scorm.server.beans.LMSDataHandler; import org.olat.modules.scorm.server.beans.LMSResultsBean; +import org.olat.modules.scorm.server.sequence.ItemSequence; /** * OLATApiAdapter implements the ApiAdapter Interface from the pfplms code which was initially @@ -206,6 +207,9 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm if (!isLaunched) return; isLaunched = false; if (commit) olatCommit(false); // Stupid "implicit commit" + // <OLATCE-289> + archiveScoData(); + // </OLATCE-289> } /** @@ -286,6 +290,17 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm } } } + // <OLATCE-289> + }else{ + //if "isACommit" is false, this is a lmsFinish and the apiCallback shall save the points an passed information + if (apiCallback != null) { + String rawScore = cmiData.get(SCORE_IDENT); + if (rawScore != null && !rawScore.equals("")) { + scoresProp.put(olatScoId, rawScore); + } + apiCallback.lmsFinish(olatScoId, scoresProp); + } + // </OLATCE-289> } lmsDataBean.setDataAsMap(cmiData); @@ -305,6 +320,28 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm return lmsBean.getItemID(); } + //<OLATCE-289> + /** + * Archive the current SCORM CMI Data, see ItemSequence.archiveScoData + * @return + */ + public boolean archiveScoData() { + boolean success = false; + try { + String itemId = scormManager.getSequence().findItemFromIndex(Integer.valueOf(olatScoId)); + ItemSequence item = scormManager.getSequence().getItem(itemId); + if (item != null) { + success = item.archiveScoData(); + } + } catch (Exception e) { + if(isLogDebugEnabled()) { + logWarn("Error at OLATApiAdapter.archiveScoData(): " + e.getMessage(), e); + } + } + return success; + } + // </OLATCE-289> + /** * @param itemId * @return true if the item is completed @@ -415,24 +452,34 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSCommit(java.lang.String) */ public final String LMSCommit (String s) { - String rv = core.LMSCommit(s); - if (rv.equals("false")) return rv; - rv = olatCommit(true); - say ("LMSCommit("+s+")="+rv); - return rv; + try { + String rv = core.LMSCommit(s); + if (rv.equals("false")) return rv; + rv = olatCommit(true); + say ("LMSCommit("+s+")="+rv); + return rv; + } catch (Exception e) { + logError("LMSCommit failed: " + s, e); + return "false"; + } } /** * @see ch.ethz.pfplms.scorm.api.ApiAdapterInterface#LMSFinish(java.lang.String) */ public final String LMSFinish (String s) { - String rv = core.LMSFinish(s); - say ("LMSFinish("+s+")="+rv); - say(" ----------------- "); - if (rv.equals("false")) return rv; - olatFinish(true); - core.reset(); - return rv; + try { + String rv = core.LMSFinish(s); + say ("LMSFinish("+s+")="+rv); + say(" ----------------- "); + if (rv.equals("false")) return rv; + olatFinish(true); + core.reset(); + return rv; + } catch (Exception e) { + logError("LMSFinish failed: " + s, e); + return "false"; + } } /** diff --git a/src/main/java/org/olat/modules/scorm/ScormAPICallback.java b/src/main/java/org/olat/modules/scorm/ScormAPICallback.java index 26b785cabab368da4f67e0745127e8877c42e176..680798d80befa60af73b0cd5ebcf8698d1c1ade1 100644 --- a/src/main/java/org/olat/modules/scorm/ScormAPICallback.java +++ b/src/main/java/org/olat/modules/scorm/ScormAPICallback.java @@ -39,5 +39,13 @@ public interface ScormAPICallback { * @param scoScores properties with key: scoId, value = rawscore */ void lmsCommit(String olatSahsId, Properties scoScores); + + // <OLATCE-289> + /** + * @param olatSahsId + * @param scoScores properties with key: scoId, value = rawscore + */ + void lmsFinish(String olatSahsId, Properties scoProps); + // </OLATCE-289> } diff --git a/src/main/java/org/olat/modules/scorm/ScormAPIandDisplayController.java b/src/main/java/org/olat/modules/scorm/ScormAPIandDisplayController.java index a41c05c03821b6bbffbd5d3f8945de4b8e42dfee..112bb2e9c1282fe849780a123e1e1c9325a83999 100644 --- a/src/main/java/org/olat/modules/scorm/ScormAPIandDisplayController.java +++ b/src/main/java/org/olat/modules/scorm/ScormAPIandDisplayController.java @@ -55,16 +55,12 @@ import org.olat.core.id.OLATResourceable; import org.olat.core.id.UserConstants; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; -import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LearningResourceLoggingAction; -import org.olat.core.logging.activity.StringResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.FileUtils; -import org.olat.core.util.Util; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.course.CourseModule; -import org.olat.util.logging.activity.LoggingResourceable; /** * Description:<br> @@ -111,7 +107,7 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { * @param credit_mode add null for the default value or "credit", "no-credit" */ ScormAPIandDisplayController(UserRequest ureq, WindowControl wControl, boolean showMenu, ScormAPICallback apiCallback, File cpRoot, String resourceId, String courseIdNodeId, String lesson_mode, - String credit_mode, boolean previewMode, boolean activate) { + String credit_mode, boolean previewMode, boolean activate, boolean fullWindow) { super(ureq, wControl); // logging-note: the callers of createScormAPIandDisplayController make sure they have the scorm resource added to the ThreadLocalUserActivityLogger @@ -186,18 +182,20 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { // bootId is the item the user left the sco last time or the first one String bootId = scormAdapter.getScormLastAccessedItemId(); // if bootId is -1 all course sco's are completed, we show a message - if (bootId.equals("-1")) { - iframectr.getInitialComponent().setVisible(false); - showInfo("scorm.course.completed"); - - } else { + // <OLATCE-289> +// if (bootId.equals("-1")) { +// iframectr.getInitialComponent().setVisible(false); +// showInfo("scorm.course.completed"); +// +// } else { scormAdapter.launchItem(bootId); TreeNode bootnode = treeModel.getNodeByScormItemId(bootId); iframectr.setCurrentURI((String) bootnode.getUserObject()); menuTree.setSelectedNodeId(bootnode.getIdent()); - } +// } + // </OLATCE-239> updateNextPreviousButtons(bootId); myContent.put("contentpackage", iframectr.getInitialComponent()); @@ -205,11 +203,13 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { if (activate) { if (previewMode) { LayoutMain3ColsPreviewController ctr = new LayoutMain3ColsPreviewController(ureq, getWindowControl(), (showMenu ? menuTree : null), null, myContent, "scorm" + resourceId); - ctr.activate(); + if(fullWindow) + ctr.setAsFullscreen(ureq); columnLayoutCtr = ctr; } else { LayoutMain3ColsBackController ctr = new LayoutMain3ColsBackController(ureq, getWindowControl(), (showMenu ? menuTree : null), null, myContent, "scorm" + resourceId); - ctr.activate(); + if(fullWindow) + ctr.setAsFullscreen(ureq); columnLayoutCtr = ctr; } } else { @@ -307,6 +307,12 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { iframectr.setJSEncoding(encoding); } + //fxdiff FXOLAT-116: SCORM improvements + public void close() { + if(columnLayoutCtr instanceof LayoutMain3ColsBackController) { + ((LayoutMain3ColsBackController)columnLayoutCtr).deactivate(); + } + } /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, @@ -326,6 +332,17 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { } + @Override + //fxdiff FXOLAT-116: SCORM improvements + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == columnLayoutCtr) { + if(event == Event.BACK_EVENT) { + fireEvent(ureq, Event.BACK_EVENT); + } + } + super.event(ureq, source, event); + } + private void switchToNextOrPreviousSco(Link link) { String nextScoId = (String)link.getUserObject(); GenericTreeNode tn = (GenericTreeNode) treeModel.getNodeByScormItemId(nextScoId); @@ -426,6 +443,16 @@ public class ScormAPIandDisplayController extends MainLayoutBasicController { previousScoBottom.setVisible(false); } } + + public void activate(){ + if (columnLayoutCtr instanceof LayoutMain3ColsPreviewController) { + LayoutMain3ColsPreviewController ctrl = (LayoutMain3ColsPreviewController) columnLayoutCtr; + ctrl.activate(); + } else if (columnLayoutCtr instanceof LayoutMain3ColsBackController){ + LayoutMain3ColsBackController ctrl = (LayoutMain3ColsBackController)columnLayoutCtr; + ctrl.activate(); + } + } private void updateMenuTreeIconsAndMessages() { menuTree.setDirty(true); diff --git a/src/main/java/org/olat/modules/scorm/ScormMainManager.java b/src/main/java/org/olat/modules/scorm/ScormMainManager.java index 755e68706c9132f598ec359b76e35daac611dffb..b550165630684d35a492b41223ef7c6bc9d79505 100644 --- a/src/main/java/org/olat/modules/scorm/ScormMainManager.java +++ b/src/main/java/org/olat/modules/scorm/ScormMainManager.java @@ -57,9 +57,10 @@ public class ScormMainManager extends BasicManager { * "review" * @param credit_mode add null for the default value or "credit", "no-credit" */ + //fxdiff FXOLAT-116: SCORM improvements public ScormAPIandDisplayController createScormAPIandDisplayController(UserRequest ureq, WindowControl wControl, boolean showMenu, ScormAPICallback apiCallback, - File cpRoot, String resourceId, String courseId, String lesson_mode, String credit_mode, boolean previewMode, boolean activate) { - return new ScormAPIandDisplayController(ureq, wControl, showMenu, apiCallback, cpRoot, resourceId, courseId, lesson_mode, credit_mode, previewMode, activate); + File cpRoot, String resourceId, String courseId, String lesson_mode, String credit_mode, boolean previewMode, boolean activate, boolean fullWindow) { + return new ScormAPIandDisplayController(ureq, wControl, showMenu, apiCallback, cpRoot, resourceId, courseId, lesson_mode, credit_mode, previewMode, activate, fullWindow); } } diff --git a/src/main/java/org/olat/modules/scorm/_static/js/scormApiAdapter.js b/src/main/java/org/olat/modules/scorm/_static/js/scormApiAdapter.js index 405643d42f489acb550baea02def9ef4a276c948..41d7a420922a594e8c3fc8cbc2dd2eed3a26de0c 100644 --- a/src/main/java/org/olat/modules/scorm/_static/js/scormApiAdapter.js +++ b/src/main/java/org/olat/modules/scorm/_static/js/scormApiAdapter.js @@ -119,7 +119,7 @@ function loadHTMLDoc(url,apiCall, param1, param2) { if (req.status == 200) { rteResponseText = req.responseText; scormRTEresponse = rteResponseText.substring(rteResponseText.indexOf("<p>")+3,rteResponseText.indexOf("</p>")); - dump(scormRTEresponse); + if (debug) dump(scormRTEresponse); } else { if (debug) dump("There was a problem retrieving the XMLHttpRequest data:\n"+ req.statusText+"\n"); } } }; diff --git a/src/main/java/org/olat/modules/scorm/archiver/ScormArchiveWizardController.java b/src/main/java/org/olat/modules/scorm/archiver/ScormArchiveWizardController.java index 608a4d593c65c6dc865519e21901a535065670e3..de37977930a93516785102ed088c374e95560c4c 100644 --- a/src/main/java/org/olat/modules/scorm/archiver/ScormArchiveWizardController.java +++ b/src/main/java/org/olat/modules/scorm/archiver/ScormArchiveWizardController.java @@ -43,6 +43,7 @@ import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.wizard.WizardController; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.MimedFileMediaResource; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.course.CourseFactory; import org.olat.course.ICourse; @@ -146,10 +147,8 @@ public class ScormArchiveWizardController extends BasicController { showFileButton = LinkFactory.createButton("showfile", finishedVC, this); finishedVC.contextPut("nodetitle", node.getShortTitle()); - boolean hasResults = ScormExportManager.getInstance().hasResults(course.getCourseEnvironment(), node, getTranslator()); - if(hasResults) { - doExport(ureq, (ScormCourseNode)node); - + targetFileName = doExport(ureq, (ScormCourseNode)node); + if(StringHelper.containsNonWhitespace(targetFileName)) { finishedVC.contextPut("filename", targetFileName); wc.setWizardTitle(getTranslator().translate("wizard.finished.title")); wc.setNextWizardStep(getTranslator().translate("wizard.finished.howto"), finishedVC); @@ -171,13 +170,13 @@ public class ScormArchiveWizardController extends BasicController { } } - private void doExport(UserRequest ureq, ScormCourseNode node) { + private String doExport(UserRequest ureq, ScormCourseNode node) { ICourse course = CourseFactory.loadCourse(courseId); exportDir = CourseFactory.getOrCreateDataExportDirectory(ureq.getIdentity(), course.getCourseTitle()); UserManager um = UserManager.getInstance(); charset = um.getUserCharset(ureq.getIdentity()); ScormExportManager sreManager = ScormExportManager.getInstance(); - targetFileName = sreManager.exportResults(course.getCourseEnvironment(), node, getTranslator(), exportDir, charset); + return sreManager.exportResults(course.getCourseEnvironment(), node, getTranslator(), exportDir, charset); } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/scorm/archiver/ScormExportManager.java b/src/main/java/org/olat/modules/scorm/archiver/ScormExportManager.java index 8024aa82cc4b3cc855effe024e45e232b1b9aff9..777ad54de907ec140bd8fbf8639c31f83e4b7c2e 100644 --- a/src/main/java/org/olat/modules/scorm/archiver/ScormExportManager.java +++ b/src/main/java/org/olat/modules/scorm/archiver/ScormExportManager.java @@ -23,6 +23,8 @@ package org.olat.modules.scorm.archiver; import java.io.File; import java.util.List; +import org.olat.basesecurity.IdentityShort; +import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; @@ -120,6 +122,9 @@ public class ScormExportManager extends BasicManager { VFSContainer scormRoot = ScormDirectoryHelper.getScormRootFolder(); List<Identity> users = ScoreAccountingHelper.loadUsers(courseEnv); + //fxdiff: FXOLAT-249 prevent connection timeout if collecting data take a long time + DBFactory.getInstance().commitAndCloseSession(); + for (Identity identity : users) { String username = identity.getName(); VFSItem userFolder = scormRoot.resolve(username); diff --git a/src/main/java/org/olat/modules/scorm/assessment/ScormAssessmentManager.java b/src/main/java/org/olat/modules/scorm/assessment/ScormAssessmentManager.java index 837a5b036ea238e789bc325b2421a46ed266e9a9..6e3a09727e786b8de8b8eaa36b3928416a12744b 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/ScormAssessmentManager.java +++ b/src/main/java/org/olat/modules/scorm/assessment/ScormAssessmentManager.java @@ -20,14 +20,19 @@ package org.olat.modules.scorm.assessment; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.manager.BasicManager; import org.olat.core.util.vfs.LocalFileImpl; +import org.olat.core.util.vfs.VFSConstants; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.filters.VFSItemFilter; @@ -74,6 +79,75 @@ public class ScormAssessmentManager extends BasicManager { return null; } + //fxdiff FXOLAT-108: reset SCORM test + public boolean deleteResults(String username, CourseEnvironment courseEnv, ScormCourseNode node) { + VFSContainer scoDirectory = ScormDirectoryHelper.getScoDirectory(username, courseEnv, node); + if(scoDirectory == null) return true; //nothing to reset -> ok + + return (scoDirectory.delete() == VFSConstants.YES); + } + + //<OLATCE-289> + /** + * Method to get a List of cmi datas for every xml file in the users directory. + * They are ordered in a Map with the lastModifiedDate of the file as Key. + * @param username + * @param courseEnv + * @param node + * @return + */ + public Map<Date, List<CmiData>> visitScoDatasMultiResults(String username, CourseEnvironment courseEnv, ScormCourseNode node) { + Map<Date, List<CmiData>> cmiDataObjects = new HashMap<Date, List<CmiData>>(); + VFSContainer scoContainer = ScormDirectoryHelper.getScoDirectory(username, courseEnv, node); + if(scoContainer == null) { + return null; + } + + Calendar cal = Calendar.getInstance(); + List<VFSItem> contents = scoContainer.getItems(new XMLFilter()); + for(VFSItem file:contents) { + List<CmiData> item = collectData(file); + if (item != null) { + //modified date + cal.setTimeInMillis(file.getLastModified()); + Collections.sort(item, new CmiDataComparator()); + cmiDataObjects.put(cal.getTime(), item); + } + } + + return cmiDataObjects; + } + + /** + * Collects the cmi data of the given Scorm-file. + * @param scoFile + * @return + */ + private List<CmiData> collectData(VFSItem scoFile) { + List<CmiData> datas = new ArrayList<CmiData>(); + ScoDocument document = new ScoDocument(null); + try { + if(scoFile instanceof LocalFileImpl) { + document.loadDocument(((LocalFileImpl)scoFile).getBasefile()); + } + else { + logger.warn("Cannot use this type of VSFItem to load a SCO Datamodel: " + scoFile.getClass().getName(), null); + return null; + } + + String fileName = scoFile.getName(); + String itemId = fileName.substring(0, fileName.length() - 4); + String[][] scoModel = document.getScoModel(); + for(String[] line:scoModel) { + datas.add(new CmiData(itemId, line[0], line[1])); + } + } catch (Exception e) { + logger.error("Cannot load a SCO Datamodel", e); + } + return datas; + } + // </OLATCE-289> + /** * Return all the datas in the sco datamodels of a SCORM course * @param username diff --git a/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java b/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java index ab28914bfcabbffc4ecc0fa67eebf74fab3281d0..d4fc7479728b884474ca4bf19ba04caa10d009b1 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java +++ b/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java @@ -20,15 +20,17 @@ */ package org.olat.modules.scorm.assessment; -import java.text.DateFormat; -import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.table.BaseTableDataModelWithoutFilter; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.StaticColumnDescriptor; @@ -42,13 +44,16 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.gui.translator.Translator; +import org.olat.core.id.User; +import org.olat.core.id.UserConstants; import org.olat.core.util.StringHelper; import org.olat.course.nodes.ScormCourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.scorm.server.servermodels.ScoUtils; -import org.olat.modules.scorm.server.servermodels.SequencerModel; /** * @@ -69,13 +74,13 @@ public class ScormResultDetailsController extends BasicController { private TableController summaryTableCtr; private TableController cmiTableCtr; private CloseableModalController cmc; + private Link resetButton; + private DialogBoxController resetConfirmationBox; private final ScormCourseNode node; private final UserCourseEnvironment userCourseEnvironment; - - private List<CmiData> rawDatas; - private SequencerModel sequencerModel; - + + public ScormResultDetailsController(UserRequest ureq, WindowControl wControl, ScormCourseNode node, UserCourseEnvironment userCourseEnvironment) { super(ureq, wControl); @@ -99,63 +104,19 @@ public class ScormResultDetailsController extends BasicController { CourseEnvironment courseEnv = userCourseEnvironment.getCourseEnvironment(); String username = userCourseEnvironment.getIdentityEnvironment().getIdentity().getName(); - rawDatas = ScormAssessmentManager.getInstance().visitScoDatas(username, courseEnv, node); - sequencerModel = ScormAssessmentManager.getInstance().getSequencerModel(username, courseEnv, node); - - summaryTableCtr.setTableDataModel(getSummaryTableDataModel(ureq, rawDatas, sequencerModel)); + // <OLATCE-289> + Map<Date, List<CmiData>> rawDatas = ScormAssessmentManager.getInstance().visitScoDatasMultiResults(username, courseEnv, node); + summaryTableCtr.setTableDataModel(new SummaryTableDataModelMultiResults(rawDatas)); + // </OLATCE-289> listenTo(summaryTableCtr); main.put("summary", summaryTableCtr.getInitialComponent()); + //fxdiff FXOLAT-108: reset SCORM test + resetButton = LinkFactory.createButton("reset", main, this); + main.put("resetButton", resetButton); putInitialPanel(main); } - - protected TableDataModel getSummaryTableDataModel(UserRequest ureq, List<CmiData> datas, SequencerModel sequenceModel) { - SummaryTableDataModel model = new SummaryTableDataModel(); - - double score = 0; - String totalTime = null; - for(CmiData data:datas) { - String key = data.getKey(); - if(CMI_RAW_SCORE.equals(key)) { - String value = data.getValue(); - if(StringHelper.containsNonWhitespace(value)) { - try { - score += Double.parseDouble(value); - } catch (NumberFormatException e) { - //fail silently - } - } - } - else if(CMI_TOTAL_TIME.equals(key)) { - String value = data.getValue(); - if(StringHelper.containsNonWhitespace(value)) { - if(totalTime == null) { - totalTime = value; - } - else { - totalTime = ScoUtils.addTimes(totalTime, value); - } - } - } - } - model.setScore(Double.toString(score)); - model.setTotalTime(totalTime); - - String modifiedDateMs = sequenceModel == null ? "" : sequenceModel.getManifestModifiedDate(); - if(StringHelper.containsNonWhitespace(modifiedDateMs)) { - long timestamp = Long.parseLong(modifiedDateMs); - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(timestamp); - Date lastModificationDate = cal.getTime(); - - DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, ureq.getLocale()); - String formattedDate = format.format(lastModificationDate); - model.setLastModificationDate(formattedDate); - } - - return model; - } @Override protected void doDispose() { @@ -164,7 +125,14 @@ public class ScormResultDetailsController extends BasicController { @Override protected void event(UserRequest ureq, Component source, Event event) { - // + //fxdiff FXOLAT-108: reset SCORM test + if(source == resetButton) { + String title = translate("reset.title"); + User user = userCourseEnvironment.getIdentityEnvironment().getIdentity().getUser(); + String name = user.getProperty(UserConstants.FIRSTNAME, null) + " " + user.getProperty(UserConstants.LASTNAME, null); + String text = translate("reset.text", new String[]{name}); + resetConfirmationBox = activateOkCancelDialog(ureq, title, text, resetConfirmationBox); + } } @Override @@ -172,10 +140,6 @@ public class ScormResultDetailsController extends BasicController { if (source == summaryTableCtr) { TableEvent tEvent = (TableEvent)event; if (tEvent.getActionId().equals("sel")) { - - - - TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setPreferencesOffered(true, "scormAssessmentDetails"); @@ -188,7 +152,11 @@ public class ScormResultDetailsController extends BasicController { cmiTableCtr.addColumnDescriptor(new DefaultColumnDescriptor("cmis.column.header.key", 2, null, ureq.getLocale())); cmiTableCtr.addColumnDescriptor(new DefaultColumnDescriptor("cmis.column.header.value", 3, null, ureq.getLocale())); - cmiTableCtr.setTableDataModel(new CmiTableDataModel(getTranslator(), rawDatas)); + // <BPS-252> BPS-252_3 + int rowId = tEvent.getRowId(); + List<CmiData> data = ((SummaryTableDataModelMultiResults)summaryTableCtr.getTableDataModel()).getObject(rowId); + cmiTableCtr.setTableDataModel(new CmiTableDataModel(getTranslator(), data)); + // </BPS-252> BPS-252_3 removeAsListenerAndDispose(cmc); cmc = new CloseableModalController(getWindowControl(), translate("close"), cmiTableCtr.getInitialComponent()); @@ -196,56 +164,14 @@ public class ScormResultDetailsController extends BasicController { cmc.activate(); } - } - } - - public class SummaryTableDataModel extends BaseTableDataModelWithoutFilter implements TableDataModel { - private String score; - private String totalTime; - private String lastModificationDate; - - public SummaryTableDataModel() { - // - } - - public String getScore() { - return score; - } - - public void setScore(String score) { - this.score = score; - } - - public String getTotalTime() { - return totalTime; - } - - public void setTotalTime(String totalTime) { - this.totalTime = totalTime; - } - - public String getLastModificationDate() { - return lastModificationDate; - } - - public void setLastModificationDate(String lastModificationDate) { - this.lastModificationDate = lastModificationDate; - } - - public int getColumnCount() { - return 3; - } - - public int getRowCount() { - return 1; - } - - public Object getValueAt(int row, int col) { - switch(col) { - case 0: return lastModificationDate; - case 1: return totalTime; - case 2: return score; - default: return "ERROR"; + //fxdiff FXOLAT-108: reset SCORM test + } else if ( source == resetConfirmationBox) { + if (DialogBoxUIFactory.isOkEvent(event)) { + //delete scorm + String username = userCourseEnvironment.getIdentityEnvironment().getIdentity().getName(); + CourseEnvironment courseEnv = userCourseEnvironment.getCourseEnvironment(); + ScormAssessmentManager.getInstance().deleteResults(username, courseEnv, node); + fireEvent(ureq, Event.CHANGED_EVENT); } } } @@ -307,4 +233,106 @@ public class ScormResultDetailsController extends BasicController { } } } + + // <OLATCE-289> + /** + * Description:<br> + * A TableDataModel for multi scorm results files. + * + * <P> + * Initial Date: 07.01.2010 <br> + * @author thomasw + */ + public class SummaryTableDataModelMultiResults implements TableDataModel { + + private final Map<Date, List<CmiData>> objects; + + /** + * Array of Keys of the Object-Map. The Key is at the same time the + * String representation of the last modified date. + */ + private Date[] objectKeys; + + public SummaryTableDataModelMultiResults(Map<Date, List<CmiData>> datas) { + objects = datas; + if(objects != null) { + objectKeys = objects.keySet().toArray(new Date[objects.size()]); + } + } + + public int getColumnCount() { + return 3; + } + + public int getRowCount() { + return objects == null ? 0 : objects.size(); + } + + public Object getValueAt(int row, int col) { + Date dateKey = objectKeys[row]; + List<CmiData> cmiObject = objects.get(dateKey); + String[] result = calcTimeAndScore(cmiObject); + switch (col) { + case 0: + return dateKey; + case 1: + return result[0]; + case 2: + return result[1]; + default: return "ERROR"; + } + } + + private String[] calcTimeAndScore(List<CmiData> cmiObject) { + double score = 0; + String totalTime = null; + for(CmiData data:cmiObject) { + String key = data.getKey(); + if(CMI_RAW_SCORE.equals(key)) { + String value = data.getValue(); + if(StringHelper.containsNonWhitespace(value)) { + try { + score += Double.parseDouble(value); + } catch (NumberFormatException e) { + //fail silently + } + } + } + else if(CMI_TOTAL_TIME.equals(key)) { + String value = data.getValue(); + if(StringHelper.containsNonWhitespace(value)) { + if(totalTime == null) { + totalTime = value; + } + else { + totalTime = ScoUtils.addTimes(totalTime, value); + } + } + } + } + String[] result = new String[2]; + result[0] = totalTime; + result[1] = "" + score; + return result; + } + + @Override + public Object createCopyWithEmptyList() { + return new SummaryTableDataModelMultiResults(new HashMap<Date, List<CmiData>>()); + } + + @Override + public List<CmiData> getObject(int row) { + Date dateKey = objectKeys[row]; + List<CmiData> cmiObject = objects.get(dateKey); + return cmiObject; + } + + @Override + @SuppressWarnings({"unused","rawtypes"}) + public void setObjects(List objects) { + // + } + } + // </OLATCE-289> } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/scorm/assessment/_content/scores.html b/src/main/java/org/olat/modules/scorm/assessment/_content/scores.html index 5edc10af7e96ca4def6ecb3231ecb7fc376a2a5a..5cd98196f2591438b0f1f167d1df8a4a8206c848 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/_content/scores.html +++ b/src/main/java/org/olat/modules/scorm/assessment/_content/scores.html @@ -1,3 +1,4 @@ <p> $r.render("summary") -</p> + $r.render("resetButton") +</p> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_de.properties index 4cbd2dcadad75dad9753cba9031bc3a85fd96e66..4ad4081b9de55c4083b18e8556ad34ca9d63027e 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_de.properties @@ -45,3 +45,6 @@ summary.column.header.assesspoints=Punkte summary.column.header.date=Datum summary.column.header.details=Details summary.column.header.duration=Dauer +reset=SCORM Resultaten zur\u00FCcksetzen +reset.title=SCORM Resultate zur\u00FCcksetzen +reset.text=Wollen Sie die SCORM Resultate von <b>{0}</b> wirklich zur\u00FCcksetzen? Dies beinhaltet alle Bewegungsdaten dieses SCORM Moduls inklusive allfälliger Testdaten. Als abgeschlossen markierte SCORM Module können so erneut aufgerufen und durchgeführt werden. Die beim letzten Versuch übertragenen Punkte werden nicht verändert. diff --git a/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_en.properties index a80e405162e8b36558799bb658b7647d8cc238d1..7e5b592cfb1d9970875f5302c2407990f8c71905 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/scorm/assessment/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Sat Jan 22 13:54:05 CET 2011 +#Thu May 26 09:55:38 CEST 2011 cmi.comments=Comment cmi.comments_from_lms=Comment LMS cmi.core.credit=Credit @@ -42,6 +42,9 @@ cmis.column.header.itemId=Elements cmis.column.header.key=Key cmis.column.header.translatedKey=Question cmis.column.header.value=Value +reset=Reset SCORM results +reset.text=Do you really want to reset the SCORM results of <b>{0}</b>? This will delete all dynamic data including possible test data generated by this user. SCORM modules that are marked as completed can then be started again. The score transmitted by the users last attempts will not be modified by this operation. +reset.title=Reset SCORM results summary.column.header.assesspoints=Score summary.column.header.date=Date summary.column.header.details=Details diff --git a/src/main/java/org/olat/modules/scorm/server/beans/LMSDataHandler.java b/src/main/java/org/olat/modules/scorm/server/beans/LMSDataHandler.java index 8c6131b4f301800a3614ff31a1c54c83a99cdaef..302be067942c7b53f7dd90865474db0fb8bdcd15 100644 --- a/src/main/java/org/olat/modules/scorm/server/beans/LMSDataHandler.java +++ b/src/main/java/org/olat/modules/scorm/server/beans/LMSDataHandler.java @@ -174,6 +174,12 @@ public class LMSDataHandler { * @return LMSResultsBean */ public LMSResultsBean getCMIData(String itemIndex) { + // <OLATCE-289> + //ignore completed state -> ability to launch the scorm depends on the number of attempts + if (itemIndex.equals(Integer.toString(SequenceManager.COURSE_COMPLETED_VALUE))) { + itemIndex = "0"; + } + // </OLATCE-289> // HAS THIS COURSE BEEN COMPLETED if (itemIndex.equals(Integer.toString(SequenceManager.COURSE_COMPLETED_VALUE))) { isCourseCompleted = true; @@ -183,16 +189,18 @@ public class LMSDataHandler { else { itemId = findItemFromIndex(Integer.parseInt(itemIndex)); // has this particular item been completed? - if (hasItemBeenCompleted(itemId)) { - isItemCompleted = true; - } + // <OLATCE-289> + //if (hasItemBeenCompleted(itemId)) { + // isItemCompleted = true; + //} // does this item have prerequisites? - else if (!checkItemsPrerequisites(itemId)) { - hasPrerequisites = true; - generatePrereqBean(); - } + //else if (!checkItemsPrerequisites(itemId)) { + // hasPrerequisites = true; + // generatePrereqBean(); + //} // WE CAN LAUNCH THIS ITEM - else { + //else { + // </OLATCE-289> // if this is a sco if (isItemSco(itemId)) { // load this scos model @@ -209,7 +217,7 @@ public class LMSDataHandler { isSco = false; isUpdating = false; } - } + //} } return new LMSResultsBean(itemIndex, Boolean.toString(isSco), cmiStrings, Boolean.toString(isUpdating), getPreReqStrings(), Boolean .toString(isItemCompleted), Boolean.toString(isCourseCompleted), Boolean.toString(hasPrerequisites)); diff --git a/src/main/java/org/olat/modules/scorm/server/sequence/ItemSequence.java b/src/main/java/org/olat/modules/scorm/server/sequence/ItemSequence.java index 882f24757b528231115d48df11854a7dee21e13d..379cbb40c467c431d76c2e25ae9e282a03ffeb7b 100644 --- a/src/main/java/org/olat/modules/scorm/server/sequence/ItemSequence.java +++ b/src/main/java/org/olat/modules/scorm/server/sequence/ItemSequence.java @@ -67,9 +67,17 @@ package org.olat.modules.scorm.server.sequence; */ +import org.olat.core.util.FileUtils; +import org.olat.core.util.vfs.LocalFileImpl; import org.olat.modules.scorm.ISettingsHandler; import org.olat.modules.scorm.server.servermodels.ScoDocument; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + import uk.ac.reload.moonunit.contentpackaging.SCORM12_Core; /** * A class used to house a single item from a scorm package. It should @@ -168,6 +176,38 @@ public class ItemSequence { public void updateClientModel(String[][] entries){ _scoDataModel.doLmsCommit(entries); } + + // <OLATCE-289> + /** + * This method copies the current cmi-file to a new file with timestamp so that all attempts + * can be evaluated by the assessment tool. + * @return + */ + public boolean archiveScoData() { + File currentCmiFile = _scoDataModel.getFile().getAbsoluteFile(); + LocalFileImpl currentCmiFileVFS = new LocalFileImpl(_scoDataModel.getFile().getAbsoluteFile()); + + String suffix = "." + FileUtils.getFileSuffix(currentCmiFile.getName()); + + String newFileName = currentCmiFile.getName().substring(0, currentCmiFile.getName().indexOf(suffix)) + + "_" + String.valueOf(System.currentTimeMillis() + + suffix); + + File outf = new File(currentCmiFile.getParentFile(), newFileName); + OutputStream os = null; + try { + os = new FileOutputStream(outf); + } catch (FileNotFoundException e) { + return false; + } + InputStream is = currentCmiFileVFS.getInputStream(); + FileUtils.copy(is, os); + FileUtils.closeSafely(os); + FileUtils.closeSafely(is); + + return true; + } + // </OLATCE-289> /** * Method to set the sco type for this class and if its an diff --git a/src/main/java/org/olat/modules/scorm/server/sequence/PrerequisiteManager.java b/src/main/java/org/olat/modules/scorm/server/sequence/PrerequisiteManager.java index a9c95f3742bb0f628bad4cb8f77f8c3bf398ee26..93242d1f0153185282c0c98f4ad5f93873f055f3 100644 --- a/src/main/java/org/olat/modules/scorm/server/sequence/PrerequisiteManager.java +++ b/src/main/java/org/olat/modules/scorm/server/sequence/PrerequisiteManager.java @@ -61,7 +61,7 @@ public class PrerequisiteManager extends BasicManager{ public PrerequisiteManager(String org, ISettingsHandler settings) { this.settings = settings; if (!populateFromDisk(org)) { - System.out.println("could not load in tracking model"); + logError("could not load in tracking model: " + org, null); } } diff --git a/src/main/java/org/olat/modules/scorm/server/servermodels/ScoDocument.java b/src/main/java/org/olat/modules/scorm/server/servermodels/ScoDocument.java index 82753625d0e677ba2064b16721b6e14aed698420..11ec096d1555b566f9445d22c843f057c003f645 100644 --- a/src/main/java/org/olat/modules/scorm/server/servermodels/ScoDocument.java +++ b/src/main/java/org/olat/modules/scorm/server/servermodels/ScoDocument.java @@ -139,7 +139,9 @@ public class ScoDocument extends XMLDocument { if (getElement(root, "cmi.core.lesson_status").getText().equals("failed")) { // remember the total time. _totalTimeHolder = getElement(root, "cmi.core.total_time").getText(); - isFailed = true; + // <OLATCE-289> when isFailed is set to true, the sco data is cleared but that is not correct + //isFailed = true; + // <OLATCE-289> } else { isFailed = false; } diff --git a/src/main/java/org/olat/modules/sharedfolder/SharedFolderDisplayController.java b/src/main/java/org/olat/modules/sharedfolder/SharedFolderDisplayController.java index 1d912a2b934134a02df5a800041e82134cf1ce64..66f7b07e17cb8e4007a5ed43ac02987d51a98412 100644 --- a/src/main/java/org/olat/modules/sharedfolder/SharedFolderDisplayController.java +++ b/src/main/java/org/olat/modules/sharedfolder/SharedFolderDisplayController.java @@ -108,7 +108,7 @@ public class SharedFolderDisplayController extends DefaultController { if (item == null) { sharedFolder.setLocalSecurityCallback(new ReadOnlyCallback()); - controller = new FolderRunController(sharedFolder, true, true, ureq, getWindowControl()); + controller = new FolderRunController(sharedFolder, true, true, false, ureq, getWindowControl()); controller.addControllerListener(this); } else { controller = new WebsiteDisplayController(ureq, getWindowControl(), sharedFolder, item.getName()); diff --git a/src/main/java/org/olat/modules/sharedfolder/SharedFolderEditorController.java b/src/main/java/org/olat/modules/sharedfolder/SharedFolderEditorController.java index e28411197034f778e01edc90eea382ab2eff88df..81a3e0e2eb4f971cd694bba9e4293eaa565360da 100644 --- a/src/main/java/org/olat/modules/sharedfolder/SharedFolderEditorController.java +++ b/src/main/java/org/olat/modules/sharedfolder/SharedFolderEditorController.java @@ -76,7 +76,7 @@ public class SharedFolderEditorController extends DefaultController { re = RepositoryManager.getInstance().lookupRepositoryEntry(res, true); sharedFolder = SharedFolderManager.getInstance().getNamedSharedFolder(re); - folderRunController = new FolderRunController(sharedFolder, true, true, ureq, getWindowControl()); + folderRunController = new FolderRunController(sharedFolder, true, true, false, ureq, getWindowControl()); vcEdit.put("folder", folderRunController.getInitialComponent()); setInitialComponent(vcEdit); diff --git a/src/main/java/org/olat/modules/webFeed/FeedPreviewSecurityCallback.java b/src/main/java/org/olat/modules/webFeed/FeedPreviewSecurityCallback.java index 3b3d4e83237c2a8190b2d526809a918ceaaf1a46..aebb3e4dd8af50baa26f05cd15d2066bb18dfa34 100644 --- a/src/main/java/org/olat/modules/webFeed/FeedPreviewSecurityCallback.java +++ b/src/main/java/org/olat/modules/webFeed/FeedPreviewSecurityCallback.java @@ -57,4 +57,13 @@ public class FeedPreviewSecurityCallback implements FeedSecurityCallback { public boolean mayEditMetadata() { return false; } + + /** + * @see org.olat.modules.webFeed.FeedSecurityCallback#mayViewAllDrafts() + */ + //fxdiff BAKS-18 + @Override + public boolean mayViewAllDrafts() { + return false; + } } diff --git a/src/main/java/org/olat/modules/webFeed/FeedResourceSecurityCallback.java b/src/main/java/org/olat/modules/webFeed/FeedResourceSecurityCallback.java index 0e4ec9034ca1202c5b3776caa643f162014f5149..6aae09d36ce126207b1eeb0b60223b1c0cb046e8 100644 --- a/src/main/java/org/olat/modules/webFeed/FeedResourceSecurityCallback.java +++ b/src/main/java/org/olat/modules/webFeed/FeedResourceSecurityCallback.java @@ -64,4 +64,13 @@ public class FeedResourceSecurityCallback implements FeedSecurityCallback { public boolean mayEditMetadata() { return isAdmin || isOwner; } + + /** + * @see org.olat.modules.webFeed.FeedSecurityCallback#mayViewAllDrafts() + */ + //fxdiff BAKS-18 + @Override + public boolean mayViewAllDrafts() { + return isAdmin || isOwner; + } } diff --git a/src/main/java/org/olat/modules/webFeed/FeedSecurityCallback.java b/src/main/java/org/olat/modules/webFeed/FeedSecurityCallback.java index ffecb781b712bb888b8260d448c523c25a7cd9b0..3f3f961fa1519298e5f8f8359ecd6b3cc45bbf08 100644 --- a/src/main/java/org/olat/modules/webFeed/FeedSecurityCallback.java +++ b/src/main/java/org/olat/modules/webFeed/FeedSecurityCallback.java @@ -48,4 +48,10 @@ public interface FeedSecurityCallback { * @return Whether items may be deleted or not */ boolean mayDeleteItems(); + + /** + * @return If the user can view all drafts + */ + //fxdiff BAKS-18 + boolean mayViewAllDrafts(); } diff --git a/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java b/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java index 013760bed6a84f5f6e5788f7dac4711db9a685cb..f705b3f7070a9c711f5f31d18018794cb30c5c1b 100644 --- a/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java +++ b/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java @@ -315,6 +315,24 @@ public class FeedViewHelper { } return lastModified; } + + //fxdiff FXOLAT-118: size for video podcast + public String getWidth(Item item) { + int width = item.getWidth(); + if(width > 0 && width < 2000) { + return Integer.toString(width); + } + return "400"; + } + + //fxdiff FXOLAT-118: size for video podcast + public String getHeight(Item item) { + int height = item.getHeight(); + if(height > 0 && height < 2000) { + return Integer.toString(height); + } + return "300"; + } /** * @return The jump in link diff --git a/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java b/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java index 4cef615884e7960515dc957b77acf1eae928dd70..cf62cffe952dec19b5d4439348a656fdb3f6d4b1 100644 --- a/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java +++ b/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java @@ -325,7 +325,8 @@ public abstract class FeedManagerImpl extends FeedManager { } } else { // There's no repo entry image -> delete the feed image as well. - deleteImage(feed); + //fxdiff: FXOLAT-271 (we don't want to delete image) + //deleteImage(feed); } } } diff --git a/src/main/java/org/olat/modules/webFeed/models/Feed.java b/src/main/java/org/olat/modules/webFeed/models/Feed.java index 1920bc893d1c0f2e6d4bf1b90f336827ae1be6ec..d50305d3845af9b3f55f0d95cd3a8af49e0e857d 100644 --- a/src/main/java/org/olat/modules/webFeed/models/Feed.java +++ b/src/main/java/org/olat/modules/webFeed/models/Feed.java @@ -230,6 +230,9 @@ public class Feed implements OLATResourceable, Serializable { } else if (identity.getKey() == item.getAuthorKey()) { // scheduled items and drafts of oneself are shown filteredItems.add(item); + //fxdiff BAKS-18 + } else if (item.isDraft() && callback.mayViewAllDrafts()) { + filteredItems.add(item); } } return filteredItems; diff --git a/src/main/java/org/olat/modules/webFeed/models/Item.java b/src/main/java/org/olat/modules/webFeed/models/Item.java index 79004b518f30db6af86a2547eafad5886dedbe20..4cb149dea787c8a3aa192d8a8032dab565a6a519 100644 --- a/src/main/java/org/olat/modules/webFeed/models/Item.java +++ b/src/main/java/org/olat/modules/webFeed/models/Item.java @@ -55,6 +55,7 @@ public class Item implements Serializable, Dated { private Date lastModified; private Date publishDate; private Enclosure enclosure; + private int width, height; //fxdiff FXOLAT-118: size for video podcast private transient FileElement mediaFile; // An item can either be in draft version or it is published // -> 'not draft' is equivalent to 'published' @@ -272,6 +273,22 @@ public class Item implements Serializable, Dated { public FileElement getMediaFile() { return mediaFile; } + //fxdiff FXOLAT-118: size for video podcast + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } /** * @param draft The draft to set. diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java index ab07f2751ffc65fbb4ae61a29d41c9f4d6affb25..421a862141f3d5437d13feb7fe8c171cf3555b09 100644 --- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java +++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java @@ -124,6 +124,29 @@ public class BlogArtefactHandler extends EPAbstractHandler<BlogArtefact> { artefact.setFulltextContent(xml); FileUtils.closeSafely(in); } + String origBPath = artefact.getBusinessPath(); + String artSource = ""; + BusinessControl bc = BusinessControlFactory.getInstance().createFromString(origBPath); + if (origBPath.contains(CourseNode.class.getSimpleName())){ + // blog-post from inside a course, rebuild "course-name - feed-name" + OLATResourceable ores = bc.popLauncherContextEntry().getOLATResourceable(); + RepositoryEntry repoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(ores.getResourceableId()); + artSource = repoEntry.getDisplayname(); + if (feed!=null) { + artSource += " - " + feed.getTitle(); + } + } else if (origBPath.contains(RepositoryEntry.class.getSimpleName())){ + // blog-post from blog-LR, only get name itself + if (feed!=null) { + artSource = feed.getTitle(); + } + } else { + // collecting a post from live-blog, [Identity:xy] + if (feed!=null) { + artSource = feed.getTitle(); + } + } + artefact.setSource(artSource); } @Override diff --git a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java index ee4d47be9f3a2a083ee892e3748465aade06fc5e..440bf5733069bacd4ec1d211d263c30e43d5d72f 100644 --- a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java +++ b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java @@ -27,8 +27,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.ContextEntry; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; +import org.olat.core.logging.LogDelegator; import org.olat.core.util.filter.Filter; import org.olat.core.util.filter.FilterFactory; import org.olat.modules.webFeed.dispatching.Path; @@ -49,9 +48,7 @@ import org.olat.search.service.indexer.OlatFullIndexer; * * @author gwassmann */ -public abstract class FeedRepositoryIndexer implements Indexer { - - private static final OLog log = Tracing.createLoggerFor(FeedRepositoryIndexer.class); +public abstract class FeedRepositoryIndexer extends LogDelegator implements Indexer { /** * @see org.olat.search.service.indexer.Indexer#checkAccess(org.olat.core.id.context.ContextEntry, @@ -73,8 +70,8 @@ public abstract class FeedRepositoryIndexer implements Indexer { String repoEntryName = "*name not available*"; try { repoEntryName = repositoryEntry.getDisplayname(); - if (log.isDebug()) { - log.info("Indexing: " + repoEntryName); + if (isLogDebugEnabled()) { + logDebug("Indexing: " + repoEntryName); } Feed feed = FeedManager.getInstance().getFeed(repositoryEntry.getOlatResource()); @@ -90,13 +87,15 @@ public abstract class FeedRepositoryIndexer implements Indexer { Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(mapperBaseURL); // Only index items. Feed itself is indexed by RepositoryEntryIndexer. - log.info("PublishedItems size=" + feed.getPublishedItems().size()); + if (isLogDebugEnabled()) { + logDebug("PublishedItems size=" + feed.getPublishedItems().size()); + } for (Item item : feed.getPublishedItems()) { OlatDocument itemDoc = new FeedItemDocument(item, searchResourceContext, mediaUrlFilter); indexer.addDocument(itemDoc.getLuceneDocument()); } } catch (NullPointerException e) { - log.error("Error indexing feed:" + repoEntryName, e); + logError("Error indexing feed:" + repoEntryName, e); } } diff --git a/src/main/java/org/olat/modules/webFeed/ui/ExternalFeedFormController.java b/src/main/java/org/olat/modules/webFeed/ui/ExternalFeedFormController.java deleted file mode 100644 index 8373d6e99b089103172f1892cda5ac66104eb6bc..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/modules/webFeed/ui/ExternalFeedFormController.java +++ /dev/null @@ -1,186 +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) frentix GmbH<br> - * http://www.frentix.com<br> - * <p> - */ -package org.olat.modules.webFeed.ui; - -import java.util.Date; - -import org.apache.commons.lang.StringEscapeUtils; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItem; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.FormLink; -import org.olat.core.gui.components.form.flexible.elements.TextElement; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.components.form.flexible.impl.FormEvent; -import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.translator.Translator; -import org.olat.modules.webFeed.managers.FeedManager; -import org.olat.modules.webFeed.managers.ValidatedURL; -import org.olat.modules.webFeed.models.Feed; - -/** - * This controller is responsible for editing feed information. <br /> - * <h3>Events fired by this controller:</h3> - * <ul> - * <li>Event.CHANGED_EVENT</li> - * <li>Event.CANCELLED_EVENT</li> - * </ul> - * <P> - * Initial Date: Feb 5, 2009 <br> - * - * @author Gregor Wassmann, frentix GmbH, http://www.frentix.com - */ -public class ExternalFeedFormController extends FormBasicController { - private Feed feed; - private TextElement title, description, feedUrl; - private FormLink cancelButton; - - /** - * @param ureq - * @param control - * @param feed - */ - public ExternalFeedFormController(UserRequest ureq, WindowControl control, Feed podcast, Translator translator) { - super(ureq, control); - this.feed = podcast; - setTranslator(translator); - initForm(ureq); - } - - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) - */ - @Override - public void formOK(UserRequest ureq) { - feed.setTitle(StringEscapeUtils.escapeHtml(title.getValue()).toString()); - feed.setDescription(description.getValue()); - feed.setExternalFeedUrl(feedUrl.isEmpty() ? null : feedUrl.getValue()); - feed.setLastModified(new Date()); - this.fireEvent(ureq, Event.CHANGED_EVENT); - } - - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.form.flexible.FormItem, - * org.olat.core.gui.components.form.flexible.impl.FormEvent) - */ - protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if (source == cancelButton && event.wasTriggerdBy(FormEvent.ONCLICK)) { - fireEvent(ureq, Event.CANCELLED_EVENT); - } - } - - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#validateFormLogic(org.olat.core.gui.UserRequest) - */ - @Override - protected boolean validateFormLogic(UserRequest ureq) { - boolean validUrl = false; - if(feedUrl.isEmpty()) { - //allowed - feedUrl.clearError(); - validUrl = true; - } else { - //validated feed url - String url = feedUrl.getValue(); - String type = feed.getResourceableTypeName(); - ValidatedURL validatedUrl = FeedManager.getInstance().validateFeedUrl(url, type); - if(!validatedUrl.getUrl().equals(url)) { - feedUrl.setValue(validatedUrl.getUrl()); - } - switch(validatedUrl.getState()) { - case VALID: - feedUrl.clearError(); - validUrl = true; - break; - case NO_ENCLOSURE: - feedUrl.setErrorKey("feed.form.feedurl.invalid.no_media", null); - break; - case NOT_FOUND: - feedUrl.setErrorKey("feed.form.feedurl.invalid.not_found", null); - break; - case MALFORMED: - feedUrl.setErrorKey("feed.form.feedurl.invalid", null); - break; - } - } - - String descriptionText = description.getValue(); - boolean descOk = true; - if(descriptionText.length() <= 4000) { - description.clearError(); - } else { - description.setErrorKey("input.toolong", new String[]{"4000"}); - descOk = false; - } - return descOk && validUrl && super.validateFormLogic(ureq); - } - - /** - * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#doDispose() - */ - @Override - protected void doDispose() { - // nothing to dispose - } - - /** - * @see org.olat.modules.webFeed.ui.podcast.FeedFormController#initForm(org.olat.core.gui.components.form.flexible.FormItemContainer, - * org.olat.core.gui.control.Controller, org.olat.core.gui.UserRequest) - */ - @SuppressWarnings("unused") - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - this.setFormTitle("feed.edit"); - - title = uifactory.addTextElement("title", "feed.title.label", 256, feed.getTitle(), this.flc); - title.setMandatory(true); - title.setNotEmptyCheck("feed.form.field.is_mandatory"); - - // Description - //description = formItemsFactory.addTextAreaElement("description", 5000, 0, 2, true, feed.getDescription(), - // "feed.form.description", this.flc); - description = uifactory.addRichTextElementForStringDataMinimalistic("description", "feed.form.description", feed - .getDescription(), 5, -1, false, formLayout, ureq.getUserSession(), getWindowControl()); - description.setMandatory(true); - description.setNotEmptyCheck("feed.form.field.is_mandatory"); - // The feed url - feedUrl = uifactory.addTextElement("feedUrl", "feed.form.feedurl", 5000, feed.getExternalFeedUrl(), this.flc); - feedUrl.setDisplaySize(70); - - String type = feed.getResourceableTypeName(); - if(type != null && type.indexOf("BLOG") >= 0) { - feedUrl.setExampleKey("feed.form.feedurl.example", null); - } else { - feedUrl.setExampleKey("feed.form.feedurl.example_podcast", null); - } - - // Submit and cancelButton buttons - final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); - this.flc.add(buttonLayout); - - uifactory.addFormSubmitButton("submit", buttonLayout); - cancelButton = uifactory.addFormLink("cancel", buttonLayout, Link.BUTTON); - } -} diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java b/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java index be872e6277d8641d4a6137b844e068b551af2ff8..8ad5991c5257eb45e0a047985c136cd8b16c361a 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java @@ -47,6 +47,7 @@ import org.olat.core.util.Util; import org.olat.core.util.vfs.LocalFileImpl; import org.olat.core.util.vfs.VFSMediaResource; import org.olat.modules.webFeed.managers.FeedManager; +import org.olat.modules.webFeed.managers.ValidatedURL; import org.olat.modules.webFeed.models.Feed; /** @@ -72,6 +73,11 @@ class FeedFormController extends FormBasicController { private ImageComponent image; private FormLayoutContainer imageContainer; + /** + * if form edits an external feed: + */ + private TextElement feedUrl; + /** * @param ureq * @param control @@ -98,6 +104,10 @@ class FeedFormController extends FormBasicController { protected void formOK(UserRequest ureq) { feed.setTitle(title.getValue()); feed.setDescription(description.getValue()); + + if(feed.isExternal()) + feed.setExternalFeedUrl(feedUrl.isEmpty() ? null : feedUrl.getValue()); + feed.setLastModified(new Date()); // The image is retrieved by the main controller this.fireEvent(ureq, Event.CHANGED_EVENT); @@ -141,7 +151,48 @@ class FeedFormController extends FormBasicController { description.setErrorKey("input.toolong", new String[]{"4000"}); allOk = false; } - return allOk && super.validateFormLogic(ureq); + return allOk && validateExternalFeedUrl() && super.validateFormLogic(ureq); + } + + /** + * validates the external feed-url + * + * @return returns true if the external-feed url is an empty string or a valid url + */ + private boolean validateExternalFeedUrl(){ + //if not external, there is no text-element, do not check, just return true + if(!feed.isExternal()) return true; + + boolean validUrl = false; + if(feedUrl.isEmpty()) { + //allowed + feedUrl.clearError(); + validUrl = true; + } else { + //validated feed url + String url = feedUrl.getValue(); + String type = feed.getResourceableTypeName(); + ValidatedURL validatedUrl = FeedManager.getInstance().validateFeedUrl(url, type); + if(!validatedUrl.getUrl().equals(url)) { + feedUrl.setValue(validatedUrl.getUrl()); + } + switch(validatedUrl.getState()) { + case VALID: + feedUrl.clearError(); + validUrl = true; + break; + case NO_ENCLOSURE: + feedUrl.setErrorKey("feed.form.feedurl.invalid.no_media", null); + break; + case NOT_FOUND: + feedUrl.setErrorKey("feed.form.feedurl.invalid.not_found", null); + break; + case MALFORMED: + feedUrl.setErrorKey("feed.form.feedurl.invalid", null); + break; + } + } + return validUrl; } /** @@ -224,6 +275,19 @@ class FeedFormController extends FormBasicController { mimeTypes.add("image/gif"); file.limitToMimeType(mimeTypes, "feed.form.file.type.error.images", null); + // if external feed, display feed-url text-element: + if(feed.isExternal()){ + feedUrl = uifactory.addTextElement("feedUrl", "feed.form.feedurl", 5000, feed.getExternalFeedUrl(), this.flc); + feedUrl.setDisplaySize(70); + + String type = feed.getResourceableTypeName(); + if(type != null && type.indexOf("BLOG") >= 0) { + feedUrl.setExampleKey("feed.form.feedurl.example", null); + } else { + feedUrl.setExampleKey("feed.form.feedurl.example_podcast", null); + } + } + // Submit and cancelButton buttons final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); this.flc.add(buttonLayout); @@ -246,7 +310,7 @@ class FeedFormController extends FormBasicController { /** * @return true if the image was deleted. */ - public boolean imageDeleted() { + public boolean isImageDeleted() { return imageDeleted; } } diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java b/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java index 666c10278d36862192b12aea11d47ed4801d59a5..38718fb3a311398ba3de26c0637626a6b5a62829 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java @@ -63,7 +63,7 @@ public class FeedMainController extends BasicController implements Activateable, private Feed feed; private Link editFeedButton; private CloseableModalController cmc; - private FormBasicController feedFormCtr; + private FeedFormController feedFormCtr; private VelocityContainer vcMain, vcInfo, vcRightCol; private ItemsController itemsCtr; private LockResult lock; @@ -173,11 +173,8 @@ public class FeedMainController extends BasicController implements Activateable, if (lock.isSuccess()) { if (feed.isExternal()) { oldFeedUrl = feed.getExternalFeedUrl(); - feedFormCtr = new ExternalFeedFormController(ureq, getWindowControl(), feed, uiFactory.getTranslator()); - } else { - // Default for podcasts is that they are edited within OLAT - feedFormCtr = new FeedFormController(ureq, getWindowControl(), feed, uiFactory); - } + } + feedFormCtr = new FeedFormController(ureq, getWindowControl(), feed, uiFactory); activateModalDialog(feedFormCtr); } else { showInfo("feed.is.being.edited.by", lock.getOwner().getName()); @@ -232,21 +229,17 @@ public class FeedMainController extends BasicController implements Activateable, } // Set the URIs correctly helper.setURIs(); + } + //handle image-changes if any + if (feedFormCtr.isImageDeleted()) { + feedManager.deleteImage(feed); } else { - if (feedFormCtr instanceof FeedFormController) { - FeedFormController internalFormCtr = (FeedFormController) feedFormCtr; - if (internalFormCtr.imageDeleted()) { - feedManager.deleteImage(feed); - } else { - // set the image - FileElement image = null; - image = internalFormCtr.getFile(); - feedManager.setImage(image, feed); - } - } else { - // it's an external feed form, nothing to do in this case - } - } + // set the image + FileElement image = null; + image = feedFormCtr.getFile(); + feedManager.setImage(image, feed); + } + // Eventually update the feed feed = feedManager.updateFeedMetadata(feed); // Dispose the feedFormCtr @@ -269,7 +262,7 @@ public class FeedMainController extends BasicController implements Activateable, } } else if (source == itemsCtr && event.equals(ItemsController.HANDLE_NEW_EXTERNAL_FEED_DIALOG_EVENT)) { oldFeedUrl = feed.getExternalFeedUrl(); - feedFormCtr = new ExternalFeedFormController(ureq, getWindowControl(), feed, uiFactory.getTranslator()); + feedFormCtr = new FeedFormController(ureq, getWindowControl(), feed, uiFactory); activateModalDialog(feedFormCtr); } else if (source == itemsCtr && event.equals(ItemsController.FEED_INFO_IS_DIRTY_EVENT)) { vcInfo.setDirty(true); diff --git a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java index 9cdffc9a14c3df7ddd399cc0b0625186fed98a02..d81dbde28cf0f869a7583cf8568d0cf5f6d8f7e2 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java @@ -22,6 +22,7 @@ package org.olat.modules.webFeed.ui; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -89,6 +90,7 @@ public class ItemsController extends BasicController { private ArrayList<Link> deleteButtons; private ArrayList<Link> itemLinks; private Map<Item,Controller> artefactLinks; + private Map<Item,Controller> commentsLinks; private Link addItemButton, makeInternalButton, makeExternalButton, olderItemsLink, newerItemsLink, startpageLink; private FormBasicController itemFormCtr; private CloseableModalController cmc; @@ -101,7 +103,8 @@ public class ItemsController extends BasicController { private FeedSecurityCallback callback; private Panel mainPanel; private ItemController itemCtr; - private int allItemsCount = 0; + //private int allItemsCount = 0; + private List<ItemId> allItemIds; // Only one lock variable is needed, since only one item can be edited // at a time. private LockResult lock; @@ -177,8 +180,8 @@ public class ItemsController extends BasicController { createDateComponents(ureq, feed); // The year/month navigation - List<? extends Dated> items = feed.getFilteredItems(callback, ureq.getIdentity()); - allItemsCount = items.size(); + List<Item> items = feed.getFilteredItems(callback, ureq.getIdentity()); + setAllItemIds(items); naviCtr = new YearNavigationController(ureq, wControl, getTranslator(), items); listenTo(naviCtr); if (displayConfig.isShowDateNavigation()){ @@ -189,6 +192,22 @@ public class ItemsController extends BasicController { mainPanel.setContent(vcItems); this.putInitialPanel(mainPanel); } + + private void setAllItemIds(List<Item> items) { + allItemIds = new ArrayList<ItemId>(); + for(Item item:items) { + allItemIds.add(new ItemId(item)); + } + } + + private boolean isSameAllItems(List<Item> items) { + if(allItemIds == null) return false; + List<ItemId> itemIds = new ArrayList<ItemId>(); + for(Item item:items) { + itemIds.add(new ItemId(item)); + } + return allItemIds.containsAll(itemIds) && itemIds.containsAll(allItemIds); + } /** * Creates all necessary buttons for editing the feed's items @@ -242,11 +261,18 @@ public class ItemsController extends BasicController { */ private void createCommentsAndRatingsLink(UserRequest ureq, Feed feed, Item item) { if (CoreSpringFactory.containsBean(CommentAndRatingService.class)) { + if(commentsLinks == null) { + commentsLinks = new HashMap<Item,Controller>(); + } else if(commentsLinks.containsKey(item)) { + removeAsListenerAndDispose(commentsLinks.get(item)); + } + CommentAndRatingService commentAndRatingService = (CommentAndRatingService) CoreSpringFactory.getBean(CommentAndRatingService.class); commentAndRatingService.init(getIdentity(), feed, item.getGuid(), callback.mayEditMetadata(), ureq.getUserSession().getRoles().isGuestOnly()); UserCommentsAndRatingsController commentsAndRatingCtr = commentAndRatingService.createUserCommentsAndRatingControllerMinimized(ureq, getWindowControl()); commentsAndRatingCtr.addUserObject(item); listenTo(commentsAndRatingCtr); + commentsLinks.put(item, commentsAndRatingCtr); String guid = item.getGuid(); vcItems.put("commentsAndRating." + guid, commentsAndRatingCtr.getInitialComponent()); } @@ -411,14 +437,26 @@ public class ItemsController extends BasicController { } else if (source == olderItemsLink) { helper.olderItems(); + if (callback.mayEditItems() || callback.mayCreateItems()) { + createEditButtons(ureq, feed); + } + createCommentsAndRatingsLinks(ureq, feed); vcItems.setDirty(true); } else if (source == newerItemsLink) { helper.newerItems(); + if (callback.mayEditItems() || callback.mayCreateItems()) { + createEditButtons(ureq, feed); + } + createCommentsAndRatingsLinks(ureq, feed); vcItems.setDirty(true); } else if (source == startpageLink) { helper.startpage(); + if (callback.mayEditItems() || callback.mayCreateItems()) { + createEditButtons(ureq, feed); + } + createCommentsAndRatingsLinks(ureq, feed); vcItems.setDirty(true); } else if (source instanceof Link) { @@ -497,6 +535,11 @@ public class ItemsController extends BasicController { makeInternalAndExternalButtons(); // The subscription/feed url from the feed info is obsolete fireEvent(ureq, ItemsController.FEED_INFO_IS_DIRTY_EVENT); + } else { + if (callback.mayEditItems() || callback.mayCreateItems()) { + createEditButtons(ureq, feed); + } + createCommentsAndRatingsLinks(ureq, feed); } vcItems.setDirty(true); // in case we were in single item view, show all items @@ -594,6 +637,10 @@ public class ItemsController extends BasicController { // make sure items are sorted properly Collections.sort(items, new ItemPublishDateComparator()); helper.setSelectedItems(items); + if (callback.mayEditItems() || callback.mayCreateItems()) { + createEditButtons(ureq, feed); + } + createCommentsAndRatingsLinks(ureq, feed); vcItems.setDirty(true); mainPanel.setContent(vcItems); @@ -613,7 +660,7 @@ public class ItemsController extends BasicController { } // Check if someone else added an item, reload everything - if (feed.getFilteredItems(callback, ureq.getIdentity()).size() != allItemsCount) { + if (!isSameAllItems(feed.getFilteredItems(callback, ureq.getIdentity()))) { resetItems(ureq, feed); } } @@ -656,52 +703,18 @@ public class ItemsController extends BasicController { List<Item> items = feed.getFilteredItems(callback, ureq.getIdentity()); helper.setSelectedItems(items); naviCtr.setDatedObjects(items); - allItemsCount = items.size(); + setAllItemIds(items); // Add item details page link createItemLinks(feed); // Add item user comments link and rating - createCommentsAndRatingsLinks(ureq, feed); + if (displayConfig.isShowCRInMinimized()) { + createCommentsAndRatingsLinks(ureq, feed); + } // Add date components createDateComponents(ureq, feed); vcItems.setDirty(true); } - /** - * Sets the edit mode to editable - * - * @param editable - * @param feed the current feed - */ - public void setEditMode(UserRequest ureq, boolean editable, Feed feed) { - vcItems.contextPut("editModeEnabled", editable); - if (editable) { - createEditButtons(ureq, feed); - } else { - removeEditButtons(); - } - } - - /** - * Remove the edit buttons - */ - private void removeEditButtons() { - vcItems.contextRemove(velocity_root); - vcItems.remove(addItemButton); - vcItems.remove(makeExternalButton); - vcItems.remove(makeInternalButton); - for (Link button : editButtons) { - vcItems.remove(button); - } - for (Link button : deleteButtons) { - vcItems.remove(button); - } - addItemButton = null; - makeInternalButton = null; - makeExternalButton = null; - editButtons = null; - deleteButtons = null; - } - /** * Displays the item in the mainPanel of this controller. * @@ -754,4 +767,33 @@ public class ItemsController extends BasicController { } return result; } + + private class ItemId { + private final String guid; + private final Date lastModification; + + public ItemId(Item item) { + guid = item.getGuid(); + lastModification = item.getLastModified(); + } + + @Override + public int hashCode() { + return guid.hashCode() + (lastModification == null ? -483 : lastModification.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof ItemId) { + ItemId id = (ItemId)obj; + return guid.equals(id.guid) && ((lastModification == null && id.lastModification == null) || + (lastModification != null && lastModification.equals(id.lastModification))); + } + + return false; + } + } } diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java index 1218ecc96d61f85ade97b7077a0ef89b3bc0afb4..23c6fee1bee286bb6807422fd74fec55ecd17ecb 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java @@ -98,6 +98,9 @@ public class BlogPostFormController extends FormBasicController { setValues(); if(!currentlyDraft || post.getModifierKey() > 0) { post.setModifierKey(ureq.getIdentity().getKey()); + //fxdiff BAKS-18 + } else if(currentlyDraft && !ureq.getIdentity().getKey().equals(post.getAuthorKey())) { + post.setModifierKey(ureq.getIdentity().getKey()); } post.setDraft(false); fireEvent(ureq, Event.CHANGED_EVENT); @@ -150,6 +153,9 @@ public class BlogPostFormController extends FormBasicController { setValues(); if(!currentlyDraft || post.getModifierKey() > 0) { post.setModifierKey(ureq.getIdentity().getKey()); + //fxdiff BAKS-18 + } else if(currentlyDraft && !ureq.getIdentity().getKey().equals(post.getAuthorKey())) { + post.setModifierKey(ureq.getIdentity().getKey()); } post.setDraft(true); this.fireEvent(ureq, Event.CHANGED_EVENT); diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java b/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java index abac3aec1bbec460ac6901602e425ecb8e9fe4a4..8d36f0f69e3bc81ee8627af65d21e7e68613ba07 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java +++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java @@ -40,6 +40,7 @@ import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.translator.Translator; import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.webFeed.managers.FeedManager; import org.olat.modules.webFeed.models.Feed; @@ -64,6 +65,8 @@ public class EpisodeFormController extends FormBasicController { private Item episode; private Feed podcast; private TextElement title; + private TextElement widthEl; //fxdiff FXOLAT-118: size for video podcast + private TextElement heightEl; private RichTextElement desc; private VFSContainer baseDir; private FileElement file; @@ -104,6 +107,26 @@ public class EpisodeFormController extends FormBasicController { episode.setMediaFile(getFile()); // Set episode as published (no draft feature for podcast) episode.setDraft(false); + + //fxdiff FXOLAT-118: size for video podcast + String width = widthEl.getValue(); + if(StringHelper.containsNonWhitespace(width)) { + try { + episode.setWidth(Integer.parseInt(width)); + } catch (NumberFormatException e) { + //silently catch + } + } + + String height = heightEl.getValue(); + if(StringHelper.containsNonWhitespace(height)) { + try { + episode.setHeight(Integer.parseInt(height)); + } catch (NumberFormatException e) { + //silently catch + } + } + this.fireEvent(ureq, Event.CHANGED_EVENT); } @@ -142,6 +165,8 @@ public class EpisodeFormController extends FormBasicController { protected boolean validateFormLogic(UserRequest ureq) { // Since mimetype restrictions have been proved to be problematic, let us // validate the file ending instead as a pragmatic solution. + boolean allOk = true; + String name = file.getUploadFileName(); if (name != null) { boolean isValidFileType = name.toLowerCase().matches(MIME_TYPES_ALLOWED); @@ -149,16 +174,41 @@ public class EpisodeFormController extends FormBasicController { if (!isValidFileType || !isFilenameValid) { if(!isValidFileType) { file.setErrorKey("feed.form.file.type.error", null); + allOk = false; } else if (!isFilenameValid) { file.setErrorKey("podcastfile.name.notvalid", null); + allOk = false; } - return false; } else { file.clearError(); flc.setDirty(true); } } - return super.validateFormLogic(ureq); + + //fxdiff FXOLAT-118: size for video podcast + String width = widthEl.getValue(); + widthEl.clearError(); + if(StringHelper.containsNonWhitespace(width)) { + try { + episode.setWidth(Integer.parseInt(width)); + } catch (NumberFormatException e) { + widthEl.setErrorKey("podcast.episode.file.size.error", null); + allOk = false; + } + } + + String height = heightEl.getValue(); + heightEl.clearError(); + if(StringHelper.containsNonWhitespace(height)) { + try { + episode.setHeight(Integer.parseInt(height)); + } catch (NumberFormatException e) { + heightEl.setErrorKey("podcast.episode.file.size.error", null); + allOk = false; + } + } + + return allOk && super.validateFormLogic(ureq); } private boolean validateFilename(String filename) { @@ -196,6 +246,11 @@ public class EpisodeFormController extends FormBasicController { File mediaFile = FeedManager.getInstance().getItemEnclosureFile(episode, podcast); file.setInitialFile(mediaFile); file.addActionListener(this, FormEvent.ONCHANGE); + + String width = episode.getWidth() > 0 ? Integer.toString(episode.getWidth()) : ""; + widthEl = uifactory.addTextElement("video-width", "podcast.episode.file.width", 12, width, flc); + String height = episode.getHeight() > 0 ? Integer.toString(episode.getHeight()) : ""; + heightEl = uifactory.addTextElement("video-height", "podcast.episode.file.height", 12, height, flc); // Submit and cancel buttons final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html index 52d23212b8e171ee566e792fb2af4409ce4224bc..63b4d956ecb37dc1d5c1db3d89d876ca81450d46 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html +++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html @@ -50,7 +50,7 @@ <script type="text/javascript"> /* <![CDATA[ */ #if ($!helper.getMediaType($episode.getEnclosure()) == "video") - BPlayer.insertPlayer("$!helper.getMediaUrl($episode)","o_podcast_episode_$velocityCount",400,300,"video"); + BPlayer.insertPlayer("$!helper.getMediaUrl($episode)","o_podcast_episode_$velocityCount",$!helper.getWidth($episode),$!helper.getHeight($episode),"video"); #else BPlayer.insertPlayer("$!helper.getMediaUrl($episode)","o_podcast_episode_$velocityCount",400,24,"sound"); #end diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_de.properties index 78e02052f80d0ef63dc0c843a7e024df185acc2b..ca6432773a87934432b2ffdf697df65df148ca06 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_de.properties @@ -4,6 +4,9 @@ podcast.is.being.edited.by = Der Podcast wird bereits von {0} bearbeitet. feed.item.is.being.edited.by = Die Episode wird bereits von {0} bearbeitet. podcast.subscribe.to.this.feed = Diesen Podcast abonnieren podcast.episode.file.label = Audio- oder Videodatei +podcast.episode.file.width = Breite +podcast.episode.file.height = H\u00F6he +podcast.episode.file.size.error=Falsches Zahlenformat podcast.episode.download = Episode herunterladen podcast.episodes = Episoden podcast.episode.mandatory = Eine Episode verlangt eine Audio- oder Videodatei, die mit Flash abgespielt werden kann. diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_en.properties index c1dfca947af146c5abeb0f0f0f838c8393c8f104..797e3de5d2bee3a827b3553abae770cb19c51d8f 100644 --- a/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 20 19:02:19 CET 2011 +#Thu May 26 10:42:23 CEST 2011 chelp.episode.form.p1=Please indicate a title to describe this episode accurately. This field is mandatory. chelp.episode.form.p2=This description is optional. If there is need to further explain your media file you can do that here. chelp.episode.form.p3=An episode always contains one audio and one video file. Select these on your PC to be uploaded afterwards. Depending on the file's size this can take some time. Please note that only Flash compatible formats are allowed (such as FLV, MP4, MP3, M4V, M4A, and AAC). @@ -20,7 +20,10 @@ feed.newer.items=Recent episodes feed.older.items=Older episodes help.hover.internal-external=Help to create or embed episodes podcast.episode.download=Download episodes +podcast.episode.file.height=Height podcast.episode.file.label=Audio or video file +podcast.episode.file.size.error=Wrong number format +podcast.episode.file.width=Width podcast.episode.mandatory=An episode requires an audio or video file that can be used with Flash. podcast.episodes=Episodes podcast.has.no.episodes=There are no episodes for this podcast. diff --git a/src/main/java/org/olat/modules/wiki/WikiMainController.java b/src/main/java/org/olat/modules/wiki/WikiMainController.java index 96fdad6c3cb20831775c19ae0b3234604ec87d60..85e9eaa33278f3d3f3d7da62e5ca2f14e0844db1 100644 --- a/src/main/java/org/olat/modules/wiki/WikiMainController.java +++ b/src/main/java/org/olat/modules/wiki/WikiMainController.java @@ -118,7 +118,7 @@ public class WikiMainController extends BasicController implements CloneableCont private String pageId; private VFSContainer wikiContainer; private OLATResourceable ores; - private VelocityContainer articleContent, navigationContent, discussionContent, editContent, content, versioningContent, mediaMgntContent, imageDisplay; + private VelocityContainer articleContent, navigationContent, discussionContent, editContent, content, versioningContent, mediaMgntContent, imageDisplay, fileListVC; private WikiEditArticleForm wikiEditForm; private WikiMarkupComponent wikiMenuComp, wikiArticleComp, wikiVersionDisplayComp; private ContextualSubscriptionController cSubscriptionCtrl; @@ -283,7 +283,10 @@ public class WikiMainController extends BasicController implements CloneableCont fileUplCtr = new FileUploadController(getWindowControl(), WikiManager.getInstance().getMediaFolder(ores), ureq, (int)FolderConfig.getLimitULKB(), Quota.UNLIMITED, null, false); listenTo(fileUplCtr); editContent.put("fileUplCtr", fileUplCtr.getInitialComponent()); - editContent.contextPut("fileList", wiki.getMediaFileList()); + // fxdiff FXOLAT-216: Dedicated file list container to not loose data in main form when uploading a new file + fileListVC = createVelocityContainer("filelist"); + fileListVC.contextPut("fileList", wiki.getMediaFileList()); + editContent.put("fileList", fileListVC); editContent.contextPut("linkList", wiki.getListOfAllPageNames()); tabs.addTab(translate("tab.edit"), editContent); @@ -649,7 +652,7 @@ public class WikiMainController extends BasicController implements CloneableCont } else if (event.getCommand().equals(FolderEvent.UPLOAD_EVENT)) { FolderEvent fEvent = (FolderEvent)event; createMediaMetadataFile(fEvent.getFilename(), ureq.getIdentity().getKey()); - editContent.contextPut("fileList", wiki.getMediaFileList()); + fileListVC.contextPut("fileList", wiki.getMediaFileList()); } } else if (source == breadCrumpCtr) { /************************************************************************* @@ -697,7 +700,7 @@ public class WikiMainController extends BasicController implements CloneableCont TableMultiSelectEvent tmse = (TableMultiSelectEvent) event; if (tmse.getAction().equals(ACTION_DELETE_MEDIAS)) { deleteMediaFile(mediaFilesTableModel.getObjects(tmse.getSelection()), ureq); - editContent.contextPut("fileList", wiki.getMediaFileList()); + fileListVC.contextPut("fileList", wiki.getMediaFileList()); } } } else if (source == archiveWikiDialogCtr) { diff --git a/src/main/java/org/olat/modules/wiki/_content/edit.html b/src/main/java/org/olat/modules/wiki/_content/edit.html index 2a5409142fca5fca141ef9ee6f1b02c51915a368..8e23c3f9fb5cbce951387bd48861be58d31e4172 100644 --- a/src/main/java/org/olat/modules/wiki/_content/edit.html +++ b/src/main/java/org/olat/modules/wiki/_content/edit.html @@ -52,16 +52,7 @@ <input type="submit" onclick="javascript:insertTags('[[',']]',document.getElementById('wikiLinkChooser').value)" value="$r.translateInAttribute("link.insert")" class="b_button b_small" /> </div> #end - #if($fileList.size() > 0) - <div class="o_wikimod_filechooser"> - <select style="position:relative" id="mediaFileChooser" size="1"> - #foreach($file in $fileList) - <option value="$r.escapeHtml($file.getName())">$file.getName()</option> - #end - </select> - <input type="submit" onclick="javascript:insertMediaTag(document.getElementById('mediaFileChooser').value)" value="$r.translateInAttribute("media.insert")" class="b_button b_small" /> - </div> - #end + $r.render("fileList") ## EDIT FORM <div class="o_wikimod_editform_wrapper"> diff --git a/src/main/java/org/olat/modules/wiki/_content/filelist.html b/src/main/java/org/olat/modules/wiki/_content/filelist.html new file mode 100644 index 0000000000000000000000000000000000000000..0b48d27fcd892cd4da0ae0d90077ed4ceae7fece --- /dev/null +++ b/src/main/java/org/olat/modules/wiki/_content/filelist.html @@ -0,0 +1,10 @@ +#if($fileList.size() > 0) + <div class="o_wikimod_filechooser"> + <select style="position:relative" id="mediaFileChooser" size="1"> + #foreach($file in $fileList) + <option value="$r.escapeHtml($file.getName())">$file.getName()</option> + #end + </select> + <input type="submit" onclick="javascript:insertMediaTag(document.getElementById('mediaFileChooser').value)" value="$r.translateInAttribute("media.insert")" class="b_button b_small" /> + </div> +#end diff --git a/src/main/java/org/olat/modules/wiki/portfolio/WikiArtefactHandler.java b/src/main/java/org/olat/modules/wiki/portfolio/WikiArtefactHandler.java index d348d869366e71eada6ccdedd7a08e1af3c7bbf7..1247b41b6db1e836f127a4e94a92b65c0713fcdf 100644 --- a/src/main/java/org/olat/modules/wiki/portfolio/WikiArtefactHandler.java +++ b/src/main/java/org/olat/modules/wiki/portfolio/WikiArtefactHandler.java @@ -35,8 +35,6 @@ import org.olat.portfolio.EPAbstractHandler; import org.olat.portfolio.model.artefacts.AbstractArtefact; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; -import org.olat.resource.OLATResource; -import org.olat.resource.OLATResourceManager; /** * @@ -70,6 +68,10 @@ public class WikiArtefactHandler extends EPAbstractHandler<WikiArtefact> { OLATResourceable ores = null; if (source instanceof OLATResourceable){ ores = (OLATResourceable) source; + // fxdiff: FXOLAT-148 a wiki from a businessgroup needs to be wrapped accordingly! + if (artefact.getBusinessPath().contains(BusinessGroup.class.getSimpleName())) { + ores = OresHelper.createOLATResourceableInstance(BusinessGroup.class, ores.getResourceableId()); + } Wiki wiki = WikiManager.getInstance().getOrLoadWiki(ores); String pageName = getPageName(artefact.getBusinessPath()); page = wiki.getPage(pageName, true); diff --git a/src/main/java/org/olat/portfolio/EPMapOnInvitationExtension.java b/src/main/java/org/olat/portfolio/EPMapOnInvitationExtension.java index e84637a3948e06fd22579acddb4e4d6227515e3e..a887d4d2343fab05284fd03ee57a5086a6d3f652 100644 --- a/src/main/java/org/olat/portfolio/EPMapOnInvitationExtension.java +++ b/src/main/java/org/olat/portfolio/EPMapOnInvitationExtension.java @@ -20,9 +20,6 @@ */ package org.olat.portfolio; -import java.util.ArrayList; -import java.util.List; - import org.olat.NewControllerFactory; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; @@ -31,7 +28,6 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.ContextEntryControllerCreator; -import org.olat.home.HomeMainController; import org.olat.portfolio.manager.EPFrontendManager; import org.olat.portfolio.model.structel.PortfolioStructureMap; @@ -54,6 +50,10 @@ public class EPMapOnInvitationExtension { @Override public Controller createController(ContextEntry ce, UserRequest ureq, WindowControl wControl) { + //fxdiff FXOLAT-151: better check invitation + if(!ureq.getUserSession().getRoles().isInvitee()) { + return null; + } PortfolioStructureMap map = getMapFromContext(ce); EPSecurityCallback secCallback = new EPSecurityCallbackImpl(false, true); @@ -77,8 +77,16 @@ public class EPMapOnInvitationExtension { @Override public boolean validateContextEntryAndShowError(ContextEntry ce, UserRequest ureq, WindowControl wControl) { - if (getMapFromContext(ce) == null) return false; - return true; + //fxdiff FXOLAT-151: better check invitation + if(!ureq.getUserSession().getRoles().isInvitee()) { + return false; + } + + final EPFrontendManager ePFMgr = (EPFrontendManager) CoreSpringFactory.getBean("epFrontendManager"); + PortfolioStructureMap map = getMapFromContext(ce); + if (map == null) return false; + boolean visible = ePFMgr.isMapVisible(ureq.getIdentity(), map.getOlatResource()); + return visible; } /** diff --git a/src/main/java/org/olat/portfolio/PortfolioModule.java b/src/main/java/org/olat/portfolio/PortfolioModule.java index 7225bfea61093ba7690111a224ae0622b41d7a72..46ab4cecc2ad3d81930f19703b0b3c4431609165 100755 --- a/src/main/java/org/olat/portfolio/PortfolioModule.java +++ b/src/main/java/org/olat/portfolio/PortfolioModule.java @@ -32,8 +32,12 @@ import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.ConfigOnOff; import org.olat.core.configuration.PersistedProperties; +import org.olat.core.extensions.action.GenericActionExtension; +import org.olat.core.gui.control.Event; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; +import org.olat.core.util.event.FrameworkStartedEvent; +import org.olat.core.util.event.FrameworkStartupEventChannel; import org.olat.core.util.vfs.VFSContainer; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupManagerImpl; @@ -65,9 +69,12 @@ public class PortfolioModule extends AbstractOLATModule implements ConfigOnOff, private VFSContainer portfolioRoot; private List<String> availableMapStyles = new ArrayList<String>(); private boolean offerPublicMapList; + private boolean isReflexionStepEnabled; + private boolean isCopyrightStepEnabled; public PortfolioModule(){ - BusinessGroupManagerImpl.getInstance().registerDeletableGroupDataListener(this); + BusinessGroupManagerImpl.getInstance().registerDeletableGroupDataListener(this); + FrameworkStartupEventChannel.registerForStartupEvent(this); } @Override @@ -98,8 +105,34 @@ public class PortfolioModule extends AbstractOLATModule implements ConfigOnOff, setOfferPublicMapList("true".equals(offerPublicSetting)); } + boolean stepRef = getBooleanPropertyValue("wizard.step.reflexion"); + setReflexionStepEnabled(stepRef); + boolean stepCopy = getBooleanPropertyValue("wizard.step.copyright"); + setCopyrightStepEnabled(stepCopy); + logInfo("ePortfolio is enabled: " + Boolean.toString(enabled)); } + + private void enableExtensions(boolean enabled){ + try { + ((GenericActionExtension)CoreSpringFactory.getBean("home.menupoint.ep")).setEnabled(enabled); + ((GenericActionExtension)CoreSpringFactory.getBean("home.menupoint.ep.pool")).setEnabled(enabled); + ((GenericActionExtension)CoreSpringFactory.getBean("home.menupoint.ep.maps")).setEnabled(enabled); + ((GenericActionExtension)CoreSpringFactory.getBean("home.menupoint.ep.structuredmaps")).setEnabled(enabled); + ((GenericActionExtension)CoreSpringFactory.getBean("home.menupoint.ep.sharedmaps")).setEnabled(enabled); + } catch (Exception e) { + // do nothing when extension don't exist. + } + } + + @Override + public void event(Event event) { + if(event instanceof FrameworkStartedEvent) { + enableExtensions(isEnabled()); + } else { + super.event(event); + } + } @Override protected void initDefaultProperties() { @@ -118,6 +151,11 @@ public class PortfolioModule extends AbstractOLATModule implements ConfigOnOff, } } + boolean stepRef = getBooleanConfigParameter("wizard.step.reflexion", true); + setReflexionStepEnabled(stepRef); + boolean stepCopy = getBooleanConfigParameter("wizard.step.copyright", true); + setCopyrightStepEnabled(stepCopy); + setOfferPublicMapList(getBooleanConfigParameter("portfolio.offer.public.map.list", true)); } @@ -139,6 +177,8 @@ public class PortfolioModule extends AbstractOLATModule implements ConfigOnOff, public void setEnabled(boolean enabled) { if(this.enabled != enabled) { setStringProperty("portfolio.enabled", Boolean.toString(enabled), true); + this.enabled = enabled; + enableExtensions(enabled); } } @@ -303,4 +343,30 @@ public class PortfolioModule extends AbstractOLATModule implements ConfigOnOff, return false; } + /** + * should the artefact collect wizard contain a step to collect a reflexion + * @return + */ + public boolean isReflexionStepEnabled(){ + return isReflexionStepEnabled; + } + + /** + * should the artefact collect wizard contain a step to ask user for copyright on content + * @return + */ + public boolean isCopyrightStepEnabled(){ + return isCopyrightStepEnabled; + } + + public void setReflexionStepEnabled(boolean isReflexionStepEnabled) { + this.isReflexionStepEnabled = isReflexionStepEnabled; + setBooleanProperty("wizard.step.reflexion", isReflexionStepEnabled, true); + } + + public void setCopyrightStepEnabled(boolean isCopyrightStepEnabled) { + this.isCopyrightStepEnabled = isCopyrightStepEnabled; + setBooleanProperty("wizard.step.copyright", isCopyrightStepEnabled, true); + } + } diff --git a/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java b/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java index 596736b33ebda35ac046994ff003b969d702ced5..24f6514f22496a39c62713f74e16252e3c595cf0 100644 --- a/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java +++ b/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java @@ -42,6 +42,7 @@ import org.olat.portfolio.manager.EPFrontendManager; import org.olat.portfolio.ui.artefacts.view.details.FileArtefactDetailsController; import org.olat.repository.RepositoryManager; import org.olat.search.service.SearchResourceContext; +import org.olat.search.service.SearchServiceFactory; import org.olat.search.service.document.file.FileDocumentFactory; /** @@ -166,9 +167,11 @@ public class FileArtefactHandler extends EPAbstractHandler<FileArtefact> { VFSItem file = ePFManager.getArtefactContainer(artefact).resolve(filename); if (file != null && file instanceof VFSLeaf) { try { - Document doc = FileDocumentFactory.createDocument(context, (VFSLeaf)file); - String content = doc.get(AbstractOlatDocument.CONTENT_FIELD_NAME); - sb.append(content); + if (SearchServiceFactory.getFileDocumentFactory().isFileSupported((VFSLeaf)file)) { + Document doc = FileDocumentFactory.createDocument(context, (VFSLeaf)file); + String content = doc.get(AbstractOlatDocument.CONTENT_FIELD_NAME); + sb.append(content); + } } catch (Exception e) { log.error("", e); } diff --git a/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java b/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java index d584d769cfed591bd5e56d6de26ada904aa7590d..e96577158e848a22e9c397f96568d4fc72304cdd 100755 --- a/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java +++ b/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java @@ -36,7 +36,7 @@ public abstract class EPAbstractMap extends EPStructureElement implements Portfo /** * @uml.property name="ownerGroup" */ - private SecurityGroup ownerGroup; + transient private SecurityGroup ownerGroup; /** diff --git a/src/main/java/org/olat/portfolio/ui/PortfolioAdminController.java b/src/main/java/org/olat/portfolio/ui/PortfolioAdminController.java index 00569a6c3c7b11b0a3aa5d373be6a6b7e2cb7f63..adddb554e89f42f9e1c64397244e34465b4a74c2 100644 --- a/src/main/java/org/olat/portfolio/ui/PortfolioAdminController.java +++ b/src/main/java/org/olat/portfolio/ui/PortfolioAdminController.java @@ -55,6 +55,10 @@ public class PortfolioAdminController extends FormBasicController { private static String[] enabledKeys = new String[]{"on"}; private String[] enabledValues; + + private MultipleSelectionElement copyrightStepCB; + + private MultipleSelectionElement reflexionStepCB; public PortfolioAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl, "adminconfig"); @@ -95,6 +99,19 @@ public class PortfolioAdminController extends FormBasicController { handlerEnabled.addActionListener(listener, FormEvent.ONCHANGE); handlersEnabled.add(handlerEnabled); } + + // configure steps in artefact collection wizard + FormLayoutContainer wizardFlc = FormLayoutContainer.createDefaultFormLayout("flc_wizard", getTranslator()); + layoutContainer.add(wizardFlc); + copyrightStepCB = uifactory.addCheckboxesHorizontal("wizard.step.copyright", wizardFlc, enabledKeys, enabledValues, null); + copyrightStepCB.select(enabledKeys[0], portfolioModule.isCopyrightStepEnabled()); + copyrightStepCB.addActionListener(listener, FormEvent.ONCHANGE); + + reflexionStepCB = uifactory.addCheckboxesHorizontal("wizard.step.reflexion", wizardFlc, enabledKeys, enabledValues, null); + reflexionStepCB.select(enabledKeys[0], portfolioModule.isReflexionStepEnabled()); + reflexionStepCB.addActionListener(listener, FormEvent.ONCHANGE); + + } } @@ -120,6 +137,12 @@ public class PortfolioAdminController extends FormBasicController { EPArtefactHandler<?> handler = (EPArtefactHandler<?>)source.getUserObject(); boolean enabled = ((MultipleSelectionElement)source).isSelected(0); portfolioModule.setEnableArtefactHandler(handler, enabled); + } else if(source == reflexionStepCB){ + boolean enabled = ((MultipleSelectionElement)source).isSelected(0); + portfolioModule.setReflexionStepEnabled(enabled); + } else if(source == copyrightStepCB){ + boolean enabled = ((MultipleSelectionElement)source).isSelected(0); + portfolioModule.setCopyrightStepEnabled(enabled); } } } diff --git a/src/main/java/org/olat/portfolio/ui/_content/adminconfig.html b/src/main/java/org/olat/portfolio/ui/_content/adminconfig.html index 6adbebf0199c2fa5d0781de2e457c46c8f629179..3f64331633111eec58c5f7fba9680caa47e12df6 100644 --- a/src/main/java/org/olat/portfolio/ui/_content/adminconfig.html +++ b/src/main/java/org/olat/portfolio/ui/_content/adminconfig.html @@ -12,4 +12,10 @@ <p>$r.translate("handlers.intro")</p> $r.render("flc_handlers") +</fieldset> + +<fieldset> + <legend>$r.translate("wizard.title")</legend> + <p>$r.translate("wizard.intro")</p> + $r.render("flc_wizard") </fieldset> \ No newline at end of file diff --git a/src/main/java/org/olat/portfolio/ui/_content/artefactsmain.html b/src/main/java/org/olat/portfolio/ui/_content/artefactsmain.html index c2256a3153fc935b89611b7e454e2590394d75b8..59a666b994fdd9a6e81af30a94e90b9948abd4b8 100644 --- a/src/main/java/org/olat/portfolio/ui/_content/artefactsmain.html +++ b/src/main/java/org/olat/portfolio/ui/_content/artefactsmain.html @@ -8,6 +8,9 @@ </div> #else $r.translate("choose.artefact.intro") + <p> + $r.render("addArtefactCtrl") + </p> #end <div class="b_ep_content"> diff --git a/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_de.properties index 2c54e39db49c9fa3bf7962f2698d873515b6082a..151dd9769d6930e37059e730f2f4e3b356ee0a45 100644 --- a/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_de.properties @@ -8,6 +8,10 @@ portfolio.module.change.warning=Damit Ihre enabled=Ein handlers.title=Artefakt-Typen handlers.intro=Wählen Sie die verfügbaren Artefakt-Typen. Ausgeschaltete Artefakte werden angezeigt, können aber nicht mehr gesammelt werden. +wizard.title=Artefakt-Sammelwerkzeug +wizard.intro=Das Artefakt-Sammelwerkzeug enthält verschiedene Schritte in einem Wizard. Einige der Schritte können Sie je nach Bedarf aktivieren oder deaktivieren. +wizard.step.reflexion=Reflexion erfassen +wizard.step.copyright=Urheberschaft vom Benutzer bestätigen lassen help.hover.portfolio=Hilfe zu "$\:chelp.module.title" chelp.module.title=ePortfolio: Konfiguration @@ -39,7 +43,7 @@ myartefacts.menu.title=Meine Artefakte myartefacts.menu.title.alt=Alle Artefakte anzeigen und verwalten myartefacts.title=$\:admin.menu.title - $\:myartefacts.menu.title myartefacts.intro=Sie können Ihre gesammelten Artefakte bearbeiten oder löschen, sowie neue hinzufügen. -choose.artefact.intro=Wählen Sie das Artefakt, welches Ihrer Sammelmappe hinzugefügt werden soll. +choose.artefact.intro=Wählen Sie das Artefakt, welches Ihrer Sammelmappe hinzugefügt werden soll oder fügen Sie ein Neues hinzu. mymaps.menu.title=Meine Sammelmappen mymaps.menu.title.alt=Alle meine Sammelmappen anzeigen und verwalten diff --git a/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_en.properties index 15aad63895fead6329b4a044c46e40c8c40618da..516ba276d482eb2317b0b780bb83cf4f9df6b76e 100644 --- a/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/portfolio/ui/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 20 19:05:56 CET 2011 +#Thu May 26 10:43:00 CEST 2011 EPStructuredMapTemplate=Portfolio template admin.menu.title=ePortfolio admin.menu.title.alt=Configure ePortfolio @@ -21,7 +21,7 @@ chelp.handlers.wiki2=A Wiki entry made by oneself chelp.module.intro=With this form you can (de)activate an ePortfolio module. This ePortfolio module enables a structured and uncontrolled work with portfolios as well as their management. It is also possible to embed portfolio tasks in courses. chelp.module.intro2=If you deactivate an ePortfolio module in an ongoing course only its functionalities will no longer be visible in the GUI. All artefacts and folders will still be available. chelp.module.title=ePortfolio\: configuration -choose.artefact.intro=Please select an artefact to be added to your accumulative folder. +choose.artefact.intro=Please select an artefact to be added to your accumulative folder. You could also create a new one. create.map=Create folder create.map.default=Create accumulative folder create.map.fromTemplate=Create folder from template @@ -53,6 +53,7 @@ othermaps.menu.title=Released accumulative folders othermaps.menu.title.alt=My accumulative folders released by others othermaps.title=$\:admin.menu.title - $\:othermaps.menu.title portfolio.intro=With this you can (de)activate the entire ePortfolio functionality in OLAT. +portfolio.module.change.warning=OLAT needs to be restarted to activate your changes throughout the system. portfolio.module.enabled=Activate ePortfolio portfolio.title=$\:admin.menu.title view.mode=View\: @@ -61,3 +62,7 @@ view.mode.table=Table viewTab.all=Artefacts viewTab.browse=Tag browser viewTab.search=Search +wizard.title=Accumulative artefact tool +wizard.intro=Within the accumulative artefact tool there are several steps to complete. Some of them are optional. You can completely disable those steps if not needed. +wizard.step.reflexion=Collect a reflexion +wizard.step.copyright=Ask user for copyright diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPArtefactWizzardStepCallback.java b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPArtefactWizzardStepCallback.java index f9cfd5bef6c763c728b339fe741a3fe1c70c9d2d..1cf109c33529e5335aa17e7963a09297ea1e77eb 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPArtefactWizzardStepCallback.java +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPArtefactWizzardStepCallback.java @@ -33,6 +33,7 @@ import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.portfolio.EPLoggingAction; +import org.olat.portfolio.PortfolioModule; import org.olat.portfolio.manager.EPFrontendManager; import org.olat.portfolio.model.artefacts.AbstractArtefact; import org.olat.portfolio.model.artefacts.FileArtefact; @@ -78,9 +79,10 @@ public class EPArtefactWizzardStepCallback implements StepRunnerCallback { hasChanges = true; AbstractArtefact locArtefact = (AbstractArtefact) runContext.get("artefact"); ePFMgr = (EPFrontendManager) CoreSpringFactory.getBean("epFrontendManager"); - + PortfolioModule portfolioModule = (PortfolioModule) CoreSpringFactory.getBean("portfolioModule"); + // set the defined signature level, if its not from inside olat - if (locArtefact.getSignature() < 0 && runContext.containsKey("copyright.accepted") && (Boolean) runContext.get("copyright.accepted")){ + if (locArtefact.getSignature() < 0 && ( (runContext.containsKey("copyright.accepted") && (Boolean) runContext.get("copyright.accepted")) || !portfolioModule.isCopyrightStepEnabled() ) ) { locArtefact.setSignature(-1 * locArtefact.getSignature()); } diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep01.java b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep01.java index 938a4cf2881a7de5fbdd3e04cc4543489e12dce3..c320d2931a7b882f713f39866931bcc3dfde22ea 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep01.java +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep01.java @@ -20,6 +20,7 @@ */ package org.olat.portfolio.ui.artefacts.collect; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; @@ -28,6 +29,7 @@ import org.olat.core.gui.control.generic.wizard.BasicStep; import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.portfolio.PortfolioModule; import org.olat.portfolio.model.artefacts.AbstractArtefact; /** @@ -47,12 +49,16 @@ public class EPCollectStep01 extends BasicStep { super(ureq); this.artefact = artefact; setI18nTitleAndDescr("step1.description", "step1.short.descr"); - //signature > 0 means, collection wizzard can be sure its from OLAT, < 0 means get an approval by user (the target value is the negative one) - if (artefact.getSignature() > 0 ){ + PortfolioModule portfolioModule = (PortfolioModule) CoreSpringFactory.getBean("portfolioModule"); + //signature > 0 means, collection wizard can be sure its from OLAT, < 0 means get an approval by user (the target value is the negative one) + if (!portfolioModule.isCopyrightStepEnabled() && !portfolioModule.isReflexionStepEnabled()){ + // skip copyright AND reflexion step + setNextStep(new EPCollectStep04(ureq, artefact)); + } else if (artefact.getSignature() > 0 || !portfolioModule.isCopyrightStepEnabled()){ setNextStep(new EPCollectStep03(ureq, artefact)); - } else { + } else if (portfolioModule.isCopyrightStepEnabled() ){ setNextStep(new EPCollectStep02(ureq, artefact)); - } + } } /** diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep02.java b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep02.java index 7c08dcc7b1836f2f82397f28edb6fe7622f272d2..c6fd18958ee81076b7ac37259fda013ace537353 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep02.java +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCollectStep02.java @@ -20,6 +20,7 @@ */ package org.olat.portfolio.ui.artefacts.collect; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; @@ -28,6 +29,7 @@ import org.olat.core.gui.control.generic.wizard.BasicStep; import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig; import org.olat.core.gui.control.generic.wizard.StepFormController; import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.portfolio.PortfolioModule; import org.olat.portfolio.model.artefacts.AbstractArtefact; /** @@ -47,7 +49,13 @@ public class EPCollectStep02 extends BasicStep { super(ureq); this.artefact = artefact; setI18nTitleAndDescr("step2.description", "step2.short.descr"); - setNextStep(new EPCollectStep03(ureq, artefact)); + PortfolioModule portfolioModule = (PortfolioModule) CoreSpringFactory.getBean("portfolioModule"); + if (!portfolioModule.isCopyrightStepEnabled() && !portfolioModule.isReflexionStepEnabled()){ + // skip copyright AND reflexion step + setNextStep(new EPCollectStep04(ureq, artefact)); + } else { + setNextStep(new EPCollectStep03(ureq, artefact)); + } } /** diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCreateFileArtefactStepForm00.java b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCreateFileArtefactStepForm00.java index 99293bf2b53a833275e5c562e8261808b0af8558..bbb39b46e906288180863efa94f182dd2415246c 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCreateFileArtefactStepForm00.java +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/EPCreateFileArtefactStepForm00.java @@ -20,6 +20,9 @@ */ package org.olat.portfolio.ui.artefacts.collect; +import java.util.HashSet; +import java.util.Set; + import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -32,6 +35,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; +import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; @@ -91,6 +95,19 @@ public class EPCreateFileArtefactStepForm00 extends StepFormBasicController { } } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + if (fileupload.isUploadSuccess()){ + String type = fileupload.getUploadMimeType(); + String fileName = fileupload.getUploadFileName(); + if (StringHelper.containsNonWhitespace(type) && StringHelper.containsNonWhitespace(fileName) && fileName.contains(".")) { + return true; + } + fileupload.setErrorKey("unsupported.filetype", null); + } + return false; + } /** * @see org.olat.core.gui.control.generic.wizard.StepFormBasicController#formOK(org.olat.core.gui.UserRequest) diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_de.properties index 44e38b0876065d0677c5e5294406ea1f3bf1b07a..722e21a4633892cb95fe624eb280b018fef90d7b 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_de.properties @@ -65,4 +65,5 @@ file.upload.too.big=Die hochgeladene Datei ist zu gross. Es sind maximal {0} KB create.blog.artefact.wizzard.title=Artefakttyp Lerntagebuch erstellen map.not.choosable=Eine Sammelmappe kann nicht als Ziel gewählt werden. move.artefact.descr=Wählen Sie den Zielort (Seite oder Strukturelement), an den dieses Artefakt verschoben werden soll. -move.artefact.actual.node=Aktuelle Verlinkung \ No newline at end of file +move.artefact.actual.node=Aktuelle Verlinkung +unsupported.filetype=Bitte eine Datei mit gültiger Dateiendung hochladen. \ No newline at end of file diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_en.properties index 15b79c38116f977fef7910f5cad66fb5808c471e..cc8b610e82f7b6e3bbdebaa6f274e057c9542f14 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/portfolio/ui/artefacts/collect/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Thu Jan 20 19:09:16 CET 2011 +#Thu May 26 10:43:56 CEST 2011 add.artefact=Add artefact add.artefact.blog=Create learning journal/blog add.artefact.import=Import artefact/folder @@ -28,6 +28,9 @@ file.artefact.source.info=File upload file.upload=Select file file.upload.no.file=At least one file has to be uploaded. file.upload.too.big=The file uploaded is too big. Only a max. of {0} KB is allowed. +map.not.choosable=A map can not be selected as target. +move.artefact.actual.node=Current linking +move.artefact.descr=Select the target (page or structural element) where this artifact should be moved to. no.map.as.target=Do not select any accumulative folders no.map.as.target.desc=Please select this element if you don't want to add your artefact to a folder. It is always possible to add an artefact later on. no.structs.available=There aren't any accumulative folders to which you could add this artefact. @@ -56,4 +59,5 @@ tag.input.hint=Click here to add new tags. tagging.intro=Tag your artefacts (provide keywords) in order to better find them later on. You can indicate more than one tag. Those already used will appear as suggestion while typing. Please separate tags by commas. tagging.intro.existing=Select tags already used from the following list. text.artefact.source.info=Text upload +unsupported.filetype=Please upload a file with a valid filename-extension. users.tags=50 tags you use most\: diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties index f0676a05ac786e6849b748f405a54534d01b2701..7b146ee6309d29b6f17b859d58fc272472af3a7c 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_i18n/LocalStrings_en.properties @@ -1,12 +1,16 @@ -#Thu Jan 20 19:14:48 CET 2011 +#Thu May 26 10:45:05 CEST 2011 artefact.amount=Number of artefacts found\: {0} artefact.author=Author artefact.date=Date artefact.description=Description artefact.description.too.long=The $\:artefact.description must not exceed {0} characters. artefact.handlerdetails=Type details +artefact.move.title=Select a target to move this artifact +artefact.moved=The artifact was successfully moved to {0} artefact.no.source=not available artefact.open.source=regarding source +artefact.options.move=Move +artefact.options.title=Select an operation for this artifact artefact.reflexion=Reflection artefact.reflexion.original=Original reflection artefact.reflexion.view.descr=Here you can see the original reflection provided when creating this artefact. You can also reflect on links to folders. @@ -30,6 +34,7 @@ display.option.submit=Submit and save display.option.title=$\:detail.options info.no.reflexion.yet=There aren't any reflections yet regarding the selection of this artefact in your accumulative folder. The default setting is this artefact's reflection. no.artefacts.found=You haven't collected any artefacts yet. Upload one or collect them within OLAT. +option.link=More options... page.links=Page reflexion.artefact=Reflection of artefact reflexion.link=Reflection on link to an accumulative folder @@ -37,6 +42,7 @@ remove.from.map=Remove from folder small.details.link=View table.empty=$\:no.artefacts.found table.header.choose=Select +table.header.move=Move table.header.reflexion=Reflection table.header.type=Type table.header.unlink=Remove diff --git a/src/main/java/org/olat/portfolio/ui/structel/_content/multiMaps.html b/src/main/java/org/olat/portfolio/ui/structel/_content/multiMaps.html index c074af759627d3578abac12e5ea59700c61ad7e4..08bc0cf5249963a40ba538da9c29d35e11cd3e4b 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/_content/multiMaps.html +++ b/src/main/java/org/olat/portfolio/ui/structel/_content/multiMaps.html @@ -23,7 +23,7 @@ #if($map.getClass().getSimpleName() == "EPStructuredMapTemplate") #set($addTempStamp = "template") #end <li class="$!mapStyles.get($index) $!addTempStamp"> <h4>$map.title</h4> - $map.shortenedDescription + <div class="b_map_descr">$map.shortenedDescription</div> <div class="b_map_info"> #if ($owners.get($index)) <p>$r.translate("map.owners", $owners.get($index)) </p> #end <p>$amounts.get($index) diff --git a/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_de.properties index 7264efbbd69de65010c7c6dc1203e3b6f6f2cf00..ebf26847a66b41c27a4a6de58c62603c27ff4cdf 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_de.properties @@ -52,7 +52,7 @@ map.submit.assess.title=Portfolioaufgabe beenden map.submit.assess.description=Wollen Sie Ihre Portfolioaufgabe wirklich abgeben? Danach können Sie sie nicht mehr bearbeiten. map.submit.assess.restriction.error.title=Ihre Portfolioaufgabe erfüllt noch nicht alle Sammeleinschränkungen. map.submit.assess.restriction.error.description=Ihre Portfolioaufgabe erfüllt nicht alle Sammeleinschränkungen. Wollen Sie Ihre Portfolioaufgabe wirklich abgeben? Danach können Sie sie nicht mehr bearbeiten. -map.submit.assess.restriction.error.hint=Sie haben bisher $:map.restriction.stats. +map.submit.assess.restriction.error.hint=Sie haben bisher $:map.restriction.stats map.editButton=Editor map.editButton.on=$\:map.editButton öffnen map.editButton.off=$\:map.editButton schliessen @@ -95,6 +95,18 @@ synced.map.success=Diese Mappe wurde mit der urspr map.style.default=Schlicht map.style.comic=Comic map.style.leather=Leder +map.style.epmst-green=Grün +map.style.epmst-green2=Grün 2 +map.style.epmst-green3=Grün 3 +map.style.epmst-green4=Grün 4 +map.style.epmst-red=Rot +map.style.epmst-red2=Rot 2 +map.style.epmst-red3=Rot 3 +map.style.epmst-red4=Rot 4 +map.style.epmst-blue=Blau +map.style.epmst-blue2=Blau 2 +map.style.epmst-blue3=Blau 3 +map.style.epmst-blue4=Blau 4 map.style=Layout wählen map.deadline=Abgabetermin: {0} map.viewModeButton=Ansicht wählen @@ -118,6 +130,4 @@ chelp.editor.hoover=Hilfe zum Editieren von Sammelmappen chelp.editorwithrestrictions.hoover=Hilfe zum Editieren von Portfoliovorlagen map.cannot.submit.nomore.coursenode=Der Kursknoten dieser Portfolioaufgabe wurde gelöscht. Diese Sammelmappe kann daher nicht abgegeben werden und bleibt zur Bearbeitung geöffnet. map.restriction.stats={0} von {1} zu sammelnden Artefakten verlinkt. -map.restriction.overview=Noch {0} Artefakte zu sammeln - - +map.restriction.overview=Noch {0} Artefakte zu sammeln \ No newline at end of file diff --git a/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_en.properties index 2f9c817dc0eb7c3ad5c67c0eeb9de390c8926d3a..46b868cae098b87ae994fcedc66093481f6ce881 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/portfolio/ui/structel/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Wed Jan 26 18:56:53 CET 2011 +#Thu May 26 10:55:20 CEST 2011 add.map=Link accumulative folder add.page=Add page add.struct=Add structural element @@ -43,6 +43,7 @@ delete.used.map.error=You can't delete a portfolio task as long as there is a co from.date.behind.to=The end date can't be earlier than the start date. linkArtefact.tooltip=Select the artefact to be linked here. map.already.edited=This folder is being edited by another user. +map.cannot.submit.nomore.coursenode=The course element of this portfolio task has been deleted. The map can therefore not be handed in and stays opened for further editing. map.contains=Contains {0} pages and {1} artefacts map.contains.pages=Contains {0} pages map.copy.of=Copy of {0} @@ -54,6 +55,8 @@ map.editButton.off=Close $\:map.editButton map.editButton.on=Open $\:map.editButton map.is.closed.hint=Folder closed map.owners=Author\: {0} +map.restriction.overview={0} artifacts left to collect +map.restriction.stats={0} of {1} to be collected artifacts are linked. map.share=Release folder map.share.add.policy=Create release rule map.share.date.invalid=Please indicate a valid date. @@ -91,9 +94,22 @@ map.style=Select layout map.style.comic=Comic map.style.default=Simple map.style.leather=Leather +map.style.epmst-green=Green +map.style.epmst-green2=Green 2 +map.style.epmst-green3=Green 3 +map.style.epmst-green4=Green 4 +map.style.epmst-red=Red +map.style.epmst-red2=Red 2 +map.style.epmst-red3=Red 3 +map.style.epmst-red4=Red 4 +map.style.epmst-blue=Blue +map.style.epmst-blue2=Blue 2 +map.style.epmst-blue3=Blue 3 +map.style.epmst-blue4=Blue 4 map.submit.assess=Submit task map.submit.assess.description=Do you really want to submit your portfolio task? You will no longer be able to edit it afterwards. map.submit.assess.restriction.error.description=Your portfolio task doesn't conform to all restrictions. Do you really want to submit it? You will no longer be able to edit it afterwards. +map.submit.assess.restriction.error.hint=You have $\:map.restriction.stats so far map.submit.assess.restriction.error.title=Your portfolio task doesn't yet conform to all restrictions. map.submit.assess.title=Finish portfolio task map.title=Title of accumulative folder diff --git a/src/main/java/org/olat/portfolio/ui/structel/edit/_content/toc.html b/src/main/java/org/olat/portfolio/ui/structel/edit/_content/toc.html index 06cc5d345e901d78460ad0cfb5054e7e18e08df8..8948c3d2fd71ece1d24b349bb7b01f75588b5f9a 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/edit/_content/toc.html +++ b/src/main/java/org/olat/portfolio/ui/structel/edit/_content/toc.html @@ -8,6 +8,47 @@ $r.render("deleteButton") </div> +<script type="text/javascript"> +/* <![CDATA[ */ +function myjsCallback(dragOverEvent) { + // the following members can be getted from the dragOverEvent: + // tree - The TreePanel + // target - The node being targeted for the drop + // data - The drag data from the drag source + // point - The point of the drop - append, above or below + // source - The drag source + // rawEvent - Raw mouse event + // dropNode - Drop node(s) provided by the source. + // cancel - Set this to true to signal drop not allowed. + + var node = dragOverEvent.dropNode; + var nodeDeep = node.getDepth(); + + // append -> add as child; above -> at same level above; below -> at same level above + var newParent = (dragOverEvent.point == 'append' ? dragOverEvent.target : dragOverEvent.target.parentNode); + + var allowChild = newParent.allowChildren; + // no drop on artefacts + if (!allowChild) return false; + var parentDeep = newParent.getDepth(); + + //console.log(node.attributes.text + " nodeDeep: " + nodeDeep + " targetDeep: "+ parentDeep); + + // no drop on root + if (parentDeep == 0) return false; + // no drop on map/root + if (nodeDeep == 3 && (parentDeep == 1 || parentDeep == 0)) return false; + // moving a page/struct to struct is not possible + if (nodeDeep <= 3 && parentDeep==3 && node.allowChildren) return false; + // moving a page to page is not possible + if (nodeDeep == 2 && parentDeep==2 && node.allowChildren) return false; + + return true; +} +/* ]]> */ +</script> + + <script type="text/javascript"> /* <![CDATA[ */ function myjsCallback(dragOverEvent) { diff --git a/src/main/java/org/olat/registration/EmailOrUsernameFormController.java b/src/main/java/org/olat/registration/EmailOrUsernameFormController.java index 5c88ba023b0278e9dfec440b88e503f65efb7c22..c0c648e34fb8daf8954548558ccc1dcd162e23e7 100644 --- a/src/main/java/org/olat/registration/EmailOrUsernameFormController.java +++ b/src/main/java/org/olat/registration/EmailOrUsernameFormController.java @@ -40,10 +40,12 @@ import org.olat.core.gui.control.WindowControl; */ public class EmailOrUsernameFormController extends FormBasicController { + private final String initialEmail;//fxdiff FXOLAT-113: business path in DMZ private TextElement emailOrUsername; - public EmailOrUsernameFormController(UserRequest ureq, WindowControl wControl) { + public EmailOrUsernameFormController(UserRequest ureq, WindowControl wControl, String initialEmail) { super(ureq, wControl); + this.initialEmail = initialEmail; initForm(ureq); } @@ -73,7 +75,8 @@ public class EmailOrUsernameFormController extends FormBasicController { * org.olat.core.gui.control.Controller, org.olat.core.gui.UserRequest) */ protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - emailOrUsername = uifactory.addTextElement("emailOrUsername", "email.or.username", -1, null, formLayout); + //fxdiff FXOLAT-113: business path in DMZ + emailOrUsername = uifactory.addTextElement("emailOrUsername", "email.or.username", -1, initialEmail, formLayout); emailOrUsername.setMandatory(true); emailOrUsername.setNotEmptyCheck("email.or.username.maynotbeempty"); diff --git a/src/main/java/org/olat/registration/PwChangeController.java b/src/main/java/org/olat/registration/PwChangeController.java index a16b6a087f31aa491d036a48c1b58c810dd236a4..698fb167279daaab8f5290342eaa0320be56bd41 100644 --- a/src/main/java/org/olat/registration/PwChangeController.java +++ b/src/main/java/org/olat/registration/PwChangeController.java @@ -54,6 +54,7 @@ import org.olat.core.util.mail.MailTemplate; import org.olat.core.util.mail.MailerResult; import org.olat.core.util.mail.MailerWithTemplate; import org.olat.user.UserManager; +import org.olat.user.UserModule; /** * Description:<br> @@ -81,6 +82,16 @@ public class PwChangeController extends BasicController { * @param wControl */ public PwChangeController(UserRequest ureq, WindowControl wControl) { + this(ureq, wControl, null); + } + + /** + * Controller to change a user's password. + * @param ureq + * @param wControl + */ + //fxdiff FXOLAT-113: business path in DMZ + public PwChangeController(UserRequest ureq, WindowControl wControl, String initialEmail) { super(ureq, wControl); myContent = createVelocityContainer("pwchange"); wic = new WizardInfoController(ureq, 4); @@ -90,7 +101,8 @@ public class PwChangeController extends BasicController { pwKey = ureq.getHttpReq().getParameter("key"); if (pwKey == null || pwKey.equals("")) { // no temporarykey is given, we assume step 1 - createEmailForm(ureq, wControl); + //fxdiff FXOLAT-113: business path in DMZ + createEmailForm(ureq, wControl, initialEmail); putInitialPanel(myContent); } else { // we check if given key is a valid temporary key @@ -99,7 +111,8 @@ public class PwChangeController extends BasicController { if (tempKey == null) { // error, there should be an entry getWindowControl().setError(translate("pwkey.missingentry")); - createEmailForm(ureq, wControl); + //fxdiff FXOLAT-113: business path in DMZ + createEmailForm(ureq, wControl, initialEmail); // load view in layout LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), null, null, myContent, null); putInitialPanel(layoutCtr.getInitialComponent()); @@ -120,11 +133,12 @@ public class PwChangeController extends BasicController { /** * just needed for creating EmailForm */ - private void createEmailForm(UserRequest ureq, WindowControl wControl) { + //fxdiff FXOLAT-113: business path in DMZ + private void createEmailForm(UserRequest ureq, WindowControl wControl, String initialEmail) { myContent.contextPut("title", translate("step1.pw.title")); myContent.contextPut("text", translate("step1.pw.text")); removeAsListenerAndDispose(emailOrUsernameCtr); - emailOrUsernameCtr = new EmailOrUsernameFormController(ureq, wControl); + emailOrUsernameCtr = new EmailOrUsernameFormController(ureq, wControl, initialEmail); listenTo(emailOrUsernameCtr); pwarea.setContent(emailOrUsernameCtr.getInitialComponent()); } @@ -195,7 +209,7 @@ public class PwChangeController extends BasicController { if (identity != null) { // check if user has an OLAT provider token, otherwhise a pwd change makes no sense Authentication auth = BaseSecurityManager.getInstance().findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier()); - if (auth == null) { + if (auth == null || !UserModule.isPwdchangeallowed(identity)) { getWindowControl().setWarning(translate("password.cantchange")); return; } diff --git a/src/main/java/org/olat/registration/RegistrationController.java b/src/main/java/org/olat/registration/RegistrationController.java index 0e8194e68a3ffba5bad29523b45f03d91f8d560c..e66a22f78ae5b8736244e7ee97ccbec239080137 100644 --- a/src/main/java/org/olat/registration/RegistrationController.java +++ b/src/main/java/org/olat/registration/RegistrationController.java @@ -113,7 +113,13 @@ public class RegistrationController extends BasicController { // no temporary key is given, we assume step 1. If this is the case, we // render in a modal dialog, no need to add the 3cols layout controller // wrapper - createLanguageForm(ureq, wControl); + //fxdiff FXOLAT-113: business path in DMZ + if(I18nModule.getEnabledLanguageKeys().size() == 1) { + wic.setCurStep(2); + createEmailForm(ureq); + } else { + createLanguageForm(ureq, wControl); + } putInitialPanel(myContent); } else { // we check if given key is a valid temporary key @@ -122,7 +128,13 @@ public class RegistrationController extends BasicController { if (tempKey == null) { // error, there should be an entry showError("regkey.missingentry"); - createLanguageForm(ureq, wControl); + //fxdiff FXOLAT-113: business path in DMZ + if(I18nModule.getEnabledLanguageKeys().size() == 1) { + wic.setCurStep(2); + createEmailForm(ureq); + } else { + createLanguageForm(ureq, wControl); + } } else { wic.setCurStep(3); myContent.contextPut("pwdhelp", translate("pwdhelp")); diff --git a/src/main/java/org/olat/registration/_spring/registrationContext.xml b/src/main/java/org/olat/registration/_spring/registrationContext.xml index 5a2fa1a081ecb32518c09395f823516935c3de45..535a7ee2249f2a75737ed4af638845b95c56eca6 100644 --- a/src/main/java/org/olat/registration/_spring/registrationContext.xml +++ b/src/main/java/org/olat/registration/_spring/registrationContext.xml @@ -45,7 +45,7 @@ <bean id="registrationPresetUsername.byEmail" class="org.olat.registration.SelfRegistrationUserNameFromEmailUserNameCreationInterceptor" lazy-init="true"> - <property name="allowChangeOfUsername" value="false"/> + <property name="allowChangeOfUsername" value="${registration.preset.username.allowChanges}"/> <property name="emailDomain" value="${registration.preset.username.domain}"/> </bean> diff --git a/src/main/java/org/olat/repository/controllers/RepositoryEditPropertiesController.java b/src/main/java/org/olat/repository/controllers/RepositoryEditPropertiesController.java index 7c1c00849986c0d13b57d792744792193a65f2be..1677f58b843a77ccf6aa1e20f8b985c48d92f492 100644 --- a/src/main/java/org/olat/repository/controllers/RepositoryEditPropertiesController.java +++ b/src/main/java/org/olat/repository/controllers/RepositoryEditPropertiesController.java @@ -63,11 +63,12 @@ import org.olat.course.config.ui.CourseCalendarConfigController; import org.olat.course.config.ui.CourseChatSettingController; import org.olat.course.config.ui.CourseConfigGlossaryController; import org.olat.course.config.ui.CourseEfficencyStatementController; -import org.olat.course.config.ui.CourseLayoutController; import org.olat.course.config.ui.CourseSharedFolderController; +import org.olat.course.config.ui.courselayout.CourseLayoutGeneratorController; import org.olat.course.run.RunMainController; import org.olat.fileresource.types.GlossaryResource; import org.olat.instantMessaging.InstantMessagingModule; +import org.olat.modules.glossary.GlossaryEditSettingsController; import org.olat.modules.glossary.GlossaryManager; import org.olat.modules.glossary.GlossaryRegisterSettingsController; import org.olat.repository.PropPupForm; @@ -98,7 +99,7 @@ public class RepositoryEditPropertiesController extends BasicController { private PropPupForm propPupForm; private CourseChatSettingController ccc; private CourseSharedFolderController csfC; - private CourseLayoutController clayoutC; + private CourseLayoutGeneratorController clayoutC; private CourseEfficencyStatementController ceffC; private CourseCalendarConfigController calCfgCtr; private CourseConfigGlossaryController cglosCtr; @@ -200,10 +201,8 @@ public class RepositoryEditPropertiesController extends BasicController { // push on controller stack and register <this> as controllerlistener tabbedPane.addTab(translate("tab.chat"), ccc.getInitialComponent()); } - VFSContainer namedContainerImpl = new NamedContainerImpl( translate("coursefolder", course.getCourseTitle()), - course.getCourseFolderContainer()); - clayoutC = new CourseLayoutController(ureq, getWindowControl(), changedCourseConfig, namedContainerImpl); - this.listenTo(clayoutC); + clayoutC = new CourseLayoutGeneratorController(ureq, getWindowControl(), changedCourseConfig, course.getCourseEnvironment()); + listenTo(clayoutC); tabbedPane.addTab(translate("tab.layout"), clayoutC.getInitialComponent()); csfC = new CourseSharedFolderController(ureq, getWindowControl(), changedCourseConfig); @@ -220,11 +219,15 @@ public class RepositoryEditPropertiesController extends BasicController { cglosCtr = new CourseConfigGlossaryController(ureq, getWindowControl(), changedCourseConfig, course.getResourceableId()); this.listenTo(cglosCtr); - tabbedPane.addTab(translate("tab.glossary"), cglosCtr.getInitialComponent()); + tabbedPane.addTab(translate("tab.glossary"), cglosCtr.getInitialComponent()); } } else if (repositoryEntry.getOlatResource().getResourceableTypeName().equals(GlossaryResource.TYPE_NAME)){ GlossaryRegisterSettingsController glossRegisterSetCtr = new GlossaryRegisterSettingsController(ureq, getWindowControl(), repositoryEntry.getOlatResource()); tabbedPane.addTab(translate("tab.glossary.register"), glossRegisterSetCtr.getInitialComponent()); + + GlossaryEditSettingsController glossEditCtr = new GlossaryEditSettingsController(ureq, getWindowControl(), repositoryEntry.getOlatResource()); + tabbedPane.addTab(translate("tab.glossary.edit"), glossEditCtr.getInitialComponent()); + } bgVC.put("descTB", tabbedPane); diff --git a/src/main/java/org/olat/repository/handlers/GlossaryHandler.java b/src/main/java/org/olat/repository/handlers/GlossaryHandler.java index e7ecd9d8356fb01d91b9f52c653b53eb1efc31fc..40cc7cfc1e673b4be8e773c26efdd37eef5fe58d 100644 --- a/src/main/java/org/olat/repository/handlers/GlossaryHandler.java +++ b/src/main/java/org/olat/repository/handlers/GlossaryHandler.java @@ -24,9 +24,14 @@ package org.olat.repository.handlers; import java.util.ArrayList; import java.util.List; +import java.util.Properties; +import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; +import org.olat.core.commons.modules.glossary.GlossaryItemManager; import org.olat.core.commons.modules.glossary.GlossaryMainController; +import org.olat.core.commons.modules.glossary.GlossarySecurityCallback; +import org.olat.core.commons.modules.glossary.GlossarySecurityCallbackImpl; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; @@ -137,7 +142,19 @@ public class GlossaryHandler implements RepositoryHandler { */ public MainLayoutController createLaunchController(OLATResourceable res, String initialViewIdentifier, UserRequest ureq, WindowControl wControl) { VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(res); - GlossaryMainController gctr = new GlossaryMainController(wControl, ureq, glossaryFolder, res, false); + + Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); + boolean editableByUser = "true".equals(glossProps.getProperty(GlossaryItemManager.EDIT_USERS)); + RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(res, true); + boolean owner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(ureq.getIdentity(), re.getOwnerGroup()); + + GlossarySecurityCallback secCallback; + if (ureq.getUserSession().getRoles().isGuestOnly()) { + secCallback = new GlossarySecurityCallbackImpl(); + } else { + secCallback = new GlossarySecurityCallbackImpl(false, owner, editableByUser, ureq.getIdentity().getKey()); + } + GlossaryMainController gctr = new GlossaryMainController(wControl, ureq, glossaryFolder, res, secCallback, false); // use on column layout LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, null, null, gctr.getInitialComponent(), null); layoutCtr.addDisposableChildController(gctr); // dispose content on layout dispose @@ -160,7 +177,16 @@ public class GlossaryHandler implements RepositoryHandler { */ public Controller createEditorController(OLATResourceable res, UserRequest ureq, WindowControl wControl) { VFSContainer glossaryFolder = GlossaryManager.getInstance().getGlossaryRootFolder(res); - GlossaryMainController gctr = new GlossaryMainController(wControl, ureq, glossaryFolder, res, true); + + Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder); + boolean editableByUser = "true".equals(glossProps.getProperty(GlossaryItemManager.EDIT_USERS)); + GlossarySecurityCallback secCallback; + if (ureq.getUserSession().getRoles().isGuestOnly()) { + secCallback = new GlossarySecurityCallbackImpl(); + } else { + secCallback = new GlossarySecurityCallbackImpl(true, true, editableByUser, ureq.getIdentity().getKey()); + } + GlossaryMainController gctr = new GlossaryMainController(wControl, ureq, glossaryFolder, res, secCallback, false); // use on column layout LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, null, null, gctr.getInitialComponent(), null); layoutCtr.addDisposableChildController(gctr); // dispose content on layout dispose diff --git a/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java b/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java index f62adfaa5ed4d1f7599543bafdb715459436c60a..78a9f17442dbdc16f80c0bcce9e9382623c5a72e 100644 --- a/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java +++ b/src/main/java/org/olat/repository/handlers/SCORMCPHandler.java @@ -113,7 +113,7 @@ public class SCORMCPHandler extends FileHandler implements RepositoryHandler { ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapScormRepositoryEntry(re)); } //fxdiff FXOLAT-116: SCORM improvements - MainLayoutController realController = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, wControl, true, null, cpRoot, res.getResourceableId().toString(), null, "browse", "no-credit", false, false); + MainLayoutController realController = ScormMainManager.getInstance().createScormAPIandDisplayController(ureq, wControl, true, null, cpRoot, res.getResourceableId().toString(), null, "browse", "no-credit", false, false, false); //fxdiff VCRP-1: access control of learn resources RepositoryMainAccessControllerWrapper wrapper = new RepositoryMainAccessControllerWrapper(ureq, wControl, res, realController); return wrapper; diff --git a/src/main/java/org/olat/search/_spring/searchContext.xml b/src/main/java/org/olat/search/_spring/searchContext.xml index c65623e92e84d32992d2020c983977208125d3af..45290d7a9df329e181492b93e77d937e31e63c0b 100644 --- a/src/main/java/org/olat/search/_spring/searchContext.xml +++ b/src/main/java/org/olat/search/_spring/searchContext.xml @@ -70,8 +70,9 @@ <property name="arguments"> <value> generateIndexAtStartup=${generate.index.at.startup} - <!-- restartInterval in ms (0=no restart) --> - restartInterval=0 + <!-- restartInterval in ms (0=no restart) + fxdiff FXOLAT-221: start indexer at different times for each instance --> + restartInterval=${search.indexing.restart.interval} tempIndexPath=temp_search_index tempSpellCheckPath=temp_spellcheck_index pdfTextBufferPath=temp_pdf_text_buf @@ -84,7 +85,7 @@ maxFileSize=10485760 <!-- Control indexer prozess --> numberIndexWriter=0 - folderPoolSize=10 + folderPoolSize=4 <!-- Define automatic restart time window e.g. 01:00-02:59 restartWindowStart=1 restartWindowEnd=3 --> restartWindowStart=${restart.window.start} restartWindowEnd=${restart.window.end} @@ -95,6 +96,7 @@ <!-- updater runs every xx ms (0=stopped) --> <!-- The updater is NOT implemented for all index elements, do not use it for now! --> updateInterval=0 + ramBufferSizeMb=16 </value> </property> diff --git a/src/main/java/org/olat/search/service/document/ContextHelpDocument.java b/src/main/java/org/olat/search/service/document/ContextHelpDocument.java index 562ca49d51118546565d0e0b70dd04eb5ca265f0..bd66c345d1fbac5195e427d4a5e8a1493aaa2123 100644 --- a/src/main/java/org/olat/search/service/document/ContextHelpDocument.java +++ b/src/main/java/org/olat/search/service/document/ContextHelpDocument.java @@ -83,16 +83,21 @@ public class ContextHelpDocument extends OlatDocument { contextHelpDocument.setCssIcon("b_contexthelp_icon"); contextHelpDocument.setTitle(pageTranslator.translate("chelp." + page.split("\\.")[0] + ".title") + " (" + lang + ")"); - VelocityHelper vh = VelocityHelper.getInstance(); - String mergedContent = vh.mergeContent(pagePath, ctx, null); - // Remove any HTML stuff from page - Matcher m = HTML_TAG_PATTERN.matcher(mergedContent); - mergedContent = m.replaceAll(" "); - // Remove all   - m = HTML_SPACE_PATTERN.matcher(mergedContent); - mergedContent = m.replaceAll(" "); - // Finally set content - contextHelpDocument.setContent(mergedContent); + try { + VelocityHelper vh = VelocityHelper.getInstance(); + String mergedContent = vh.mergeContent(pagePath, ctx, null); + // Remove any HTML stuff from page + Matcher m = HTML_TAG_PATTERN.matcher(mergedContent); + mergedContent = m.replaceAll(" "); + // Remove all   + m = HTML_SPACE_PATTERN.matcher(mergedContent); + mergedContent = m.replaceAll(" "); + // Finally set content + contextHelpDocument.setContent(mergedContent); + } catch (Exception e) { + log.error("Error indexing context help: " + bundleName + " / " + page + " in " + pageTranslator.getLocale(), e); + contextHelpDocument.setContent(""); + } if (log.isDebug()) log.debug(contextHelpDocument.toString()); return contextHelpDocument.getLuceneDocument(); diff --git a/src/main/java/org/olat/search/service/document/file/ExcelOOXMLDocument.java b/src/main/java/org/olat/search/service/document/file/ExcelOOXMLDocument.java index 862f295d74132b85e928bf1b38c85ca59de2a825..1809365700def0aa1f469f1a871ab35ff5f9bf75 100644 --- a/src/main/java/org/olat/search/service/document/file/ExcelOOXMLDocument.java +++ b/src/main/java/org/olat/search/service/document/file/ExcelOOXMLDocument.java @@ -1,28 +1,30 @@ /** -* 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. -* <p> -*/ + * 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. + * <p> + */ package org.olat.search.service.document.file; import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; import java.util.Iterator; import org.apache.lucene.document.Document; @@ -30,23 +32,42 @@ import org.apache.poi.POIXMLDocument; import org.apache.poi.POIXMLTextExtractor; import org.apache.poi.extractor.ExtractorFactory; import org.apache.poi.hssf.extractor.ExcelExtractor; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.HeaderFooter; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; +import org.apache.poi.xssf.eventusermodel.XSSFReader; +import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.VFSLeaf; import org.olat.search.service.SearchResourceContext; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHeaderFooter; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; /** * Description:<br> * Parse the Excel XML document (.xslx) with Apache POI * <P> - * Initial Date: 14 dec. 2009 <br> + * Initial Date: 14 dec. 2009 <br> + * * @author srosse, stephane.rosse@frentix.com */ public class ExcelOOXMLDocument extends FileDocument { @@ -68,75 +89,182 @@ public class ExcelOOXMLDocument extends FileDocument { return excelDocument.getLuceneDocument(); } - protected String readContent(VFSLeaf leaf) throws IOException, DocumentException { + protected String readContent(VFSLeaf leaf) throws DocumentException { BufferedInputStream bis = null; StringBuilder buffy = new StringBuilder(); try { bis = new BufferedInputStream(leaf.getInputStream()); - POIXMLTextExtractor extractor = (POIXMLTextExtractor) ExtractorFactory.createExtractor(bis); - POIXMLDocument document = extractor.getDocument(); - if (document instanceof XSSFWorkbook) { - XSSFWorkbook xDocument = (XSSFWorkbook) document; - extractContent(buffy, xDocument); + OPCPackage pkg = OPCPackage.open(bis); + XSSFReader r = new XSSFReader(pkg); + SharedStringsTable sst = r.getSharedStringsTable(); + XMLReader parser = XMLReaderFactory.createXMLReader(); + StylesTable styles = r.getStylesTable(); + MySheetHandler handler = new MySheetHandler(buffy, styles, sst); + parser.setContentHandler(handler); + + for (XSSFReader.SheetIterator it = (XSSFReader.SheetIterator) r.getSheetsData(); it.hasNext();) { + InputStream sheet = it.next(); + InputSource sheetSource = new InputSource(sheet); + parser.parse(sheetSource); + sheet.close(); } return buffy.toString(); } catch (Exception e) { - throw new DocumentException(e.getMessage()); + throw new DocumentException(e.getMessage(), e); } finally { - if (bis != null) { - bis.close(); - } + FileUtils.closeSafely(bis); } } - private void extractContent(StringBuilder buffy, XSSFWorkbook document) { - for (int i = 0; i < document.getNumberOfSheets(); i++) { - XSSFSheet sheet = document.getSheetAt(i); - buffy.append(document.getSheetName(i)).append(' '); - - // Header(s), if present - extractHeaderFooter(buffy, sheet.getFirstHeader()); - extractHeaderFooter(buffy, sheet.getOddHeader()); - extractHeaderFooter(buffy, sheet.getEvenHeader()); - - // Rows and cells - for (Object rawR : sheet) { - Row row = (Row) rawR; - for (Iterator<Cell> ri = row.cellIterator(); ri.hasNext();) { - Cell cell = ri.next(); - - if (cell.getCellType() == Cell.CELL_TYPE_FORMULA || cell.getCellType() == Cell.CELL_TYPE_STRING) { - buffy.append(cell.getRichStringCellValue().getString()).append(' '); - } else { - XSSFCell xc = (XSSFCell) cell; - String rawValue = xc.getRawValue(); - if (rawValue != null) { - buffy.append(rawValue).append(' '); + enum xssfDataType { + BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, + } + + public class MySheetHandler extends DefaultHandler { + private final StringBuilder content; + + private StringBuilder buffer; + private xssfDataType nextDataType; + private int formatIndex; + private String formatString; + private CTHeaderFooter headerFooter = CTHeaderFooter.Factory.newInstance(); + + private final SharedStringsTable sst; + private final StylesTable stylesTable; + private final DataFormatter formatter = new DataFormatter(); + + public MySheetHandler(StringBuilder content, StylesTable styles, SharedStringsTable sst) { + this.sst = sst; + this.content = content; + this.stylesTable = styles; + } + + @Override + public void startDocument() { + headerFooter = CTHeaderFooter.Factory.newInstance(); + } + + @Override + public void endDocument() { + append(headerFooter.getFirstHeader()); + append(headerFooter.getOddHeader()); + append(headerFooter.getEvenHeader()); + + append(headerFooter.getFirstFooter()); + append(headerFooter.getOddFooter()); + append(headerFooter.getEvenFooter()); + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) { + if (name.equals("c")) { + // c -> cell + // Set up defaults. + nextDataType = xssfDataType.NUMBER; + formatIndex = -1; + formatString = null; + + String cellType = attributes.getValue("t"); + String cellStyleStr = attributes.getValue("s"); + if ("b".equals(cellType)) { + nextDataType = xssfDataType.BOOL; + } else if ("e".equals(cellType)) { + nextDataType = xssfDataType.ERROR; + } else if ("inlineStr".equals(cellType)) { + nextDataType = xssfDataType.INLINESTR; + } else if ("s".equals(cellType)) { + nextDataType = xssfDataType.SSTINDEX; + } else if ("str".equals(cellType)) { + nextDataType = xssfDataType.FORMULA; + } else if (cellStyleStr != null) { + // It's a number, but almost certainly one with a special style or + // format + int styleIndex = Integer.parseInt(cellStyleStr); + XSSFCellStyle style = stylesTable.getStyleAt(styleIndex); + formatIndex = style.getDataFormat(); + formatString = style.getDataFormatString(); + if (formatString == null) formatString = BuiltinFormats.getBuiltinFormat(formatIndex); + } + } + // Clear contents cache + buffer = new StringBuilder(); + } + + @Override + public void endElement(String uri, String localName, String name) { + if (name.equals("c")) { + // + } else if ("v".equals(name)) { + String thisStr = null; + switch (nextDataType) { + case BOOL: + char first = buffer.charAt(0); + thisStr = first == '0' ? "FALSE" : "TRUE"; + break; + case ERROR: + thisStr = buffer.toString(); + break; + case FORMULA: + // A formula could result in a string value, + // so always add double-quote characters. + thisStr = buffer.toString(); + break; + case INLINESTR: + // TODO: have seen an example of this, so it's untested. + XSSFRichTextString rtsi = new XSSFRichTextString(buffer.toString()); + thisStr = rtsi.toString(); + break; + case SSTINDEX: + String sstIndex = buffer.toString(); + try { + int idx = Integer.parseInt(sstIndex); + XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx)); + thisStr = rtss.toString(); + } catch (NumberFormatException ex) { + // sorry but it's not a disaster... } + break; - } + case NUMBER: + String n = buffer.toString(); + if (this.formatString != null) thisStr = formatter.formatRawCellContents(Double.parseDouble(n), this.formatIndex, + this.formatString); + else thisStr = n; + break; + } - // Output the comment in the same cell as the content - Comment comment = cell.getCellComment(); - if (comment != null) { - buffy.append(comment.getString().getString()).append(' '); - } + append(thisStr); + } else if ("firstHeader".equals(name)) { + headerFooter.setFirstHeader(buffer.toString()); + } else if ("firstFooter".equals(name)) { + headerFooter.setFirstFooter(buffer.toString()); + } else if ("evenFooter".equals(name)) { + headerFooter.setEvenFooter(buffer.toString()); + } else if ("evenHeaderer".equals(name)) { + headerFooter.setEvenHeader(buffer.toString()); + } else if ("oddHeader".equals(name)) { + headerFooter.setOddHeader(buffer.toString()); + } else if ("oddFooter".equals(name)) { + headerFooter.setOddFooter(buffer.toString()); + } else if ("row".equals(name)) { + if (content.length() > 0 && content.charAt(content.length() - 1) != '\n') { + content.append('\n'); } } + } - // Finally footer(s), if present - extractHeaderFooter(buffy, sheet.getFirstFooter()); - extractHeaderFooter(buffy, sheet.getOddFooter()); - extractHeaderFooter(buffy, sheet.getEvenFooter()); + @Override + public void characters(char[] ch, int start, int length) { + buffer.append(ch, start, length); } - } - private void extractHeaderFooter(StringBuilder buffy, HeaderFooter hf) { - String content = ExcelExtractor._extractHeaderFooter(hf); - if (content.length() > 0) { - buffy.append(content).append(' '); + private final void append(String str) { + if (StringHelper.containsNonWhitespace(str)) { + if (content.length() > 0) content.append(' '); + content.append(str); + } } } } diff --git a/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java b/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java index d1c1a4bad92afe5111754b4c45d772c711ef0e29..bff4c1da6dab6725cb579dced31ac2204fc0b6f4 100644 --- a/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java +++ b/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java @@ -22,13 +22,14 @@ package org.olat.search.service.document.file; import java.io.IOException; -import java.util.List; import org.apache.lucene.document.Document; import org.olat.core.commons.services.search.SearchModule; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.vfs.LocalImpl; import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; import org.olat.search.service.SearchResourceContext; /** @@ -50,9 +51,6 @@ public class FileDocumentFactory { static FileDocumentFactory instance; - private static boolean pptFileEnabled; - private static boolean excelFileEnabled; - private final static String PDF_SUFFIX = "pdf"; private final static String EXCEL_SUFFIX = "xls"; private final static String WORD_SUFFIX = "doc"; @@ -73,36 +71,31 @@ public class FileDocumentFactory { //as a special parser; private static final String IMS_MANIFEST_FILE = "imsmanifest.xml"; - private static List<String> checkFileSizeSuffixes; - private static long maxFileSize; private int excludedFileSizeCount = 0; - private List<String> fileBlackList; + + private static SearchModule searchModule; /** * [used by spring] * @param searchModule */ - public FileDocumentFactory(SearchModule searchModule) { + public FileDocumentFactory(SearchModule module) { instance = this; - fileBlackList = searchModule.getFileBlackList(); - pptFileEnabled = searchModule.getPptFileEnabled(); - if (!pptFileEnabled) log.info("PPT files are disabled in indexer."); - excelFileEnabled = searchModule.getExcelFileEnabled(); - if (!excelFileEnabled) log.info("Excel files are disabled in indexer."); - checkFileSizeSuffixes = searchModule.getFileSizeSuffixes(); - maxFileSize = searchModule.getMaxFileSize(); + searchModule = module; } public static Document createDocument(SearchResourceContext leafResourceContext, VFSLeaf leaf) throws DocumentNotImplementedException, IOException, DocumentException, DocumentAccessException { String fileName = leaf.getName(); - String suffix = getSuffix(fileName); + String suffix = FileTypeDetector.getSuffix(leaf); if (log.isDebug()) log.debug("suffix=" + suffix); if (PDF_SUFFIX.indexOf(suffix) >= 0) { - return PdfDocument.createDocument(leafResourceContext, leaf); + if(searchModule.getPdfFileEnabled()) + return PdfDocument.createDocument(leafResourceContext, leaf); + return null; } if (HTML_SUFFIX.indexOf(suffix) >= 0) { return HtmlDocument.createDocument(leafResourceContext, leaf); @@ -122,12 +115,12 @@ public class FileDocumentFactory { return WordOOXMLDocument.createDocument(leafResourceContext, leaf); } if (suffix.indexOf(EXCEL_X_SUFFIX) >= 0) { - if (excelFileEnabled) + if (searchModule.getExcelFileEnabled()) return ExcelOOXMLDocument.createDocument(leafResourceContext, leaf); return null; } if (suffix.indexOf(POWERPOINT_X_SUFFIX) >= 0) { - if(pptFileEnabled) + if(searchModule.getPptFileEnabled()) return PowerPointOOXMLDocument.createDocument(leafResourceContext, leaf); return null; } @@ -137,12 +130,12 @@ public class FileDocumentFactory { return WordDocument.createDocument(leafResourceContext, leaf); } if (POWERPOINT_SUFFIX.indexOf(suffix) >= 0) { - if(pptFileEnabled) + if(searchModule.getPptFileEnabled()) return PowerPointDocument.createDocument(leafResourceContext, leaf); return null; } if (EXCEL_SUFFIX.indexOf(suffix) >= 0) { - if (excelFileEnabled) + if (searchModule.getExcelFileEnabled()) return ExcelDocument.createDocument(leafResourceContext, leaf); return null; } @@ -156,16 +149,6 @@ public class FileDocumentFactory { return UnkownDocument.createDocument(leafResourceContext, leaf); } - - private static String getSuffix(String fileName) throws DocumentNotImplementedException { - int dotpos = fileName.lastIndexOf('.'); - if (dotpos < 0 || dotpos == fileName.length() - 1) { - if (log.isDebug()) log.debug("I cannot detect the document suffix (marked with '.')."); - throw new DocumentNotImplementedException("I cannot detect the document suffix (marked with '.') for " + fileName); - } - String suffix = fileName.substring(dotpos+1).toLowerCase(); - return suffix; - } /** * Check if certain file is supported. @@ -181,18 +164,27 @@ public class FileDocumentFactory { String suffix; try { - suffix = getSuffix(fileName); + suffix = FileTypeDetector.getSuffix(leaf); } catch (DocumentNotImplementedException e) { return false; } // 1. Check if file is not on fileBlackList - if (fileBlackList.contains(fileName)) { + if (searchModule.getFileBlackList().contains(fileName)) { // File name is on blacklist return false; } + + if(leaf instanceof LocalImpl) { + String path = ((LocalImpl)leaf).getBasefile().getAbsolutePath(); + if (searchModule.getFileBlackList().contains(path)) { + return false; + } + } + // 2. Check for certain file-type the file size - if (checkFileSizeSuffixes.contains(suffix)) { + if (searchModule.getFileSizeSuffixes().contains(suffix)) { + long maxFileSize = searchModule.getMaxFileSize(); if ( (maxFileSize != 0) && (leaf.getSize() > maxFileSize) ) { log.info("File too big, exlude from search index. filename=" + fileName); excludedFileSizeCount++; diff --git a/src/main/java/org/olat/search/service/document/file/FileTypeDetector.java b/src/main/java/org/olat/search/service/document/file/FileTypeDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..e32d71fb7ef286f29ec85f3ea4d5f14830911011 --- /dev/null +++ b/src/main/java/org/olat/search/service/document/file/FileTypeDetector.java @@ -0,0 +1,87 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ + +package org.olat.search.service.document.file; + +import java.io.IOException; +import java.io.InputStream; + +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.vfs.VFSLeaf; + +/** + * + * Description:<br> + * Detect the suffix with double check for office document with the magic bytes + * + * <P> + * Initial Date: 1 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +public class FileTypeDetector { + + private static final OLog log = Tracing.createLoggerFor(FileDocumentFactory.class); + + private static final String ZIP = "PK\003\004"; + + + public static String getSuffix(VFSLeaf leaf) throws DocumentNotImplementedException { + String fileName = leaf.getName(); + int dotpos = fileName.lastIndexOf('.'); + if (dotpos < 0 || dotpos == fileName.length() - 1) { + if (log.isDebug()) log.debug("I cannot detect the document suffix (marked with '.')."); + throw new DocumentNotImplementedException("I cannot detect the document suffix (marked with '.') for " + fileName); + } + String suffix = fileName.substring(dotpos+1).toLowerCase(); + if("doc".equals(suffix)) { + if(checkMagicBytes(leaf, ZIP)) return "docx"; + } else if("xls".equals(suffix)) { + if(checkMagicBytes(leaf, ZIP)) return "xslx"; + } else if("ppt".equals(suffix)) { + if(checkMagicBytes(leaf, ZIP)) return "pptx"; + } + return suffix; + } + + public static boolean checkMagicBytes(VFSLeaf leaf, String reference) { + InputStream in = null; + try { + in = leaf.getInputStream(); + byte[] buffer = new byte[50]; + int n = in.read(buffer); + if (n > 0) { + boolean allOk = true; + byte[] ref = reference.getBytes(); + for(int i=0; i<ref.length; i++) { + allOk &= (ref[i] == buffer[i]); + } + return allOk; + } + } catch (IOException e) { + log.warn("", e); + } finally { + FileUtils.closeSafely(in); + } + return false; + } +} diff --git a/src/main/java/org/olat/search/service/document/file/WordDocument.java b/src/main/java/org/olat/search/service/document/file/WordDocument.java index b3d4cf5cf0cc93694d2c564a96bcaa28dfbf467b..d3a9d3aa7eb899d41c2f93a54b7a663329e1eb2c 100644 --- a/src/main/java/org/olat/search/service/document/file/WordDocument.java +++ b/src/main/java/org/olat/search/service/document/file/WordDocument.java @@ -40,7 +40,7 @@ import org.olat.search.service.SearchResourceContext; * @author Christian Guretzki */ public class WordDocument extends FileDocument { - private static final OLog log = Tracing.createLoggerFor(WordOOXMLDocument.class); + private static final OLog log = Tracing.createLoggerFor(WordDocument.class); public final static String FILE_TYPE = "type.file.word"; @@ -75,6 +75,7 @@ public class WordDocument extends FileDocument { } return sb.toString(); } catch (Exception e) { + log.warn("could not read in word document: " + leaf + " please check, that this is not an docx/rtf/html file!"); throw new DocumentException(e.getMessage()); } finally { if (bis != null) { diff --git a/src/main/java/org/olat/search/service/document/file/XmlDocument.java b/src/main/java/org/olat/search/service/document/file/XmlDocument.java index 9e34ed48da82f68e05727b362926139874921eff..c445f23b68b0a879b930805cf864f907fb4d2fad 100644 --- a/src/main/java/org/olat/search/service/document/file/XmlDocument.java +++ b/src/main/java/org/olat/search/service/document/file/XmlDocument.java @@ -21,14 +21,14 @@ package org.olat.search.service.document.file; -import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import org.apache.lucene.document.Document; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; -import org.olat.core.util.filter.FilterFactory; +import org.olat.core.util.filter.impl.NekoHTMLFilter; import org.olat.core.util.vfs.VFSLeaf; import org.olat.search.service.SearchResourceContext; @@ -54,14 +54,13 @@ public class XmlDocument extends FileDocument { return htmlDocument.getLuceneDocument(); } + //fxdiff FXOLAT-97: index run in infinite loop protected String readContent(VFSLeaf leaf) throws IOException { - BufferedInputStream bis = new BufferedInputStream(leaf.getInputStream()); - String inputString = FileUtils.load(bis, "utf-8"); + InputStream is = leaf.getInputStream(); // Remove all HTML and Tags - if (log.isDebug() ) log.debug("HTML content with tags :" + inputString); - String output = FilterFactory.getHtmlTagsFilter().filter(inputString); + String output = new NekoHTMLFilter().filter(is); if (log.isDebug() ) log.debug("HTML content without tags :" + output); - bis.close(); + FileUtils.closeSafely(is); return output; } diff --git a/src/main/java/org/olat/search/service/indexer/AbstractIndexer.java b/src/main/java/org/olat/search/service/indexer/AbstractIndexer.java index ab06595a5ab422d045617d4fb2feb968f0d5ddcf..574f23fdc18345bc375cc4174bd1bb68a7d7caf3 100644 --- a/src/main/java/org/olat/search/service/indexer/AbstractIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/AbstractIndexer.java @@ -33,21 +33,17 @@ import org.olat.core.id.Roles; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.AssertException; -import org.olat.core.logging.OLog; +import org.olat.core.logging.LogDelegator; import org.olat.core.logging.StartupException; -import org.olat.core.logging.Tracing; import org.olat.search.service.SearchResourceContext; /** * Common abstract indexer. Used as base class for indexers. * @author Christian Guretzki */ -public abstract class AbstractIndexer implements Indexer { - - private static final OLog log = Tracing.createLoggerFor(AbstractIndexer.class); - - public Map<String,Indexer> childIndexers = new HashMap<String,Indexer>(); +public abstract class AbstractIndexer extends LogDelegator implements Indexer{ + public Map<String,Indexer> childIndexers = new HashMap<String,Indexer>(); /** * Bean setter method used by spring. @@ -60,7 +56,7 @@ public abstract class AbstractIndexer implements Indexer { try { for (Indexer indexer:indexerList) { childIndexers.put(indexer.getSupportedTypeName(), indexer); - log.debug("Adding indexer from configuraton. TypeName=" + indexer.getSupportedTypeName()); + logDebug("Adding indexer from configuraton. TypeName=" + indexer.getSupportedTypeName()); } } catch (ClassCastException cce) { throw new StartupException("Configured indexer is not of type Indexer", cce); @@ -73,14 +69,14 @@ public abstract class AbstractIndexer implements Indexer { */ public void doIndex(SearchResourceContext searchResourceContext, Object object, OlatFullIndexer indexerWriter) throws IOException, InterruptedException { for (Indexer indexer : childIndexers.values()) { - if (log.isDebug()) log.debug("Start doIndex for indexer.typeName=" + indexer.getSupportedTypeName()); + if (isLogDebugEnabled()) logDebug("Start doIndex for indexer.typeName=" + indexer.getSupportedTypeName()); try { indexer.doIndex(searchResourceContext, object, indexerWriter); } catch (InterruptedException iex) { throw iex; } catch (Throwable ex) { // FIXME:chg: Workaround to fix indexing-abort - log.warn("Exception in diIndex indexer.typeName=" + indexer.getSupportedTypeName(),ex); + logWarn("Exception in diIndex indexer.typeName=" + indexer.getSupportedTypeName(),ex); } } } @@ -93,7 +89,7 @@ public abstract class AbstractIndexer implements Indexer { * @return */ public boolean checkAccess(BusinessControl businessControl, Identity identity, Roles roles) { - if (log.isDebug()) log.debug("checkAccess for businessControl=" + businessControl + " identity=" + identity + " roles=" + roles); + if (isLogDebugEnabled()) logDebug("checkAccess for businessControl=" + businessControl + " identity=" + identity + " roles=" + roles); ContextEntry contextEntry = businessControl.popLauncherContextEntry(); if (contextEntry != null) { // there is an other context-entry => go further @@ -106,11 +102,11 @@ public abstract class AbstractIndexer implements Indexer { AbstractIndexer childIndexer = entSet.getValue() instanceof AbstractIndexer ? (AbstractIndexer) entSet.getValue() : null; Indexer foundSubChildIndexer = childIndexer == null ? null : childIndexer.getChildIndexers().get(type); if (foundSubChildIndexer != null) { - if (log.isDebug()) log.debug("took a childindexer for ores= " + ores + " not directly linked (means businesspath is not the same stack as indexer -> childindexer). type= " +type + " . indexer parent-type not on businesspath=" + childIndexer.getSupportedTypeName()); + if (isLogDebugEnabled()) logDebug("took a childindexer for ores= " + ores + " not directly linked (means businesspath is not the same stack as indexer -> childindexer). type= " +type + " . indexer parent-type not on businesspath=" + childIndexer.getSupportedTypeName()); return foundSubChildIndexer.checkAccess(contextEntry, businessControl, identity, roles); } } - log.error("could not find an indexer for type="+type + " businessControl="+businessControl + " identity=" + identity); + logError("could not find an indexer for type="+type + " businessControl="+businessControl + " identity=" + identity, null); return false; } return indexer.checkAccess(contextEntry, businessControl, identity, roles); diff --git a/src/main/java/org/olat/search/service/indexer/ContextHelpIndexer.java b/src/main/java/org/olat/search/service/indexer/ContextHelpIndexer.java index bd16fd47371e25f993bf863c4d83b6dc190ea2e5..acb1b164434431e3920ce86592289f5187176175 100644 --- a/src/main/java/org/olat/search/service/indexer/ContextHelpIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/ContextHelpIndexer.java @@ -95,6 +95,11 @@ public class ContextHelpIndexer extends AbstractIndexer { String[] identifyerSplit = helpPageIdentifyer.split(":"); String bundleName = identifyerSplit[0]; String page = identifyerSplit[1]; + //fxdiff: FXOLAT-221: don't use velocity on images + if(page == null || !page.endsWith(".html")) { + continue; + } + // Translator with default locale. Locale is set to each language in the // language iteration below Translator pageTranslator = new PackageTranslator(bundleName, I18nModule.getDefaultLocale()); diff --git a/src/main/java/org/olat/search/service/indexer/FolderIndexer.java b/src/main/java/org/olat/search/service/indexer/FolderIndexer.java index e57a4096caafe46b37b4039ce4885c8b442b7fb1..968423820b9a3630cae77d3874a69131766efabf 100644 --- a/src/main/java/org/olat/search/service/indexer/FolderIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/FolderIndexer.java @@ -28,6 +28,7 @@ import org.apache.lucene.document.Document; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.WorkThreadInformations; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; @@ -105,6 +106,8 @@ public abstract class FolderIndexer extends AbstractIndexer { myFilePath = filePath + "/" + leaf.getName(); } leafResourceContext.setFilePath(myFilePath); + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.set("Index VFSLeaf=" + myFilePath + " at " + leafResourceContext.getResourceUrl()); Document document = FileDocumentFactory.createDocument(leafResourceContext, leaf); indexWriter.addDocument(document); } else { @@ -122,6 +125,9 @@ public abstract class FolderIndexer extends AbstractIndexer { throw new InterruptedException(iex.getMessage()); } catch (Exception ex) { log.warn("Exception: Can not index leaf=" + leaf.getName(), ex); + //fxdiff FXOLAT-97: high CPU load tracker + } finally { + WorkThreadInformations.unset(); } } diff --git a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java b/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java index f7a162a03b9f1269e8d3ede171d99d04e3703372..64a9bda1ef879124c03e8f1914d8cc8c77078b01 100644 --- a/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java +++ b/src/main/java/org/olat/search/service/indexer/FolderIndexerWorker.java @@ -22,12 +22,17 @@ package org.olat.search.service.indexer; +import java.io.File; import java.io.IOException; import org.apache.lucene.document.Document; +import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.WorkThreadInformations; +import org.olat.core.util.vfs.LocalImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; @@ -123,8 +128,13 @@ public class FolderIndexerWorker implements Runnable{ if (SearchServiceFactory.getFileDocumentFactory().isFileSupported(leaf)) { String myFilePath = fPath + "/" + leaf.getName(); leafResourceContext.setFilePath(myFilePath); + //fxdiff FXOLAT-97: high CPU load tracker + setInfoFiles(myFilePath, leaf); + WorkThreadInformations.set("Index VFSLeaf=" + myFilePath + " at " + leafResourceContext.getResourceUrl()); Document document = FileDocumentFactory.createDocument(leafResourceContext, leaf); - writer.addDocument(document); + if(document != null) {//document wihich are disabled return null + writer.addDocument(document); + } } else { if (log.isDebug()) log.debug("Documenttype not supported. file=" + leaf.getName()); } @@ -140,9 +150,27 @@ public class FolderIndexerWorker implements Runnable{ log.warn("IOException: Can not index leaf=" + leaf.getName(), ioEx); } catch (Exception ex) { log.warn("Exception: Can not index leaf=" + leaf.getName(), ex); + //fxdiff FXOLAT-97: high CPU load tracker + } finally { + WorkThreadInformations.unset(); + } + } + + private void setInfoFiles(String filePath, VFSLeaf leaf) { + try { + File file = new File(FolderConfig.getCanonicalTmpDir(), "threadInfos"); + if(!file.exists()) { + file.mkdirs(); + } + if(leaf instanceof LocalImpl) { + filePath = ((LocalImpl)leaf).getBasefile().getAbsolutePath(); + } + File infoFile = new File(file, Thread.currentThread().getName()); + FileUtils.save(infoFile, filePath, "UTF-8"); + } catch (Exception e) { + log.error("Cannot write info message about FolderIndexerWorker: " + filePath, e); } } - public void setParentResourceContext(SearchResourceContext newParentResourceContext) { this.parentResourceContext = newParentResourceContext; diff --git a/src/main/java/org/olat/search/service/indexer/ForumIndexer.java b/src/main/java/org/olat/search/service/indexer/ForumIndexer.java index 38d400b99476bf5e4386f24bde99af07271b592b..894f6c6410b378ca01cbb65524a0854f2dd292ad 100644 --- a/src/main/java/org/olat/search/service/indexer/ForumIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/ForumIndexer.java @@ -38,16 +38,18 @@ import org.olat.search.service.document.ForumMessageDocument; */ public abstract class ForumIndexer extends AbstractIndexer { - protected ForumManager forumManager; - public ForumIndexer() { - forumManager = ForumManager.getInstance(); + // } public void doIndexAllMessages(SearchResourceContext parentResourceContext, Forum forum, OlatFullIndexer indexWriter) throws IOException,InterruptedException { + if (forum == null) { + logWarn("tried to index a forum that could not be found! skipping. context: " + parentResourceContext.getResourceUrl(), null); + return; + } // loop over all messages of a forum - List<Message> messages = forumManager.getMessagesByForum(forum); + List<Message> messages = ForumManager.getInstance().getMessagesByForum(forum); for(Message message : messages) { SearchResourceContext searchResourceContext = new SearchResourceContext(parentResourceContext); searchResourceContext.setBusinessControlFor(message); diff --git a/src/main/java/org/olat/search/service/indexer/IndexCronGenerator.java b/src/main/java/org/olat/search/service/indexer/IndexCronGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..199c0332d8feb6f860e1925750880c2470a2766d --- /dev/null +++ b/src/main/java/org/olat/search/service/indexer/IndexCronGenerator.java @@ -0,0 +1,92 @@ +/** + * 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) frentix GmbH<br> + * http://www.frentix.com<br> + * <p> + */ +package org.olat.search.service.indexer; + +import org.springframework.beans.factory.FactoryBean; + +/** + * + * Description:<br> + * + * <P> + * Initial Date: 8 sept. 2011 <br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + */ +//fxdiff FXOLAT-221: start indexer at different times for each instance +public class IndexCronGenerator implements FactoryBean<String> { + + private int tomcatId; + private String enabled; + + /** + * [used by Spring] + * @param tomcatId + */ + public void setTomcatId(int tomcatId) { + this.tomcatId = tomcatId; + } + + /** + * [used by Spring] + * @param enabled + */ + public void setEnabled(String enabled) { + this.enabled = enabled; + } + + public boolean isCronEnabled() { + return "enabled".equals(enabled); + } + + public String getCron() { + int shiftHours = tomcatId % 4; + String hours; + switch(shiftHours) { + case 0: hours = "0,4,8,12,16,20"; break; + case 1: hours = "1,5,9,13,17,21"; break; + case 2: hours = "2,6,10,14,18,22"; break; + default: hours = "3,7,11,15,19,23"; + } + + int shiftMinuts = tomcatId % 6; + return "0 " + shiftMinuts + "0 " + hours + " * * ? *"; + } + + @Override + public String getObject() throws Exception { + return getCron(); + } + + + @Override + public Class<?> getObjectType() { + return String.class; + } + + + @Override + public boolean isSingleton() { + return true; + } + + + +} diff --git a/src/main/java/org/olat/search/service/indexer/IndexWriterWorker.java b/src/main/java/org/olat/search/service/indexer/IndexWriterWorker.java index e0201fe967869225a2fd32046afa19361e09a853..ea12410eb2dbce2a385f636f36cf16f5b8d1cd03 100644 --- a/src/main/java/org/olat/search/service/indexer/IndexWriterWorker.java +++ b/src/main/java/org/olat/search/service/indexer/IndexWriterWorker.java @@ -133,6 +133,7 @@ public class IndexWriterWorker implements Runnable { } try { indexWriter.close(); + indexWriter = null; closed = true; if (log.isDebug()) log.debug("IndexWriter " + id + " closed"); } catch (IOException e) { diff --git a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java index 9a5fd7cd2b8bb82a727a498fc987d104fbf88c44..98c5fc4c56108d9ef4310107a401866c7771d75c 100644 --- a/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/OlatFullIndexer.java @@ -130,8 +130,8 @@ public class OlatFullIndexer implements Runnable { public void startIndexing() { // Start updateThread if ( (indexingThread == null) || !indexingThread.isAlive()) { - log.info("start full indexing thread..."); if (stopIndexing) { + log.info("start full indexing thread..."); indexingThread = new Thread(this, "FullIndexer"); stopIndexing = false; resetDocumentCounters(); @@ -224,8 +224,9 @@ public class OlatFullIndexer implements Runnable { fullIndexerStatus.setIndexSize(indexWriter.maxDoc()); indexWriter.optimize(); indexWriter.close(); + indexWriter = null; + indexWriterWorkers = null; } catch (IOException e) { - e.printStackTrace(); log.warn("Can not create IndexWriter, indexname=" + tempIndexPath, e); } finally { DBFactory.getInstance().commitAndCloseSession(); @@ -303,6 +304,7 @@ public class OlatFullIndexer implements Runnable { } fullIndexerStatus.setStatus(FullIndexerStatus.STATUS_STOPPED); stopIndexing = true; + indexingThread = null; try { log.info("quit indexing run."); } catch (NullPointerException nex) { diff --git a/src/main/java/org/olat/search/service/indexer/group/GroupForumIndexer.java b/src/main/java/org/olat/search/service/indexer/group/GroupForumIndexer.java index e61db3be90f787b736daa5726a0ded7d0b98277d..dbb7c46daec49286cbc825b249bab0fcb27a5ed2 100644 --- a/src/main/java/org/olat/search/service/indexer/group/GroupForumIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/group/GroupForumIndexer.java @@ -86,6 +86,11 @@ public class GroupForumIndexer extends ForumIndexer{ forumSearchResourceContext.setDocumentContext(businessGroup.getKey() + " " + forumKey); forumSearchResourceContext.setParentContextType(GroupDocument.TYPE); forumSearchResourceContext.setParentContextName(businessGroup.getName()); + if (forum == null) { // fxdiff: FXOLAT-104 warn about missing forums + logError("found a forum-key " + forumKey + " for businessgroup " + businessGroup.getName() + " [" + businessGroup.getKey() + "] to index a forum that could not be " + + "found by key! skip indexing, check if forum should still be enabled. context: " + forumSearchResourceContext.getResourceUrl(), null); + return; + } doIndexAllMessages(forumSearchResourceContext, forum, indexWriter ); } } diff --git a/src/main/java/org/olat/search/service/indexer/group/GroupIndexer.java b/src/main/java/org/olat/search/service/indexer/group/GroupIndexer.java index b6c44e545f91d3431b23c3cde21fd48691ac47b9..bd474b8e36796de7a674f268042be2dacfe11291 100644 --- a/src/main/java/org/olat/search/service/indexer/group/GroupIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/group/GroupIndexer.java @@ -23,10 +23,12 @@ package org.olat.search.service.indexer.group; import java.io.IOException; +import java.util.Date; import java.util.Iterator; import java.util.List; import org.apache.lucene.document.Document; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.id.Identity; import org.olat.core.id.Roles; @@ -37,6 +39,10 @@ import org.olat.core.util.resource.OresHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupManager; import org.olat.group.BusinessGroupManagerImpl; +import org.olat.resource.OLATResource; +import org.olat.resource.OLATResourceManager; +import org.olat.resource.accesscontrol.AccessControlModule; +import org.olat.resource.accesscontrol.manager.ACFrontendManager; import org.olat.search.service.SearchResourceContext; import org.olat.search.service.document.GroupDocument; import org.olat.search.service.indexer.AbstractIndexer; @@ -106,23 +112,19 @@ public class GroupIndexer extends AbstractIndexer { public boolean checkAccess(ContextEntry contextEntry, BusinessControl businessControl, Identity identity, Roles roles) { Long key = contextEntry.getOLATResourceable().getResourceableId(); BusinessGroupManager bman = BusinessGroupManagerImpl.getInstance(); - List oGroups = bman.findBusinessGroupsOwnedBy(null, identity, null); - List aGroups = bman.findBusinessGroupsAttendedBy(null, identity, null); - - boolean inGroup = false; //TODO - for (Iterator it_ogroups = oGroups.iterator(); !inGroup && it_ogroups.hasNext();) { - BusinessGroup gr = (BusinessGroup) it_ogroups.next(); - Long grk = gr.getKey(); - if (grk.equals(key)) inGroup = true; - } - for (Iterator it_agroups = aGroups.iterator(); !inGroup && it_agroups.hasNext();) { - BusinessGroup gr = (BusinessGroup) it_agroups.next(); - Long grk = gr.getKey(); - if (grk.equals(key)) inGroup = true; - } + BusinessGroup group = bman.loadBusinessGroup(key, false); + boolean inGroup = bman.isIdentityInBusinessGroup(identity, group); if (inGroup) { return super.checkAccess(businessControl, identity, roles); } else { + AccessControlModule acModule = (AccessControlModule)CoreSpringFactory.getBean("acModule"); + if(acModule.isEnabled()) { + ACFrontendManager acFrontendManager = (ACFrontendManager)CoreSpringFactory.getBean("acFrontendManager"); + OLATResource resource = OLATResourceManager.getInstance().findResourceable(group); + if(acFrontendManager.isResourceAccessControled(resource, new Date())) { + return super.checkAccess(businessControl, identity, roles); + } + } return false; } } diff --git a/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java b/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java index 69cce40eb662bdc5bc297689bada193271f5f5ee..96e723d83dca446d8c72634d10ff20f090d32c08 100644 --- a/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java +++ b/src/main/java/org/olat/search/service/indexer/repository/course/DialogCourseNodeIndexer.java @@ -37,6 +37,7 @@ import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.ContextEntry; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.WorkThreadInformations; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.filters.VFSLeafFilter; @@ -128,6 +129,8 @@ public class DialogCourseNodeIndexer implements CourseNodeIndexer { if (SearchServiceFactory.getFileDocumentFactory().isFileSupported(leaf)) { leafResourceContext.setFilePath(filename); leafResourceContext.setDocumentType(TYPE_FILE); + //fxdiff FXOLAT-97: high CPU load tracker + WorkThreadInformations.set("Index Dialog VFSLeaf=" + filename + " at " + leafResourceContext.getResourceUrl()); Document document = FileDocumentFactory.createDocument(leafResourceContext, leaf); indexWriter.addDocument(document); } else { @@ -145,6 +148,9 @@ public class DialogCourseNodeIndexer implements CourseNodeIndexer { throw new InterruptedException(iex.getMessage()); } catch (Exception ex) { log.warn("Exception: Can not index leaf=" + leaf.getName(), ex); + //fxdiff FXOLAT-97: high CPU load tracker + } finally { + WorkThreadInformations.unset(); } } diff --git a/src/main/java/org/olat/user/AbstractUserPropertyHandler.java b/src/main/java/org/olat/user/AbstractUserPropertyHandler.java index 69b3d6b34b06ac57b55478ed48787d1e18302f4c..397e94dff4a1f94c2df4738072fa406f7e01f94d 100644 --- a/src/main/java/org/olat/user/AbstractUserPropertyHandler.java +++ b/src/main/java/org/olat/user/AbstractUserPropertyHandler.java @@ -129,7 +129,9 @@ public abstract class AbstractUserPropertyHandler implements UserPropertyHandler // remove fields with null or empty value from o_userfield table (hibernate) // sparse data storage if (value == null || value.length() == 0) { - ((UserImpl)user).getProperties().remove(name); + //fxdiff: store each value + ((UserImpl)user).getProperties().put(name, ""); + //((UserImpl)user).getProperties().remove(name); } else { ((UserImpl)user).getProperties().put(name, value); } diff --git a/src/main/java/org/olat/user/ChangePasswordController.java b/src/main/java/org/olat/user/ChangePasswordController.java index 38fffe60ca8c95d2855bfe0cd69ab84e8872712d..33c6887fd6ab34900f451e6c276f1c917bbb0545 100644 --- a/src/main/java/org/olat/user/ChangePasswordController.java +++ b/src/main/java/org/olat/user/ChangePasswordController.java @@ -38,7 +38,6 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.messages.MessageUIFactory; import org.olat.core.id.Identity; -import org.olat.core.logging.OLATSecurityException; import org.olat.core.util.WebappHelper; import org.olat.core.util.resource.OresHelper; import org.olat.ldap.LDAPError; @@ -78,10 +77,10 @@ public class ChangePasswordController extends BasicController implements Support super(ureq, wControl); // if a user is not allowed to change his/her own password, say it here - if (!UserModule.isPwdchangeallowed()) { + if (!UserModule.isPwdchangeallowed(ureq.getIdentity())) { String text = translate("notallowedtochangepwd", new String[] { WebappHelper.getMailConfig("mailSupport") }); Controller simpleMsg = MessageUIFactory.createSimpleMessage(ureq, wControl, text); - listenTo(simpleMsg);//register controller to be disposed automatically on dispose of Change password controller + listenTo(simpleMsg); //register controller to be disposed automatically on dispose of Change password controller putInitialPanel(simpleMsg.getInitialComponent()); return; } @@ -90,8 +89,13 @@ public class ChangePasswordController extends BasicController implements Support if (!mgr.isIdentityPermittedOnResourceable( ureq.getIdentity(), Constants.PERMISSION_ACCESS, - OresHelper.lookupType(this.getClass()))) - throw new OLATSecurityException("Insufficient permission to access ChangePasswordController"); + OresHelper.lookupType(this.getClass()))) { + String text = "Insufficient permission to access ChangePasswordController"; + Controller simpleMsg = MessageUIFactory.createSimpleMessage(ureq, wControl, text); + listenTo(simpleMsg); //register controller to be disposed automatically on dispose of Change password controller + putInitialPanel(simpleMsg.getInitialComponent()); + return; + } myContent = createVelocityContainer("pwd"); //adds "provider_..." variables to myContent @@ -105,6 +109,13 @@ public class ChangePasswordController extends BasicController implements Support putInitialPanel(myContent); } + @Override + public boolean isInterceptionRequired(UserRequest ureq) { + if (ureq.getUserSession().getRoles() == null || ureq.getUserSession().getRoles().isInvitee() + || ureq.getUserSession().getRoles().isGuestOnly()) return false; + else return true; + } + /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ diff --git a/src/main/java/org/olat/user/ChangePrefsController.java b/src/main/java/org/olat/user/ChangePrefsController.java index accc747aa623a8b260e5af28aa6d9984e109416c..0192fa278534380b39d4a1d4ac76f967de00226d 100644 --- a/src/main/java/org/olat/user/ChangePrefsController.java +++ b/src/main/java/org/olat/user/ChangePrefsController.java @@ -46,6 +46,7 @@ import org.olat.core.id.context.HistoryModule; import org.olat.core.util.StringHelper; import org.olat.core.util.prefs.Preferences; import org.olat.core.util.prefs.PreferencesFactory; +import org.olat.properties.PropertyManager; /** @@ -63,6 +64,7 @@ public class ChangePrefsController extends BasicController { private VelocityContainer myContent; private Controller generalPrefsCtr; private Controller specialPrefsCtr; + private Controller resetCtr; /** * Constructor for the change user preferences controller @@ -82,8 +84,13 @@ public class ChangePrefsController extends BasicController { specialPrefsCtr = new SpecialPrefsForm(ureq, wControl, changeableIdentity); listenTo(specialPrefsCtr); + // fxdiff FXOLAT-149 + resetCtr = new UserPrefsResetForm(ureq, wControl, changeableIdentity); + listenTo(resetCtr); + myContent.put("general", generalPrefsCtr.getInitialComponent()); myContent.put("special", specialPrefsCtr.getInitialComponent()); + myContent.put("reset", resetCtr.getInitialComponent()); putInitialPanel(myContent); } @@ -283,3 +290,79 @@ class SpecialPrefsForm extends FormBasicController { } +// fxdiff FXOLAT-149 +/** + * Controller to reset the users GUI prefs and other preferences + */ +class UserPrefsResetForm extends FormBasicController { + + private Identity tobeChangedIdentity; + private MultipleSelectionElement resetElements; + private String[] keys, values; + + public UserPrefsResetForm(UserRequest ureq, WindowControl wControl, Identity changeableIdentity) { + super(ureq, wControl); + tobeChangedIdentity = changeableIdentity; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("reset.title"); + setFormDescription("reset.desc"); + + keys = new String[]{"guiprefs", "sysprefs", "resume"}; + values = new String[] {translate("reset.elements.guiprefs"), translate("reset.elements.sysprefs"), translate("reset.elements.resume")}; + + resetElements = uifactory.addCheckboxesVertical("prefs", "reset.elements", formLayout, keys, values, null, 1); + + final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); + formLayout.add(buttonLayout); + uifactory.addFormSubmitButton("reset.submit", buttonLayout); + } + + @Override + protected void formOK(UserRequest ureq) { + if (resetElements.isAtLeastSelected(1)) { + // Log out user first if logged in + Set<UserSession> sessions = UserSession.getAuthenticatedUserSessions(); + for (UserSession session : sessions) { + Identity ident = session.getIdentity(); + if (ident != null && tobeChangedIdentity.equalsByPersistableKey(ident)) { + session.signOffAndClear(); + break; + } + } + // Delete gui prefs + if (resetElements.isSelected(0)) { + PropertyManager pm = PropertyManager.getInstance(); + pm.deleteProperties(tobeChangedIdentity, null, null, null, "v2guipreferences"); + } + // Reset preferences + if (resetElements.isSelected(1)) { + UserManager um = UserManager.getInstance(); + User user = um.loadUserByKey(tobeChangedIdentity.getUser().getKey()); + org.olat.core.id.Preferences preferences = user.getPreferences(); + preferences.setFontsize(null); + preferences.setNotificationInterval(null); + preferences.setPresenceMessagesPublic(false); + preferences.setReceiveRealMail(null); + um.updateUser(user); + PropertyManager pm = PropertyManager.getInstance(); + pm.deleteProperties(tobeChangedIdentity, null, null, null, "charset"); + } + // Reset history + if (resetElements.isSelected(2)) { + HistoryManager.getInstance().deleteHistory(tobeChangedIdentity); + } + // reset form buttons + resetElements.uncheckAll(); + } + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/user/ProfileAndHomePageEditController.java b/src/main/java/org/olat/user/ProfileAndHomePageEditController.java index b090d8ca17414d1b145387a9710acdaf79ec7ae7..1aad713878ed1bd2955125c5d1bacf0f02cee7a9 100644 --- a/src/main/java/org/olat/user/ProfileAndHomePageEditController.java +++ b/src/main/java/org/olat/user/ProfileAndHomePageEditController.java @@ -26,14 +26,13 @@ import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.mail.MessagingException; import javax.mail.SendFailedException; import javax.mail.internet.AddressException; -import org.olat.basesecurity.BaseSecurityModule; import org.olat.basesecurity.BaseSecurityManager; +import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -61,6 +60,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.login.SupportsAfterLoginInterceptor; import org.olat.registration.RegistrationManager; import org.olat.registration.TemporaryKey; +import org.olat.registration.TemporaryKeyImpl; import com.thoughtworks.xstream.XStream; @@ -134,6 +134,13 @@ public class ProfileAndHomePageEditController extends BasicController implements this.myContent.put("c", c); putInitialPanel(this.myContent); } + + @Override + public boolean isInterceptionRequired(UserRequest ureq) { + if (ureq.getUserSession().getRoles() == null || ureq.getUserSession().getRoles().isInvitee() + || ureq.getUserSession().getRoles().isGuestOnly()) return false; + else return true; + } /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, @@ -203,6 +210,13 @@ public class ProfileAndHomePageEditController extends BasicController implements emailChanged = true; // change email address to old address until it is verified ProfileAndHomePageEditController.this.identityToModify.getUser().setProperty("email", currentEmail); + } else { + // fxdiff: FXOLAT-44 delete previous change-workflows + String key = ProfileAndHomePageEditController.this.identityToModify.getUser().getProperty("emchangeKey", null); + TemporaryKeyImpl tempKey = rm.loadTemporaryKeyByRegistrationKey(key); + if (tempKey != null) { + rm.deleteTemporaryKey(tempKey); + } } } if (!um.updateUserFromIdentity(ProfileAndHomePageEditController.this.identityToModify)) { @@ -279,7 +293,7 @@ public class ProfileAndHomePageEditController extends BasicController implements cal.add(Calendar.DAY_OF_WEEK, ChangeEMailController.TIME_OUT); String time = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, ureq.getLocale()).format(cal.getTime()); // create body and subject for email - body = this.translator.translate("email.change.body", new String[] { serverpath + "/dmz/emchange/index.html?key=" + tk.getRegistrationKey() + "&lang=" + ureq.getLocale().getLanguage(), time }) + body = this.translator.translate("email.change.body", new String[] { serverpath + "/dmz/emchange/index.html?key=" + tk.getRegistrationKey() + "&lang=" + ureq.getLocale().getLanguage(), time, currentEmail, changedEmail }) + SEPARATOR + this.translator.translate("email.change.wherefrom", new String[] { serverpath, today, ip }); subject = translate("email.change.subject"); // send email diff --git a/src/main/java/org/olat/user/UserInfoMainController.java b/src/main/java/org/olat/user/UserInfoMainController.java index 97c991b11ad9d0e6d32ad448ba5ae54956156549..6e622b2f7f69f3b34da0766cf5a2c6ee36c6b3d7 100644 --- a/src/main/java/org/olat/user/UserInfoMainController.java +++ b/src/main/java/org/olat/user/UserInfoMainController.java @@ -271,7 +271,7 @@ public class UserInfoMainController extends MainLayoutBasicController { namedFolder.setLocalSecurityCallback(secCallback); removeAsListenerAndDispose(folderRunController); - folderRunController = new FolderRunController(namedFolder, false, true, ureq, getWindowControl()); + folderRunController = new FolderRunController(namedFolder, false, true, false, ureq, getWindowControl()); listenTo(folderRunController); myContent.put("userinfo", folderRunController.getInitialComponent()); diff --git a/src/main/java/org/olat/user/UserModule.java b/src/main/java/org/olat/user/UserModule.java index e71fe98271090de0dedfc564a384177dc32e9814..0f69e71eac598c1b8445822559016b018685c757 100644 --- a/src/main/java/org/olat/user/UserModule.java +++ b/src/main/java/org/olat/user/UserModule.java @@ -32,6 +32,7 @@ import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityManager; import org.olat.basesecurity.Constants; import org.olat.basesecurity.SecurityGroup; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.PersistedProperties; @@ -43,6 +44,7 @@ import org.olat.core.logging.OLog; import org.olat.core.logging.StartupException; import org.olat.core.logging.Tracing; import org.olat.core.util.Encoder; +import org.olat.ldap.LDAPLoginManager; import org.olat.login.AfterLoginConfig; import org.olat.login.AfterLoginInterceptionManager; import org.olat.user.propertyhandlers.UserPropertyHandler; @@ -61,6 +63,7 @@ public class UserModule extends AbstractOLATModule { private BaseSecurity securityManager; private SecurityGroup adminGroup, authorGroup, olatuserGroup, anonymousGroup, groupmanagerGroup, usermanagerGroup; private static boolean pwdchangeallowed; + private static boolean pwdchangeallowedLDAP; private static String adminUserName; private List<DefaultUser> defaultUsers; private List<DefaultUser> testUsers; @@ -120,6 +123,7 @@ public class UserModule extends AbstractOLATModule { */ public void init() { pwdchangeallowed = getBooleanConfigParameter("passwordChangeAllowed", true); + pwdchangeallowedLDAP = getBooleanConfigParameter("passwordChangeAllowedLDAP", false); int count = 0; for (String regexp : loginBlacklist) { @@ -275,7 +279,39 @@ public class UserModule extends AbstractOLATModule { return loginBlacklistChecked; } - public static boolean isPwdchangeallowed() { + /** + * checks whether the given identity is allowed to change it's own password. + * default settings (olat.properties) : + * <ul> + * <li>LDAP-user are not allowed to change their pw</li> + * <li>other users are allowed to change their pw</li> + * </ul> + * + * @param id + * @return + */ + public static boolean isPwdchangeallowed(Identity id) { + + if(id == null) return isAnyPwdchangeallowed(); + + // if this is set to false, noone can change their pw + if (!pwdchangeallowed) + return false; + LDAPLoginManager ldapLoginManager = (LDAPLoginManager) CoreSpringFactory.getBean("org.olat.ldap.LDAPLoginManager"); + if (ldapLoginManager.isIdentityInLDAPSecGroup(id)) { + // it's an ldap-user + return pwdchangeallowedLDAP; + } + return pwdchangeallowed; + } + + /** + * use this if you don't have an identity-object (DMZ), and just want to + * check, if anyone could change his password + * + * @return + */ + private static boolean isAnyPwdchangeallowed() { return pwdchangeallowed; } diff --git a/src/main/java/org/olat/user/UserPropertiesConfig.java b/src/main/java/org/olat/user/UserPropertiesConfig.java index 9537465a764eb8dd1558dc5e31989a982dfcdd1a..11f9709dbea8f43dbbc5cca9937e60cdb7bd5b49 100644 --- a/src/main/java/org/olat/user/UserPropertiesConfig.java +++ b/src/main/java/org/olat/user/UserPropertiesConfig.java @@ -21,9 +21,11 @@ package org.olat.user; import java.util.List; +import java.util.Map; import org.olat.core.gui.translator.Translator; import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.olat.user.propertyhandlers.UserPropertyUsageContext; /** * <h3>Description:</h3> @@ -86,6 +88,16 @@ public interface UserPropertiesConfig { */ public List<UserPropertyHandler> getUserPropertyHandlersFor(String usageIdentifyer, boolean isAdministrativeUser); + /** + * sets the list of userPropertyHandlers of this config + */ + public void setUserPropertyHandlers(List<UserPropertyHandler> userPropertyHandlers); + + /** + * returns a map containing all the userPropertyUsageContexts + */ + public Map<String,UserPropertyUsageContext> getUserPropertyUsageContexts(); + /** * Get all available property handlers. Do not use this for forms or tables, * use this only to cleanup things diff --git a/src/main/java/org/olat/user/UserPropertiesController.java b/src/main/java/org/olat/user/UserPropertiesController.java index 030ace76e5967798aa47d5b1fc68064ce4bc1a4d..31f4b55cea9580eeb3450bafa973b06c25a80063 100644 --- a/src/main/java/org/olat/user/UserPropertiesController.java +++ b/src/main/java/org/olat/user/UserPropertiesController.java @@ -34,10 +34,11 @@ import org.olat.core.gui.components.table.TableController; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.control.Controller; -import org.olat.core.gui.control.ControllerEventListener; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.group.BusinessGroup; import org.olat.properties.Property; @@ -80,7 +81,8 @@ public class UserPropertiesController extends BasicController { tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.prop.moddat", 6, null, ureq.getLocale())); // property selection / id only for admins if (ureq.getUserSession().getRoles().isOLATAdmin()) { - tableCtr.addColumnDescriptor(new StaticColumnDescriptor("choose", "table.header.action", translate("action.choose"))); + //fxdiff FXOLAT-149 + tableCtr.addColumnDescriptor(new StaticColumnDescriptor("delete", "table.header.action", translate("delete"))); } tdm = new PropTableDataModel(l); tableCtr.setTableDataModel(tdm); @@ -108,8 +110,22 @@ public class UserPropertiesController extends BasicController { // Tell parentController that a subject has been found fireEvent(ureq, new PropFoundEvent(foundProp)); } + //fxdiff FXOLAT-149 + else if (actionid.equals("delete")) { + int rowid = te.getRowId(); + foundProp = (Property) tdm.getObject(rowid); + activateYesNoDialog(ureq, "really", "do you really", null); + } } } + //fxdiff FXOLAT-149 + else if (DialogBoxUIFactory.isYesEvent(event) && foundProp != null) { + PropertyManager.getInstance().deleteProperty(foundProp); + tdm.getObjects().remove(foundProp); + tableCtr.modelChanged(); + foundProp = null; + } + } /** diff --git a/src/main/java/org/olat/user/_content/prefs.html b/src/main/java/org/olat/user/_content/prefs.html index 3d1daaab8c4dba1954a1259aa7790cb32c1ceaa7..f968ac3a481e81a46a77f1a5ca70f79e8709c048 100644 --- a/src/main/java/org/olat/user/_content/prefs.html +++ b/src/main/java/org/olat/user/_content/prefs.html @@ -1,4 +1,5 @@ $r.render("general") $r.render("special") +$r.render("reset") diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties index 3aef0d99ab7d13fe9a8ea831174c6a983d69e838..f8d2c7f5bfde3402b66211ce5162d8d2165148a5 100644 --- a/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/user/_i18n/LocalStrings_de.properties @@ -188,7 +188,7 @@ email.change.dialog.text = Sie haben Ihre E-Mail-Adresse ge\u00e4ndert. Zur Akti email.change.dialog.text.usermanager = Sie haben die E-Mail-Adresse eines Benutzers ge\u00e4ndert. Zur Aktivierung der neuen Adresse wird dem Nutzer ein Aktivierungs-Link an diese gesendet. Wollen Sie fortfahren? Wenn Sie "Nein" ausw\u00e4hlen, wird die \u00c4nderung der E-Mail-Adresse verworfen! email.sent = Die E-Mail wurde erfolgreich versendet. email.notsent = Die E-Mail konnte nicht versendet werden. -email.change.body = *** Dies ist eine automatisch generierte Nachricht. Bitte antworten Sie nicht auf diese E-Mail! ***\n\nSie oder ein verantwortlicher Benutzerverwalter haben veranlasst, Ihre in OLAT eingetragene E-Mail-Adresse zu \u00e4ndern. Zur Aktivierung der neuen Adresse w\u00e4hlen Sie bitte den folgenden Link an und melden Sie sich auf OLAT an:\n\n{0}\n\nDieser Link ist nur einmal g\u00fcltig und verf\u00e4llt um {1} Uhr.\n\nBis zur erfolgreichen Aktivierung Ihrer neuen Adresse werden alle Nachrichten \u00fcber OLAT weiterhin an Ihre bisherige E-Mail-Adresse gesendet. Nach Ablauf der G\u00fcltigkeitsdauer wird der Antrag zur \u00c4nderung Ihrer E-Mail-Adresse verworfen. Sie haben selbstverst\u00e4ndlich jederzeit die M\u00f6glichkeit, Ihre in OLAT eingetragene E-Mail-Adresse erneut zu \u00e4ndern.\n\nIhr OLAT Team +email.change.body = *** Dies ist eine automatisch generierte Nachricht. Bitte antworten Sie nicht auf diese E-Mail! ***\n\nSie oder ein verantwortlicher Benutzerverwalter haben veranlasst, Ihre in OLAT eingetragene E-Mail-Adresse zu \u00e4ndern. \nSofern dies korrekt ist, wird Ihre bisherige Email {2} durch {3} ersetzt. \nZur Aktivierung der neuen Adresse w\u00e4hlen Sie bitte den folgenden Link an und melden Sie sich auf OLAT an:\n\n{0}\n\nDieser Link ist nur einmal g\u00fcltig und verf\u00e4llt um {1} Uhr.\n\nBis zur erfolgreichen Aktivierung Ihrer neuen Adresse werden alle Nachrichten \u00fcber OLAT weiterhin an Ihre bisherige E-Mail-Adresse gesendet. Nach Ablauf der G\u00fcltigkeitsdauer wird der Antrag zur \u00c4nderung Ihrer E-Mail-Adresse verworfen. Sie haben selbstverst\u00e4ndlich jederzeit die M\u00f6glichkeit, Ihre in OLAT eingetragene E-Mail-Adresse erneut zu \u00e4ndern.\n\nIhr OLAT Team email.change.subject = [OLAT] Aktivierung Ihrer neuen E-Mail-Adresse email.change.wherefrom = Diese Anfrage an den Server {0} wurde am {1} \nvon der IP-Adresse {2} abgeschickt. error.change.email = Dieser Aktivierungs-Link wurde bereits verwendet und ist somit nicht mehr g\u00fcltig. @@ -231,5 +231,10 @@ resume.none=Nein resume.auto=Ja, automatisch resume.ondemand=Ja, bei Bedarf - - +reset.title=Einstellungen Zurücksetzen +reset.desc=Hier können Sie die personalisierten Systemkonfiguration wieder auf die Standardeinstellung zurücksetzen. Um die Einstellungen zu aktivieren werden Sie automatisch aus dem System ausgeloggt! +reset.elements=Einstellungen +reset.elements.guiprefs=Personalisierung von Bildschirmkomponenten (Menü, Toolboxen, Tabellen, Portal, Kalender etc.) +reset.elements.sysprefs=Systemeinstellungen (Schriftgrösse, Benachrichtigungen, E-Mail Versand, Zeichensatz etc.) +reset.elements.resume=Sitzung wiederherstellen +reset.submit=Zurücksetzen \ No newline at end of file diff --git a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties index b3a8328a531a176bf0379b73baa0a4c820b110cc..244997326d34ccebdab10363086882b97054d6df 100644 --- a/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/user/_i18n/LocalStrings_en.properties @@ -95,7 +95,7 @@ chelp.vname=<i>$org.olat.user.propertyhandlers\:form.name.firstName</i> command.closehp=Close preview command.delete=Delete command.preview=Show preview -email.change.body=*** This is an automated message. Please do not reply\! ***\r\n\r\nYou or a user manager in authority arranged for a modification of your e-mail address in OLAT. In order to activate your new address, please use the following link and log in to OLAT\:\r\n\r\n{0}\r\n\r\nThis link can only be used once and expires at {1} hrs.\r\n\r\nUntil activating your new address successfully all OLAT messages will be sent to your old e-mail address. After the expiration of this link your application for a modification of your e-mail address will be dismissed. However, you can always arrange for a new modification of your e-mail address in OLAT.\r\n\r\nYour OLAT Team +email.change.body=*** This is an automated message. Please do not reply\! ***\r\n\r\nYou or a user manager in authority arranged for a modification of your e-mail address in OLAT.\nIf all this is correct, your former email {2} will be changed into {3}. In order to activate your new address, please use the following link and log in to OLAT\:\r\n\r\n{0}\r\n\r\nThis link can only be used once and expires at {1} hrs.\r\n\r\nUntil activating your new address successfully all OLAT messages will be sent to your old e-mail address. After the expiration of this link your application for a modification of your e-mail address will be dismissed. However, you can always arrange for a new modification of your e-mail address in OLAT.\r\n\r\nYour OLAT Team email.change.dialog.text=You have modified your E-mail address. To activate it you will receive a corresponding link sent to your new address. Do you want to proceed? By selecting "No" your application for a modification of your E-mail address will be dismissed\! email.change.dialog.text.usermanager=You have modified the E-mail address of another user. To activate it this user will receive a corresponding link sent to the new address. Do you want to proceed? By selecting "No" your application for modification will be dismissed\! email.change.dialog.title=Modification of an E-mail address @@ -223,3 +223,11 @@ ul.upload=Upload user.deleted=This user has been deleted in OLAT user.preferences.successful=The system settings of user ({0}) have been saved. All changes regarding language settings and AJAX mode will be active when logging in next time. user.preferences.unsuccessful=The system settings of user ({0}) could not be saved. Please try again. + +reset.title=Reset configurations +reset.desc=You can reset your personalized system configuration to the default values using this form. To activate the changes the system will automatically trigger a logout. +reset.elements=Configurations +reset.elements.guiprefs=Personalized interface components (menu, tool boxes, tables, portal, calendar etc.) +reset.elements.sysprefs=System configuration (font size, notifications, e-mail, character set etc.) +reset.elements.resume=Session resume +reset.submit=Reset diff --git a/src/main/java/org/olat/user/_spring/userContext.xml b/src/main/java/org/olat/user/_spring/userContext.xml index d32fb701d3fa92e210a077fcd7ddc0f76c60c41a..6d3d5be18369e3b878ee9ee53479ba361b1c8209 100644 --- a/src/main/java/org/olat/user/_spring/userContext.xml +++ b/src/main/java/org/olat/user/_spring/userContext.xml @@ -235,6 +235,7 @@ <value> generateTestUsers=${user.generateTestUsers} passwordChangeAllowed=${password.change.allowed} + passwordChangeAllowedLDAP=${ldap.propagatePasswordChangedOnLdapServer} adminUserName=administrator </value> </property> @@ -272,4 +273,10 @@ <constructor-arg ref="userDeletionManager"></constructor-arg> </bean> + <!-- Inbox in users Home --> + <bean id="mailBoxExtension" class="org.olat.user.MailBoxExtension"> + <constructor-arg index="0" ref="userDeletionManager" /> + <property name="mailManager" ref="mailManager" /> + </bean> + </beans> \ No newline at end of file diff --git a/src/main/java/org/olat/user/propertyhandlers/ICQPropertyHandler.java b/src/main/java/org/olat/user/propertyhandlers/ICQPropertyHandler.java index 63c6bdf405c5b09e61b728f861e339da56737aa1..0e189a12203091a29ede67fa0fb001744bcfbb0a 100644 --- a/src/main/java/org/olat/user/propertyhandlers/ICQPropertyHandler.java +++ b/src/main/java/org/olat/user/propertyhandlers/ICQPropertyHandler.java @@ -25,8 +25,6 @@ import java.util.Map; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpClientParams; import org.olat.core.gui.components.form.ValidationError; @@ -50,10 +48,10 @@ import org.olat.user.UserManager; */ public class ICQPropertyHandler extends Generic127CharTextPropertyHandler { + public static final int ICQ_NAME_MIN_LENGTH = 5; public static final int ICQ_NAME_MAX_LENGTH = 16; - public static final String ICQ_INDICATOR_URL = "http://status.icq.com/online.gif"; - public static final String ICQ_NAME_VALIDATION_URL = "http://www.icq.com/people/about_me.php"; - public static final String ICQ_NAME_URL_PARAMETER = "uin"; + public static final String ICQ_INDICATOR_URL = "http://status.icq.com/online.gif"; + public static final String ICQ_NAME_VALIDATION_URL = "http://www.icq.com/people/"; /** * @see org.olat.user.AbstractUserPropertyHandler#getUserPropertyAsHTML(org.olat.core.id.User, @@ -61,11 +59,13 @@ public class ICQPropertyHandler extends Generic127CharTextPropertyHandler { */ @Override public String getUserPropertyAsHTML(User user, Locale locale) { + // return super.getUserPropertyAsHTML(user, locale); String icqname = getUserProperty(user, locale); if (StringHelper.containsNonWhitespace(icqname)) { StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append("<a href=\"" + ICQ_NAME_VALIDATION_URL + "?" + ICQ_NAME_URL_PARAMETER +"=" + icqname + "\" target=\"_blank\">" + icqname + "</a>"); - stringBuffer.append("<img src=\"" + ICQ_INDICATOR_URL + "?icq=" + icqname + "&img=5\" style=\"width:10px; height:10px; margin-left:2px;\">"); + stringBuffer.append("<a href=\"" + ICQ_NAME_VALIDATION_URL + "" + icqname + "\" target=\"_blank\">" + icqname + "</a>"); + stringBuffer.append("<img src=\"" + ICQ_INDICATOR_URL + "?icq=" + icqname + + "&img=5\" style=\"width:10px; height:10px; margin-left:2px;\">"); return stringBuffer.toString(); } else { return null; @@ -81,10 +81,32 @@ public class ICQPropertyHandler extends Generic127CharTextPropertyHandler { if (!super.isValidValue(value, validationError, locale)) { return false; } - if (StringHelper.containsNonWhitespace(value)) { - return value.length() <= ICQ_NAME_MAX_LENGTH; + + // allow empty string + if (!StringHelper.containsNonWhitespace(value)) + return true; + return isValidICQNumber(value); + } + + /** + * checks wheter given string is numerical and not too long. DOES NOT check + * if a icq user exists with this number! + * + * @param input + * @return + */ + private boolean isValidICQNumber(String input) { + if (StringHelper.containsNonWhitespace(input)) { + if (input.length() > ICQ_NAME_MAX_LENGTH || input.length() < ICQ_NAME_MIN_LENGTH) + return false; + try { + Long.parseLong(input); + } catch (NumberFormatException e) { + return false; + } + return true; } - return true; + return false; } /** @@ -103,56 +125,27 @@ public class ICQPropertyHandler extends Generic127CharTextPropertyHandler { } return textElement; } - + /** - * @see org.olat.user.propertyhandlers.Generic127CharTextPropertyHandler#isValid(org.olat.core.gui.components.form.flexible.FormItem, java.util.Map) + * @see org.olat.user.propertyhandlers.Generic127CharTextPropertyHandler#isValid(org.olat.core.gui.components.form.flexible.FormItem, + * java.util.Map) */ - @SuppressWarnings({"unchecked", "unused"}) + @SuppressWarnings({ "unchecked", "unused" }) @Override public boolean isValid(FormItem formItem, Map formContext) { - boolean result; - TextElement textElement = (TextElement)formItem; - OLog log = Tracing.createLoggerFor(this.getClass()); + // FXOLAT-343 :: + // there's no official icq-api to check if a user exists.. + // the previous check failed (nov 2011), urls changed etc... + // so check only for numerical value and length! + TextElement textElement = (TextElement) formItem; if (StringHelper.containsNonWhitespace(textElement.getValue())) { - - // Use an HttpClient to fetch a profile information page from ICQ. - HttpClient httpClient = HttpClientFactory.getHttpClientInstance(); - HttpClientParams httpClientParams = httpClient.getParams(); - httpClientParams.setConnectionManagerTimeout(2500); - httpClient.setParams(httpClientParams); - HttpMethod httpMethod = new GetMethod(ICQ_NAME_VALIDATION_URL); - NameValuePair uinParam = new NameValuePair(ICQ_NAME_URL_PARAMETER, textElement.getValue()); - httpMethod.setQueryString(new NameValuePair[] {uinParam}); - // Don't allow redirects since otherwise, we won't be able to get the HTTP 302 further down. - httpMethod.setFollowRedirects(false); - try { - // Get the user profile page - httpClient.executeMethod(httpMethod); - int httpStatusCode = httpMethod.getStatusCode(); - // Looking at the HTTP status code tells us whether a user with the given ICQ name exists. - if (httpStatusCode == HttpStatus.SC_OK) { - // ICQ tells us that a user name is valid if it sends an HTTP 200... - result = true; - } else if (httpStatusCode == HttpStatus.SC_MOVED_TEMPORARILY) { - // ...and if it's invalid, it sends an HTTP 302. - textElement.setErrorKey("form.name.icq.error", null); - result = false; - } else { - // For HTTP status codes other than 200 and 302 we will silently assume that the given ICQ name is valid, but inform the user about this. - textElement.setExampleKey("form.example.icqname.notvalidated", null); - log.warn("ICQ name validation: Expected HTTP status 200 or 301, but got " + httpStatusCode); - result = true; - } - } catch (Exception e) { - // In case of any exception, assume that the given ICQ name is valid (The opposite would block easily upon network problems), and inform the user about this. - textElement.setExampleKey("form.example.icqname.notvalidated", null); - log.warn("ICQ name validation: Exception: " + e.getMessage()); - result = true; + boolean valid = isValidICQNumber(textElement.getValue()); + if (!valid) { + textElement.setErrorKey("form.name.icq.error", null); } - } else { - result = true; + return valid; } - log = null; - return result; + return true; + } } diff --git a/src/main/java/org/olat/user/propertyhandlers/UserPropertiesConfigImpl.java b/src/main/java/org/olat/user/propertyhandlers/UserPropertiesConfigImpl.java index 238281b3a3861bf221925c51fe337cff338d3869..9d36f563be29f567de5a7f35dc03aa4f92e37f06 100644 --- a/src/main/java/org/olat/user/propertyhandlers/UserPropertiesConfigImpl.java +++ b/src/main/java/org/olat/user/propertyhandlers/UserPropertiesConfigImpl.java @@ -33,7 +33,6 @@ import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.logging.LogDelegator; import org.olat.core.logging.OLATRuntimeException; -import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LogModule; import org.olat.user.UserPropertiesConfig; @@ -52,15 +51,15 @@ public class UserPropertiesConfigImpl extends LogDelegator implements UserProper private Map<String, UserPropertyHandler> userPropertyNameLookupMap; - private Map<String, List> userPropertyUsageContextsLookupMap = new HashMap(); + private Map<String, List<UserPropertyHandler>> userPropertyUsageContextsLookupMap = new HashMap<String, List<UserPropertyHandler>>(); private List<UserPropertyHandler> userPropertyHandlers; private Map<String, UserPropertyUsageContext> userPropertyUsageContexts; public void init() { - List<UserPropertyHandler> userPropertyHandlers = getUserPropertyHandlersFor(USER_PROPERTY_LOG_CONFIGURATION, false); + List<UserPropertyHandler> userPropHandlers = getUserPropertyHandlersFor(USER_PROPERTY_LOG_CONFIGURATION, false); Set<String> userProperties = new LinkedHashSet<String>(); - for (Iterator<UserPropertyHandler> iterator = userPropertyHandlers.iterator(); iterator.hasNext();) { + for (Iterator<UserPropertyHandler> iterator = userPropHandlers.iterator(); iterator.hasNext();) { userProperties.add(iterator.next().getName()); } LogModule.setUserProperties(userProperties); @@ -73,6 +72,10 @@ public class UserPropertiesConfigImpl extends LogDelegator implements UserProper public void setUserPropertyUsageContexts(Map<String,UserPropertyUsageContext> userPropertyUsageContexts) { this.userPropertyUsageContexts = userPropertyUsageContexts; } + + public Map<String,UserPropertyUsageContext> getUserPropertyUsageContexts(){ + return this.userPropertyUsageContexts; + } /** * Spring setter @@ -171,11 +174,11 @@ public class UserPropertiesConfigImpl extends LogDelegator implements UserProper UserPropertyUsageContext currentUsageConfig = userPropertyUsageContexts.get(usageIdentifyer); if (currentUsageConfig == null) { currentUsageConfig = userPropertyUsageContexts.get("default"); - Tracing.logWarn("Could not find user property usage configuration for usageIdentifyer::" + usageIdentifyer - + ", please check yout olat_userconfig.xml file. Using default configuration instead.", UserPropertiesConfigImpl.class); - if (currentUsageConfig == null) { - throw new OLATRuntimeException("Missing default user property usage configuratoin in olat_userconfig.xml", null); - } + logWarn( + "Could not find user property usage configuration for usageIdentifyer::" + usageIdentifyer + + ", please check yout olat_userconfig.xml file. Using default configuration instead.", null); + if (currentUsageConfig == null) { throw new OLATRuntimeException( + "Missing default user property usage configuratoin in olat_userconfig.xml", null); } } return currentUsageConfig; } diff --git a/src/main/java/org/olat/user/propertyhandlers/UserPropertyHandler.java b/src/main/java/org/olat/user/propertyhandlers/UserPropertyHandler.java index df6d77b2d466eb04767d99df706f9e2145c09f9e..a841f018c93522c784a1b6504675e609af7f841b 100644 --- a/src/main/java/org/olat/user/propertyhandlers/UserPropertyHandler.java +++ b/src/main/java/org/olat/user/propertyhandlers/UserPropertyHandler.java @@ -66,6 +66,13 @@ public interface UserPropertyHandler extends ItemValidatorProvider { */ public String getGroup(); + + /** + * + * @param groupName The group identifyer string of this UserPropertyHandler + */ + public void setGroup(String groupName); + /** * @param user the user for which we want to get the value * @param locale the current users locale or NULL if default locale should be diff --git a/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java b/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java index 17aceef7d0e714b7be9442ae88c33f25f9095c46..d8f9ddf2af9142324179814f6c56fca6eca1b953 100644 --- a/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java +++ b/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java @@ -39,49 +39,111 @@ import java.util.Set; * @author Florian Gnaegi, frentix GmbH, http://www.frentix.com */ public class UserPropertyUsageContext { - + + private String description = ""; + private List<UserPropertyHandler> propertyHandlers = new ArrayList<UserPropertyHandler>(); private Set<UserPropertyHandler> mandatoryProperties = new HashSet<UserPropertyHandler>(); - private Set<UserPropertyHandler> adminViewOnlyProperties = new HashSet<UserPropertyHandler>(); - private Set<UserPropertyHandler> userViewReadOnlyProperties = new HashSet<UserPropertyHandler>(); - + private Set<UserPropertyHandler> adminViewOnlyProperties = new HashSet<UserPropertyHandler>(); + private Set<UserPropertyHandler> userViewReadOnlyProperties = new HashSet<UserPropertyHandler>(); + /** * Spring setter + * * @param propertyHandlers */ public void setPropertyHandlers(List<UserPropertyHandler> propertyHandlers) { this.propertyHandlers = propertyHandlers; } + /** * Spring setter + * * @param mandatoryProperties */ public void setMandatoryProperties(Set<UserPropertyHandler> mandatoryProperties) { this.mandatoryProperties = mandatoryProperties; } + /** * Spring setter + * * @param adminViewOnlyProperties */ public void setAdminViewOnlyProperties(Set<UserPropertyHandler> adminViewOnlyProperties) { this.adminViewOnlyProperties = adminViewOnlyProperties; } + /** * Spring setter + * * @param userViewReadOnlyProperties */ public void setUserViewReadOnlyProperties(Set<UserPropertyHandler> userViewReadOnlyProperties) { this.userViewReadOnlyProperties = userViewReadOnlyProperties; } + /** + * Spring setter + * + * @param description + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * returns the description of this context (description is defined injected + * via spring (xml) ) + * + * @return + */ + public String getDescription() { + return description; + } + /** * Get a list of all all property handlers available in this context + * * @return */ public List<UserPropertyHandler> getPropertyHandlers() { return propertyHandlers; } - + + /** + * adds the given handler to this context + * + * @param propertyHandler + */ + public void addPropertyHandler(UserPropertyHandler propertyHandler) { + if (propertyHandlers.contains(propertyHandler)) return; // do not add twice + propertyHandlers.add(propertyHandler); + } + + /** + * removes the given propertyHandler from this context + * + * @param propertyHandler + */ + public void removePropertyHandler(UserPropertyHandler propertyHandler) { + propertyHandlers.remove(propertyHandler); + // could be in one of the sets, remove there as well! (consistency) + mandatoryProperties.remove(propertyHandler); + adminViewOnlyProperties.remove(propertyHandler); + userViewReadOnlyProperties.remove(propertyHandler); + } + + /** + * checks whether this context contains the given handler + * + * @param propertyHandler + * @return returns true if the given handler is part of this context + */ + public boolean contains(UserPropertyHandler propertyHandler) { + return propertyHandlers.contains(propertyHandler); + } + /** * Check if this property handler is mandatory in this context. In forms this * means that the input field is marked as mandatory, in tables it means that @@ -94,6 +156,23 @@ public class UserPropertyUsageContext { return mandatoryProperties.contains(propertyHandler); } + /** + * adds or removes the given propertyHandler to the set of mandatory handlers. + * if given propertyHandler is not part of this context, the handler is not + * added to the set. (consistency) + * + * @param propertyHandler The propertyHandler to add to the set of mandatory + * Handlers + */ + public void setAsMandatoryUserProperty(UserPropertyHandler propertyHandler, boolean isMandatory) { + if (isMandatory) { + if (!this.propertyHandlers.contains(propertyHandler)) return; + this.mandatoryProperties.add(propertyHandler); + } else { + this.mandatoryProperties.remove(propertyHandler); + } + } + /** * Check if this property handler is only visible to administrative users. * Normal users won't see it in this context. This value overrides the entry @@ -105,7 +184,25 @@ public class UserPropertyUsageContext { public boolean isForAdministrativeUserOnly(UserPropertyHandler propertyHandler) { return adminViewOnlyProperties.contains(propertyHandler); } - + + /** + * adds or removes the given propertyHandler to the set of adminOnly handlers. + * if given propertyHandler is not part of this context, the handler is not + * added to the set. (consistency) + * + * @param propertyHandler The propertyHandler to add to the set of + * adminUserOnly Handlers + * @param isAdminOnly + */ + public void setAsAdminstrativeUserOnly(UserPropertyHandler propertyHandler, boolean isAdminOnly) { + if (isAdminOnly) { + if (!this.propertyHandlers.contains(propertyHandler)) return; + this.adminViewOnlyProperties.add(propertyHandler); + } else { + this.adminViewOnlyProperties.remove(propertyHandler); + } + } + /** * Check if this property is read only for normal user of read/write. * Administrative users will override this configuration @@ -117,5 +214,66 @@ public class UserPropertyUsageContext { return userViewReadOnlyProperties.contains(propertyHandler); } - + /** + * adds or removes the given propertyHandler to the set of userReadOnly + * handlers. if given propertyHandler is not part of this context, the handler + * is not added to the set. (consistency) + * + * @param propertyHandler The propertyHandler to add to the set of + * userReadOnly Handlers + * @param isUserReadOnly + */ + public void setAsUserViewReadOnly(UserPropertyHandler propertyHandler, boolean isUserReadOnly) { + if (isUserReadOnly) { + if (!this.propertyHandlers.contains(propertyHandler)) return; + this.userViewReadOnlyProperties.add(propertyHandler); + } else { + this.userViewReadOnlyProperties.remove(propertyHandler); + } + } + + /** + * Moves the given Handler one position up in the propertyHandlers-List of + * this context If the given Handler is already at the first position, it is + * moved to the end of the list + * + * if the given Handler is not part of this context, nothing is changed. + * + * @param propertyHandler + */ + public void moveHandlerUp(UserPropertyHandler propertyHandler) { + int indexBefore = propertyHandlers.indexOf(propertyHandler); + if (indexBefore < 0) return; + if (indexBefore == 0) { + propertyHandlers.remove(indexBefore); + propertyHandlers.add(propertyHandler); + } else { + propertyHandlers.remove(indexBefore); + propertyHandlers.add(indexBefore - 1, propertyHandler); + } + + } + + /** + * moves the given Handler one position down in the propertyHandlers-List of + * this context. If the given Handler is already at the last position, it is + * moved to top (first position) + * + * if the given Handler is not part of this context, nothing is changed. + * + * @param propertyHandler + */ + public void moveHandlerDown(UserPropertyHandler propertyHandler) { + int indexBefore = propertyHandlers.indexOf(propertyHandler); + if (indexBefore < 0) return; + + if (indexBefore == (propertyHandlers.size() - 1)) { + propertyHandlers.remove(indexBefore); + propertyHandlers.add(0, propertyHandler); + } else { + propertyHandlers.remove(indexBefore); + propertyHandlers.add(indexBefore + 1, propertyHandler); + } + } + } diff --git a/src/main/java/org/olat/user/propertyhandlers/XingPropertyHandler.java b/src/main/java/org/olat/user/propertyhandlers/XingPropertyHandler.java index bb6010cdda419e46720b39b1f4a22e54255dce4d..6131b241609a71e09ba02fc2ee321eb036c028f5 100644 --- a/src/main/java/org/olat/user/propertyhandlers/XingPropertyHandler.java +++ b/src/main/java/org/olat/user/propertyhandlers/XingPropertyHandler.java @@ -23,20 +23,12 @@ package org.olat.user.propertyhandlers; import java.util.Locale; import java.util.Map; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.params.HttpClientParams; import org.olat.core.gui.components.form.ValidationError; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.id.User; -import org.olat.core.logging.OLog; -import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; -import org.olat.core.util.httpclient.HttpClientFactory; import org.olat.user.UserManager; /** @@ -83,73 +75,34 @@ public class XingPropertyHandler extends Generic127CharTextPropertyHandler { } return textElement; } - + /** - * @see org.olat.user.AbstractUserPropertyHandler#getUserPropertyAsHTML(org.olat.core.id.User, java.util.Locale) + * @see org.olat.user.AbstractUserPropertyHandler#getUserPropertyAsHTML(org.olat.core.id.User, + * java.util.Locale) */ @Override public String getUserPropertyAsHTML(User user, Locale locale) { + // FXOLAT-343 :: can't search by user-email on xing... just link to xing-homepage String xingname = getUserProperty(user, locale); if (StringHelper.containsNonWhitespace(xingname)) { StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append("<a href=\"" + XING_NAME_VALIDATION_URL + xingname + "\" target=\"_blank\">" + xingname + "</a>"); + stringBuffer.append("<a href=\"http://www.xing.com\" target=\"_blank\">" + xingname + "</a>"); return stringBuffer.toString(); } else { return null; } } - + /** - * @see org.olat.user.propertyhandlers.Generic127CharTextPropertyHandler#isValid(org.olat.core.gui.components.form.flexible.FormItem, java.util.Map) + * @see org.olat.user.propertyhandlers.Generic127CharTextPropertyHandler#isValid(org.olat.core.gui.components.form.flexible.FormItem, + * java.util.Map) */ - @SuppressWarnings({"unused", "unchecked"}) @Override public boolean isValid(FormItem formItem, Map formContext) { - boolean result; - TextElement textElement = (TextElement)formItem; - OLog log = Tracing.createLoggerFor(this.getClass()); + TextElement textElement = (TextElement) formItem; if (StringHelper.containsNonWhitespace(textElement.getValue())) { - HttpClient httpClient = HttpClientFactory.getHttpClientInstance(); - HttpClientParams httpClientParams = httpClient.getParams(); - httpClientParams.setConnectionManagerTimeout(2500); - httpClient.setParams(httpClientParams); - try { - // Could throw IllegalArgumentException if argument is not a valid url - // (e.g. contains whitespaces) - HttpMethod httpMethod = new GetMethod(XING_NAME_VALIDATION_URL + textElement.getValue()); - // Don't allow redirects since otherwise, we won't be able to get the correct status - httpMethod.setFollowRedirects(false); - // Get the user profile page - httpClient.executeMethod(httpMethod); - int httpStatusCode = httpMethod.getStatusCode(); - // Looking at the HTTP status code tells us whether a user with the given Xing name exists. - if (httpStatusCode == HttpStatus.SC_OK) { - // If the user exists, we get a 200... - result = true; - } else if (httpStatusCode == HttpStatus.SC_MOVED_PERMANENTLY) { - // ... and if he doesn't exist, we get a 301. - textElement.setErrorKey("form.name.xing.error", null); - result = false; - } else { - // In case of any exception, assume that the given MSN name is valid (The opposite would block easily upon network problems), and inform the user about this. - textElement.setExampleKey("form.example.xingname.notvalidated", null); - log.warn("Xing name validation: Expected HTTP status 200 or 301, but got " + httpStatusCode); - result = true; - } - } catch (IllegalArgumentException e) { - // The xing name is not url compatible (e.g. contains whitespaces) - textElement.setErrorKey("form.xingname.notvalid", null); - result = false; - } catch (Exception e) { - // In case of any exception, assume that the given MSN name is valid (The opposite would block easily upon network problems), and inform the user about this. - textElement.setExampleKey("form.example.xingname.notvalidated", null); - log.warn("Xing name validation: Exception: " + e.getMessage()); - result = true; - } - } else { - result = true; + return textElement.getValue().length() <= XING_NAME_MAX_LENGTH; } - log = null; - return result; + return true; } } diff --git a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties index e0f822a2ef418610849ea6d89e20f8daa402cc9e..e1510bcc1bcc51a981a921ad8d8767160c6adc76 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties @@ -1,15 +1,14 @@ -#Mon Mar 02 09:54:04 CET 2009 -form.example.free = {0} -form.example.skypename = (meinskypename) -form.example.phone = (+41 12 345 67 89) -form.example.url = (http://www.openolat.org) -form.example.msnname = (msnbenutzer@hotmail.com) +#Thu Aug 25 10:27:06 CEST 2011 +form.example.free={0} +form.example.icqname=(16827354) +form.example.icqname.notvalidated=Ihre ICQ-Nummer konnte nicht \u00FCberpr\u00FCft werden und wird als g\u00FCltig angenommen. +form.example.msnname=(msnbenutzer@hotmail.com) form.example.msnname.notvalidated=Ihr MSN-Name konnte nicht \u00FCberpr\u00FCft werden und wird als g\u00FCltig angenommen. -form.example.xingname = (xingbenutzer oder E-Mail-Adresse) +form.example.phone=(+41 12 345 67 89) +form.example.skypename=(meinskypename) +form.example.url=(http\://www.openolat.org) +form.example.xingname=(xingbenutzer oder E-Mail-Adresse) form.example.xingname.notvalidated=Ihr XING-Name konnte nicht \u00FCberpr\u00FCft werden und wird als g\u00FCltig angenommen. -form.xingname.notvalid=Der XING-Name ist ung\u00FCltig. -form.example.icqname = (16827354) -form.example.icqname.notvalidated=Ihre ICQ-Nummer konnte nicht \u00FCberpr\u00FCft werden und wird als g\u00FCltig angenommen. form.group.account=Benutzername form.group.address=Adresse form.group.contact=Kontaktdaten @@ -22,13 +21,17 @@ form.name.city=Stadt form.name.city.error.empty=Das Feld "Stadt" darf nicht leer sein. form.name.country=Land form.name.country.error.empty=Das Feld "Land" darf nicht leer sein. +form.name.degree=Akademischer Grad +form.name.department=Dienststelle / Firma form.name.email=E-Mail form.name.email.error.empty=Das Feld "E-Mail" darf nicht leer sein. form.name.email.error.exists=Diese E-Mail-Adresse wird bereits von einem anderen Benutzer verwendet. form.name.email.error.valid=Bitte geben Sie eine g\u00FCltige E-Mail-Adresse an. form.name.emailDisabled=E-Mail-Adresse gesperrt +form.name.emchangeKey=E-Mail-Adresse ge\u00E4ndert form.name.extendedAddress=Adresszusatz form.name.extendedAddress.error.empty=Das Feld "Adresszusatz" darf nicht leer sein. +form.name.extendedOfficeAddress=Dienstadresszusatz form.name.firstName=Vorname form.name.firstName.error.empty=Das Feld "Vorname" darf nicht leer sein. form.name.gender=Geschlecht @@ -52,10 +55,19 @@ form.name.lastName=Nachname form.name.lastName.error.empty=Das Feld "Nachname" darf nicht leer sein. form.name.msn=MSN form.name.msn.error=Bitte geben Sie einen g\u00FCltigen MSN-Namen an. +form.name.officeCity=Dienst-Stadt +form.name.officeCountry=Dienst-Land +form.name.officeMobilePhone=Dienst-Mobiltelefon +form.name.officePoBox=Dienstpostfach +form.name.officeStreet=Strasse / Postfach +form.name.officeZipCode=Dienst-Postleitzahl form.name.orgUnit=Organisationseinheit / Studiengruppe form.name.orgUnit.error.empty=Das Feld "Organisationseinheit / Studiengruppe" darf nicht leer sein. form.name.poBox=Postfach form.name.poBox.error.empty=Das Feld "Postfach" darf nicht leer sein. +form.name.position=Funktion / Stellung +form.name.privateEmail=E-Mail (Privat) +form.name.rank=Dienstgrad / Amtsbezeichnung form.name.region=Region / Kanton form.name.region.error.empty=Das Feld "Region / Kanton" darf nicht leer sein. form.name.skype=Skype ID @@ -74,9 +86,10 @@ form.name.telPrivate=Telefon Privat form.name.telPrivate.error.empty=Das Feld "Telefon Privat" darf nicht leer sein. form.name.telPrivate.error.valid=Bitte geben Sie eine g\u00FCltige Telefonnummer an. form.name.xing=Xing -form.name.xing.error=Bitte geben Sie einen g\u00FCltigen Xing-Namen an (Entspricht dem in der WWW-Adresse Ihres Xing-Profiles, http://www.xing.com/profile/<Ihr Benutzername>) +form.name.xing.error=Bitte geben Sie einen g\u00FCltigen Xing-Namen an (Entspricht dem in der WWW-Adresse Ihres Xing-Profiles, http\://www.xing.com/profile/<Ihr Benutzername>) form.name.zipCode=Postleitzahl form.name.zipCode.error.empty=Das Feld "Postleitzahl" darf nicht leer sein. +form.xingname.notvalid=Der XING-Name ist ung\u00FCltig. gender.error=Ung\u00FCltiger Wert f\u00FCr Geschlecht general.error.max.127=Wert enth\u00E4lt mehr wie 127 Zeichen general.error.max.32=Wert enth\u00E4lt mehr wie 32 Zeichen @@ -87,6 +100,7 @@ import.example.email=peter.muster@demo.ch import.example.extendedAddress=10 import.example.firstName=Peter import.example.gender=male +import.example.grade=Grad import.example.homepage=http\://www.demo.ch import.example.icqname=16827354 import.example.institutionalEmail=peter.muster@uzh.ch @@ -94,6 +108,7 @@ import.example.institutionalName=Universit\u00E4t Z\u00FCrich import.example.institutionalUserIdentifier=08-123-987 import.example.lastName=Muster import.example.msnname=msnbenutzer@hotmail.com +import.example.msn=$:import.example.msnname import.example.orgUnit=Rechtswissenschaftliche Fakult\u00E4t import.example.poBox=Postfach import.example.region=ZH @@ -108,9 +123,13 @@ import.example.zipCode=8000 table.name.birthDay=Geburtsdatum table.name.city=Stadt table.name.country=Land +table.name.degree=Akademischer Grad +table.name.department=Dienststelle / Firma table.name.email=E-Mail table.name.emailDisabled=E-Mail-Adresse gesperrt +table.name.emchangeKey=E-Mail-Adresse ge\u00E4ndert table.name.extendedAddress=Adresszusatz +table.name.extendedOfficeAddress=Dienstadresszusatz table.name.firstName=Vorname table.name.gender=Geschlecht table.name.homepage=Homepage @@ -120,8 +139,17 @@ table.name.institutionalName=Institution table.name.institutionalUserIdentifier=Institutionsnummer table.name.lastName=Nachname table.name.msn=MSN ID +table.name.officeCity=Dienst-Stadt +table.name.officeCountry=Dienst-Land +table.name.officeMobilePhone=Dienst-Mobiltelefon +table.name.officePoBox=Dienstpostfach +table.name.officeStreet=Strasse / Postfach +table.name.officeZipCode=Dienst-Postleitzahl table.name.orgUnit=Studiengruppe table.name.poBox=Postfach +table.name.position=Funktion / Stellung +table.name.privateEmail=E-Mail (Privat) +table.name.rank=Dienstgrad / Amtsbezeichnung table.name.region=Region table.name.skype=Skype ID table.name.street=Strasse diff --git a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties index 62d172927a65463f31f1c9b0a4411350aa6f09b2..82983be2a4ffbd7de9ab6c69d1f1e5cf2fddf48a 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties @@ -1,4 +1,4 @@ -#Fri Jan 21 18:58:18 CET 2011 +#Thu May 26 09:52:28 CEST 2011 form.example.free={0} form.example.icqname=(16827354) form.example.icqname.notvalidated=Your ICQ number could not be checked and is therefore assumed valid. @@ -21,13 +21,17 @@ form.name.city=City form.name.city.error.empty=The field "City" is mandatory. form.name.country=Country form.name.country.error.empty=The field "Country" is mandatory. +form.name.degree=Academic degree +form.name.department=Department / Company form.name.email=E-mail form.name.email.error.empty=The field "E-mail" is mandatory. form.name.email.error.exists=This e-mail address is already used by another user form.name.email.error.valid=Please enter a valid e-mail address. form.name.emailDisabled=E-mail address disabled +form.name.emchangeKey=E-mail address changed form.name.extendedAddress=Extra address line form.name.extendedAddress.error.empty=The field "Extra address line" is mandatory. +form.name.extendedOfficeAddress=Extended office address form.name.firstName=First name form.name.firstName.error.empty=The field "First name" is mandatory. form.name.gender=Gender @@ -51,10 +55,19 @@ form.name.lastName=Last name form.name.lastName.error.empty=The field "Last name" is mandatory. form.name.msn=MSN form.name.msn.error=Please indicate a valid MSN name. +form.name.officeCity=Office city +form.name.officeCountry=Office country +form.name.officeMobilePhone=Office mobile phone +form.name.officePoBox=Office P.O box +form.name.officeStreet=Address / P.O. box +form.name.officeZipCode=Office ZIP form.name.orgUnit=Organizational unit/study group form.name.orgUnit.error.empty=The field "Organizational unit/study group" is mandatory. form.name.poBox=P.O. Box form.name.poBox.error.empty=The field "P.O. Box" is mandatory. +form.name.position=Role / position +form.name.privateEmail=E-mail (private) +form.name.rank=Service grade / employment title form.name.region=Region/canton form.name.region.error.empty=The field "Region/canton" is mandatory. form.name.skype=Skype ID @@ -72,6 +85,7 @@ form.name.telOffice.error.valid=Please enter a valid phone number form.name.telPrivate=Phone number private form.name.telPrivate.error.empty=The field "Phone number private" is mandatory. form.name.telPrivate.error.valid=Please enter a valid phone number +form.name.userInterests=Expertise form.name.xing=Xing form.name.xing.error=Please indicate a valid Xing name (the one in the www address of your Xing profile, http\://www.xing.com/profile/<your user name>) form.name.zipCode=Zip code @@ -87,6 +101,7 @@ import.example.email=john.doe@demo.com import.example.extendedAddress=10 import.example.firstName=John import.example.gender=male +import.example.grade=Grade import.example.homepage=http\://www.demo.com import.example.icqname=16827354 import.example.institutionalEmail=john.doe@uox.com @@ -94,6 +109,7 @@ import.example.institutionalName=University of Example import.example.institutionalUserIdentifier=08-123-987 import.example.lastName=Doe import.example.msnname=msnuser@hotmail.com +import.example.msn=$:import.example.msnname import.example.orgUnit=Faculty of law import.example.poBox=P.O. Box import.example.region=NY @@ -108,9 +124,13 @@ import.example.zipCode=10000 table.name.birthDay=Birth date table.name.city=City table.name.country=Country +table.name.degree=Academic grade +table.name.department=Department / company table.name.email=E-mail table.name.emailDisabled=E-mail address disabled +table.name.emchangeKey=E-Mail address changed table.name.extendedAddress=Extra address line +table.name.extendedOfficeAddress=Extended office address table.name.firstName=First name table.name.gender=Gender table.name.homepage=Homepage @@ -120,8 +140,17 @@ table.name.institutionalName=Institution table.name.institutionalUserIdentifier=Institution identifier table.name.lastName=Last name table.name.msn=MSN ID +table.name.officeCity=Office city +table.name.officeCountry=Office country +table.name.officeMobilePhone=Office mobile phone +table.name.officePoBox=Office P.O. box +table.name.officeStreet=Address / P.O box +table.name.officeZipCode=Office ZIP table.name.orgUnit=Study group table.name.poBox=P.O. Box +table.name.position=Role / position +table.name.privateEmail=E-mail (private) +table.name.rank=Service grade / employment title table.name.region=Region table.name.skype=Skype ID table.name.street=Street diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index 784e4063d52c9899e4d5edfadcbf5c572359c8f7..dc766058a2e7f476dc0566f93f2f68bdee20ea21 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -56,6 +56,7 @@ --> <entry key="org.olat.user.ProfileFormController"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Users Profile-Form (Home->Settings)" /> <property name="propertyHandlers"> <list> <ref bean="userPropertyFirstName" /> @@ -486,6 +487,7 @@ <entry key="org.olat.course.assessment.EfficiencyStatementController"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Efficiency-Statements" /> <property name="propertyHandlers"> <list> <ref bean="userPropertyFirstName" /> @@ -549,6 +551,7 @@ <entry key="org.olat.admin.user.UserShortDescription"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Short description of a user" /> <property name="propertyHandlers"> <list> <ref bean="userPropertyFirstName" /> @@ -562,6 +565,7 @@ <entry key="org.olat.admin.user.imp.UserImportController"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Userimport wizard" /> <property name="propertyHandlers"> <list> <ref bean="userPropertyFirstName" /> @@ -629,8 +633,8 @@ <ref bean="userPropertyFirstName" /> <ref bean="userPropertyLastName" /> <ref bean="userPropertyEmail" /> - <ref bean="userPropertyBirthDay" /> - <ref bean="userPropertyGender" /> + <!-- <ref bean="userPropertyBirthDay" /> + <ref bean="userPropertyGender" /> --> <!-- Example IdentityAttributes matching to the usertracking LoggingObject @@ -704,6 +708,49 @@ </bean> </entry> + <!-- fxdiff: FXOLAT-227 user properties used for group-mail-function --> + <entry key="org.olat.group.ui.run.BusinessGroupSendToChooserForm"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyFirstName" /> + </list> + </property> + </bean> + </entry> + + + <!-- fxdiff: FXOLAT-93 user properties used in Businesscards (mitgleidersite) --> + <entry key="com.frentix.olat.user.UserBusinessCardController"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="BusinessCards in Membersite" /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyOrgUnit" /> + <ref bean="userPropertyTelOffice" /> + <ref bean="userPropertyEmail" /> + </list> + </property> + </bean> + </entry> + + <!-- fxdiff: FXOLAT-356 user properties used for the mail-footer line together with i18nkey: "footer.with.userdata" --> + <entry key="org.olat.core.util.mail.MailHelper"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Properties used for the mail footer for emails sent from this system. Configure i18nkey: 'footer.with.userdata' accordingly. " /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyInstitutionalName" /> + </list> + </property> + </bean> + </entry> + <!-- Default configuration in case nothing else matches. --> diff --git a/src/main/resources/serviceconfig/brasatoconfig.xml b/src/main/resources/serviceconfig/brasatoconfig.xml index 0b9e379eacf93673d0e97d80e124105ad7b094ab..03db5b8dc67e1dc9f2030ca7d30b3375b52c241f 100644 --- a/src/main/resources/serviceconfig/brasatoconfig.xml +++ b/src/main/resources/serviceconfig/brasatoconfig.xml @@ -23,7 +23,8 @@ <entry key="smtpPwd" value="${smtp.pwd}"/> <entry key="sslEnabled" value="${smtp.sslEnabled}"/> <entry key="sslCheckCertificate" value="${smtp.sslCheckCertificate}"/> - <entry key="mailFrom" value="${adminemail}"/> + <entry key="mailFrom" value="${fromemail}"/> + <entry key="mailReplyTo" value="${adminemail}"/> <entry key="mailSupport" value="${supportemail}"/> <entry key="mailAttachmentMaxSize" value="${mail.attachment.maxsize}"/> </map> @@ -33,7 +34,7 @@ <property name="applicationName" value="${application.name}" /> </bean> - <bean class="org.olat.core.helpers.Settings" depends-on="org.olat.core.util.WebappHelper" init-method="init" destroy-method="destroy"> + <bean id="org.olat.core.helpers.Settings" class="org.olat.core.helpers.Settings" depends-on="org.olat.core.util.WebappHelper" init-method="init" destroy-method="destroy"> <property name="persistedProperties"> <bean class="org.olat.core.configuration.PersistedProperties" scope="prototype" init-method="init" destroy-method="destroy"> <constructor-arg index="0" ref="coordinatorManager" /> @@ -97,6 +98,8 @@ NOTE: please use only a-z, 0-9 and _ as characters (those which are safe in the url encoding without converting) --> <property name="version" value="${build.version}" /> + <property name="buildIdentifier" value="${build.identifier}" /> + <property name="repoRevision" value="${build.repo.revision}" /> <property name="applicationName" value="${application.name}" /> <property name="clusterMode" value="${cluster.mode}"/> <property name="nodeId" value="${node.id}"/> diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 7eff19e9595b070d1e5851cf2c6ab36159c6e0f0..b88ad72003fe75abf305d4c5babd7f5e9cd34f78 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -37,6 +37,8 @@ folder.root=${userdata.dir}/bcroot folder.maxulmb.comment=limits on upload size and quotas should be set in MB folder.maxulmb=50 folder.quotamb=100 +folder.sendDocumentToExtern=true +folder.sendDocumentLinkOnly=true ######################################################################## # Application settings @@ -51,7 +53,6 @@ defaultlang=en #will be set by maven project.build.home.directory= -build.version= # The language that is used as a fallback in case the system does not find a key in @@ -153,7 +154,8 @@ login.enableGuestLoginLinks=true login.invitationLogin=true # Allow users to login using alternatively their email address or username login.using.username.or.email.enabled=true -# permit users to change their own passwords +# permit users to change their own passwords +# (if you set this to false, nobody can can change their pws!) password.change.allowed=true # default deletion behaviour is to retain details (marked as deleted) and # ensure they cannot be used, otherwise (if false) values will be replaced @@ -218,13 +220,13 @@ instance.id=myolat # theme for futher information. # you can also configure a theme via the admin GUI which takes precedence layout.theme = default -# test user generation -user.generateTestUsers=true +layout.coursetemplates.blacklist= +# test user generation +user.generateTestUsers=false olat.debug.comment=for developers: Set to true to enable visual debugging by the red/green bug icon on the upper left corner of olat olat.debug=true - # cache localization files (unless in development mode) localization.cache=true #number of elements to cache in course cache @@ -337,6 +339,7 @@ db.url.options.mysql=?useUnicode=true&characterEncoding=UTF-8 db.show_sql=false # validate, update, create, create-drop are valid entries. Default is update for use as developer setup with embedded hsqldb. # set to validate or disable with empty value to not validate against your database. +#db.hibernate.ddl.auto=update db.hibernate.ddl.auto= # configure the c3p0 pool with hibernate db.hibernate.c3p0.minsize=20 @@ -372,10 +375,15 @@ instantMessaging.sync.personal.groups=true #ATTENTION: On a server with many courses and groups this can generate thousand of groups and therefore #generete millions of presence messages. Check openfire reguarly if set to true! instantMessaging.sync.learning.groups=true +#FXOLAT-219 The delay +instantMessaging.sync.learning.groups.start.delay=15000 #whether to display current course participant count in the course toolbox course.display.participants.count=true +instantMessaging.awarenessVisible=false +instantMessaging.hideExternalClientInfo=false + ######################################################################## # Translation tool settings (translation infrastructure required!) ######################################################################## @@ -393,11 +401,13 @@ i18n.application.opt.src.dir = ${i18n.application.src.dir} ######################################################################## # Fulltext Search settings ######################################################################## -generate.index.at.startup=true +generate.index.at.startup=false restart.window.start=0 restart.window.end=24 # Enable search-service for only one node per cluster [ enabled | disabled ] search.service=enabled +#fxdiff FXOLAT-221: start indexer at different times for each instance +search.indexing.restart.interval=0 # Enable triggering indexer via cron-job instead at startup [ enabled | disabled ] # When enabled , configure 'generate.index.at.startup=false' search.indexing.cronjob=disabled @@ -479,6 +489,7 @@ hibernate.caching.singlevm.class=net.sf.ehcache.hibernate.EhCacheProvider hibernate.caching.cluster.class= hibernate.caching.use.query.cache=true hibernate.use.second.level.cache=true +hibernate.connection.timeout=120 ##### # LDAP configuration parameters (optional) @@ -556,11 +567,50 @@ ldap.attributename.useridentifyer=sAMAccountName ldap.attributename.email=mail ldap.attributename.firstName=givenName ldap.attributename.lastName=sn +# fxdiff: allow to config xml in here: +#mappings from ldap-attrib to olat-userproperty +ldap.attrib.gen.map.ldapkey1= +ldap.attrib.gen.map.olatkey1= +ldap.attrib.gen.map.ldapkey2= +ldap.attrib.gen.map.olatkey2= +ldap.attrib.gen.map.ldapkey3= +ldap.attrib.gen.map.olatkey3= +ldap.attrib.gen.map.ldapkey4= +ldap.attrib.gen.map.olatkey4= +ldap.attrib.gen.map.ldapkey5= +ldap.attrib.gen.map.olatkey5= +ldap.attrib.gen.map.ldapkey6= +ldap.attrib.gen.map.olatkey6= +ldap.attrib.gen.map.ldapkey7= +ldap.attrib.gen.map.olatkey7= +ldap.attrib.gen.map.ldapkey8= +ldap.attrib.gen.map.olatkey8= +ldap.attrib.gen.map.ldapkey9= +ldap.attrib.gen.map.olatkey9= +ldap.attrib.gen.map.ldapkey10= +ldap.attrib.gen.map.olatkey10= +#static mappings, olat-userproperties will be filled with given value +ldap.attrib.static.olatkey1= +ldap.attrib.static.value1= +ldap.attrib.static.olatkey2= +ldap.attrib.static.value2= +ldap.attrib.static.olatkey3= +ldap.attrib.static.value3= +#properties only to be synced on first sync +ldap.attrib.sync.once.olatkey1= +ldap.attrib.sync.once.olatkey2= +ldap.attrib.sync.once.olatkey3= ##### # Build properties ##### application.name=OLAT +build.version=8.0 +build.identifier=openolat80-dev +build.repo.revision=local-devel +#set those parameters to false, if this instance cannot be patched/updated automatically because of some needed manual steps or no connection to build-host. +fx.allow.auto.patch=true +fx.allow.auto.update=true ##### # OLAT logging diff --git a/src/main/resources/serviceconfig/org/olat/_spring/portalContext.xml b/src/main/resources/serviceconfig/org/olat/_spring/portalContext.xml index 5c0117c5ab1caf55375d90dc4feeed5b6f4b3ede..ba0d59106bd52941b2da3e0698b8df8910e18615 100644 --- a/src/main/resources/serviceconfig/org/olat/_spring/portalContext.xml +++ b/src/main/resources/serviceconfig/org/olat/_spring/portalContext.xml @@ -25,18 +25,18 @@ <property name="enabled" value="${portlet.system.events.enabled}" /> <property name="configuration"> <map> - <entry key="title_de" value="OLAT Systemausfälle" /> - <entry key="description_de" value="Informationen zu OLAT Systemausfällen" /> - <entry key="title_en" value="OLAT system breakdown" /> - <entry key="description_en" value="Information on OLAT system breakdown" /> - <entry key="title_fr" value="Défaillances du système OLAT" /> - <entry key="description_fr" value="Informations sur les défaillances du système OLAT" /> - <entry key="title_it" value="Disfunzioni del sistema OLAT" /> - <entry key="description_it" value="Informazioni sulle disfunzioni del sistema OLAT" /> - <entry key="title_es" value="Fallo del sistema OLAT" /> - <entry key="description_es" value="Información de fallo del sistema OLAT" /> - <entry key="title_gr" value="OLAT system breakdown" /> - <entry key="description_gr" value="Information on OLAT system breakdown" /> + <!-- fxdiff: better translations, not too spoofy for poor users --> + <entry key="portletName" value="Iframe" /> + <entry key="title_de" value="OLAT Systemmeldungen" /> + <entry key="description_de" value="Informationen zum OLAT Systemstatus" /> + <entry key="title_en" value="OLAT system information" /> + <entry key="description_en" value="Information on OLAT system status" /> + <entry key="title_fr" value="Informations au système OLAT" /> + <entry key="description_fr" value="Informations sur l'état du système OLAT" /> + <entry key="title_it" value="Messaggio di sistema OLAT" /> + <entry key="description_it" value="Messaggio di sistema OLAT" /> + <entry key="title_es" value="Información sobre el sistema" /> + <entry key="description_es" value="Información sobre el sistema" /> <!-- absolute or relative URI to a HTML page that is displayed in iframe --> <entry key="uri" value="${portlet.sysinfo.url}" /> <!-- optional parameter editFilePath: if configures it must contain an --> diff --git a/src/main/resources/serviceconfig/org/olat/core/commons/scheduler/_spring/olatextconfig.xml b/src/main/resources/serviceconfig/org/olat/core/commons/scheduler/_spring/olatextconfig.xml index be8f303fcfc35e1d2750ef1f0218a973c6eceef1..46dd324533a112c12350c049a1cea7155ea57ab4 100644 --- a/src/main/resources/serviceconfig/org/olat/core/commons/scheduler/_spring/olatextconfig.xml +++ b/src/main/resources/serviceconfig/org/olat/core/commons/scheduler/_spring/olatextconfig.xml @@ -44,6 +44,7 @@ How to add a new job: <ref bean="updateQtiResultsTrigger${assessmentplugin.activate}" /> <ref bean="invitationCleanupTrigger" /> <ref bean="epDeadlineTrigger" /> + <ref bean="instantMessagingSyncTrigger"/> </list> </property> </bean> @@ -110,8 +111,10 @@ requests and therefore the session in never invalidated --> <map> <!-- default is 5 minutes - values are milliseconds --> <entry key="idleWaitTime" value="300000" /> - <!-- default is 60 minutes - values are milliseconds --> - <entry key="initialAutoLogOutCutTime" value="3600000" /> + <!-- default is 60 minutes - values are milliseconds + fxdiff: we allow longer sessions. see also org.olat.core.util.UserSession + --> + <entry key="initialAutoLogOutCutTime" value="7200000" /> </map> </property> </bean> @@ -151,11 +154,16 @@ requests and therefore the session in never invalidated --> <bean id="searchIndexingTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="org.olat.search.job.${search.indexing.cronjob}" /> - <property name="cronExpression" value="${search.indexing.cronjob.expression}" /> + <property name="cronExpression" ref="searchIndexCronGenerator" /> <!-- OLAT-5093 start delay ensures there's no conflict with server startup and db not being ready yet --> <property name="startDelay" value="300000" /> </bean> + <bean id="searchIndexCronGenerator" class="org.olat.search.service.indexer.IndexCronGenerator"> + <property name="tomcatId" value="${tomcat.id}" /> + <property name="enabled" value="${search.indexing.cronjob}" /> + </bean> + <bean id="org.olat.search.job.enabled" class="org.springframework.scheduling.quartz.JobDetailBean" lazy-init="true"> <property name="jobClass" value="org.olat.search.service.indexer.SearchIndexingJob"/> </bean>