diff --git a/pom.xml b/pom.xml index 318a9654c8e2e75493a0a8727e851700d6d6b177..9345939716a4fe3530fae3f64137e228d8c88060 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,13 @@ <include>${basedir}/target/jquery/tinymce4/tinymce/plugins/quotespliter/plugin.min.js</include> </includes> </aggregation> + <aggregation> + <output>${basedir}/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.min.js</output> + <removeIncluded>false</removeIncluded> + <includes> + <include>${basedir}/target/jquery/jquery/openolat/jquery.statistics.chart.min.js</include> + </includes> + </aggregation> </aggregations> </configuration> </execution> diff --git a/src/main/java/de/bps/course/nodes/ChecklistCourseNode.java b/src/main/java/de/bps/course/nodes/ChecklistCourseNode.java index 5d31af9878ce1e9f6015257a642ee0928ac68fea..796e6532d41020b799eab5dc9897ca28eccf3206 100644 --- a/src/main/java/de/bps/course/nodes/ChecklistCourseNode.java +++ b/src/main/java/de/bps/course/nodes/ChecklistCourseNode.java @@ -160,7 +160,8 @@ public class ChecklistCourseNode extends AbstractAccessableCourseNode { } else // this is part of a copied course, the original checklist will be copied if(getModuleConfiguration().get(CONF_CHECKLIST) != null) { - Checklist orgChecklist = ChecklistManager.getInstance().loadChecklist((Checklist) getModuleConfiguration().get(ChecklistCourseNode.CONF_CHECKLIST)); + Checklist confChecklist = (Checklist)getModuleConfiguration().get(ChecklistCourseNode.CONF_CHECKLIST); + Checklist orgChecklist = ChecklistManager.getInstance().loadChecklist(confChecklist); checklist = ChecklistManager.getInstance().copyChecklist(orgChecklist); } else { // no checklist available, create new one diff --git a/src/main/java/de/bps/course/nodes/den/DENManageParticipantsController.java b/src/main/java/de/bps/course/nodes/den/DENManageParticipantsController.java index 8a5fd268c4b844322d5e1c4ba8986f86c321ee59..76022f399f3a17ed3b5114dabd8dfb43c23d47c6 100644 --- a/src/main/java/de/bps/course/nodes/den/DENManageParticipantsController.java +++ b/src/main/java/de/bps/course/nodes/den/DENManageParticipantsController.java @@ -315,7 +315,7 @@ public class DENManageParticipantsController extends BasicController { MailTemplate mailTempl = denManager.getAddedMailTemplate(ureq, subjectStr, getTranslator()); removeAsListenerAndDispose(addedNotificationCtr); - addedNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false); + addedNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false, true); listenTo(addedNotificationCtr); VelocityContainer sendNotificationVC = createVelocityContainer("sendnotification"); @@ -331,7 +331,7 @@ public class DENManageParticipantsController extends BasicController { private void createRemovedNotificationMail(UserRequest ureq, String subjectStr) { MailTemplate mailTempl = denManager.getRemovedMailTemplate(ureq, subjectStr, getTranslator()); removeAsListenerAndDispose(addedNotificationCtr); - addedNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false); + addedNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false, true); listenTo(addedNotificationCtr); VelocityContainer sendNotificationVC = createVelocityContainer("sendnotification"); diff --git a/src/main/java/de/bps/olat/modules/cl/ChecklistManageCheckpointsController.java b/src/main/java/de/bps/olat/modules/cl/ChecklistManageCheckpointsController.java index 034c2e39dc50dfe4c414a8614485768e73fe61c3..6bcb09491551d09e4fa01243633ab11794834bba 100644 --- a/src/main/java/de/bps/olat/modules/cl/ChecklistManageCheckpointsController.java +++ b/src/main/java/de/bps/olat/modules/cl/ChecklistManageCheckpointsController.java @@ -250,7 +250,9 @@ public class ChecklistManageCheckpointsController extends BasicController { } int j = 500; - for( Checkpoint checkpoint : checklist.getCheckpoints() ) { + + List<Checkpoint> checkpointList = checklist.getCheckpointsSorted(ChecklistUIFactory.comparatorTitleAsc); + for( Checkpoint checkpoint : checkpointList ) { String pointTitle = checkpoint.getTitle() == null ? "" : checkpoint.getTitle(); manageChecklistTable.addColumnDescriptor(new ChecklistMultiSelectColumnDescriptor(pointTitle, j++)); cols++; @@ -259,14 +261,15 @@ public class ChecklistManageCheckpointsController extends BasicController { cols++; manageChecklistTable.setMultiSelect(false); - manageTableData = new ChecklistManageTableDataModel(checklist, lstIdents, userPropertyHandlers, cols); + manageTableData = new ChecklistManageTableDataModel(checkpointList, lstIdents, userPropertyHandlers, cols); manageChecklistTable.setTableDataModel(manageTableData); panel.setContent(manageChecklistTable.getInitialComponent()); } private void initEditTable(UserRequest ureq, Identity identity) { - editTableData = new ChecklistRunTableDataModel(this.checklist.getCheckpoints(), getTranslator()); + List<Checkpoint> checkpoints = checklist.getCheckpoints(); + editTableData = new ChecklistRunTableDataModel(checkpoints, getTranslator()); TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setTableEmptyMessage(translate("cl.table.empty")); @@ -286,8 +289,8 @@ public class ChecklistManageCheckpointsController extends BasicController { editChecklistTable.addMultiSelectAction("cl.save.close", "save"); editChecklistTable.setTableDataModel(editTableData); - for(int i = 0; i < this.checklist.getCheckpoints().size(); i++) { - Checkpoint checkpoint = (Checkpoint) editTableData.getObject(i); + for(int i = 0; i<checkpoints.size(); i++) { + Checkpoint checkpoint = editTableData.getObject(i); boolean selected = checkpoint.getSelectionFor(identity).booleanValue(); editChecklistTable.setMultiSelectSelectedAt(i, selected); } @@ -301,7 +304,7 @@ public class ChecklistManageCheckpointsController extends BasicController { ChecklistManager manager = ChecklistManager.getInstance(); int size = checklist.getCheckpoints().size(); for(int i = 0; i < size; i++) { - Checkpoint checkpoint = this.checklist.getCheckpoints().get(i); + Checkpoint checkpoint = checklist.getCheckpoints().get(i); Boolean selected = checkpoint.getSelectionFor(identity); if(selected.booleanValue() != selection.get(i)) { checkpoint.setSelectionFor(identity, selection.get(i)); @@ -314,8 +317,10 @@ public class ChecklistManageCheckpointsController extends BasicController { int cdcnt = manageTableData.getColumnCount(); int rcnt = manageTableData.getRowCount(); StringBuilder sb = new StringBuilder(); + boolean isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); + List<UserPropertyHandler> userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser); // additional informations - sb.append(translate("cl.course.title")).append('\t').append(this.course.getCourseTitle()); + sb.append(translate("cl.course.title")).append('\t').append(course.getCourseTitle()); sb.append('\n'); String listTitle = checklist.getTitle() == null ? "" : checklist.getTitle(); sb.append(translate("cl.title")).append('\t').append(listTitle); @@ -329,7 +334,14 @@ public class ChecklistManageCheckpointsController extends BasicController { } sb.append('\n'); // checkpoint description - sb.append('\t'); + if(isAdministrativeUser) { + sb.append('\t'); + } + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler == null) continue; + sb.append('\t'); + } + for (Checkpoint checkpoint : checklist.getCheckpoints()) { sb.append('\t').append(checkpoint.getDescription()); } diff --git a/src/main/java/de/bps/olat/modules/cl/ChecklistManageTableDataModel.java b/src/main/java/de/bps/olat/modules/cl/ChecklistManageTableDataModel.java index 09b51054fb2a08c55d5d49deacd180642704aa45..7ea3c1db7d6cee965a76d542f73592675274076b 100644 --- a/src/main/java/de/bps/olat/modules/cl/ChecklistManageTableDataModel.java +++ b/src/main/java/de/bps/olat/modules/cl/ChecklistManageTableDataModel.java @@ -35,7 +35,7 @@ public class ChecklistManageTableDataModel extends DefaultTableDataModel<Row> { private int colCount; private int rowCount; - public ChecklistManageTableDataModel(Checklist checklist, List<Identity> participants, + public ChecklistManageTableDataModel(List<Checkpoint> checkpointList, List<Identity> participants, List<UserPropertyHandler> userPropertyHandlers, int cols) { super(Collections.<Row>emptyList()); @@ -44,7 +44,7 @@ public class ChecklistManageTableDataModel extends DefaultTableDataModel<Row> { List<Row> entries = new ArrayList<>(rowCount); for( Identity identity : participants ) { - entries.add(new Row(identity, userPropertyHandlers, checklist, Locale.ENGLISH)); + entries.add(new Row(identity, userPropertyHandlers, checkpointList, Locale.ENGLISH)); } setObjects(entries); } @@ -86,7 +86,7 @@ public class ChecklistManageTableDataModel extends DefaultTableDataModel<Row> { private final String[] identityProps; private final Boolean[] checkpoints; - public Row(Identity identity, List<UserPropertyHandler> userPropertyHandlers, Checklist checklist, Locale locale) { + public Row(Identity identity, List<UserPropertyHandler> userPropertyHandlers, List<Checkpoint> checkpointList, Locale locale) { this.identityKey = identity.getKey(); this.identityName = identity.getName(); @@ -95,7 +95,6 @@ public class ChecklistManageTableDataModel extends DefaultTableDataModel<Row> { identityProps[i] = userPropertyHandlers.get(i).getUserProperty(identity.getUser(), locale); } - List<Checkpoint> checkpointList = checklist.getCheckpointsSorted(ChecklistUIFactory.comparatorTitleAsc); checkpoints = new Boolean[checkpointList.size()]; for( int i=checkpointList.size(); i-->0; ) { checkpoints[i] = checkpointList.get(i).getSelectionFor(identity); diff --git a/src/main/java/de/bps/olat/modules/cl/ChecklistManager.java b/src/main/java/de/bps/olat/modules/cl/ChecklistManager.java index c0441d85f05f711fcd6e015a991b0accfe9af2f9..457bde664829c848f1478843ded1069f8230a431 100644 --- a/src/main/java/de/bps/olat/modules/cl/ChecklistManager.java +++ b/src/main/java/de/bps/olat/modules/cl/ChecklistManager.java @@ -95,7 +95,12 @@ public class ChecklistManager { */ public Checklist saveChecklist(Checklist cl) { cl.setLastModified(new Date()); - return DBFactory.getInstance().getCurrentEntityManager().merge(cl); + if(cl.getKey() == null) { + DBFactory.getInstance().getCurrentEntityManager().persist(cl); + } else { + cl = DBFactory.getInstance().getCurrentEntityManager().merge(cl); + } + return cl; } /** 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 86f2d8bf895d52ea4a24880a984ba1e9cd86092e..f03cf5aed5d00ec7b01e588e0e5978803b98b0bc 100644 --- a/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java +++ b/src/main/java/org/olat/admin/securitygroup/gui/GroupController.java @@ -325,7 +325,7 @@ public class GroupController extends BasicController { doBuildConfirmDeleteDialog(ureq); } else { removeAsListenerAndDispose(removeUserMailCtr); - removeUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, removeUserMailDefaultTempl, true, false); + removeUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, removeUserMailDefaultTempl, true, false, true); listenTo(removeUserMailCtr); removeAsListenerAndDispose(cmc); @@ -406,7 +406,7 @@ public class GroupController extends BasicController { doAddIdentitiesToGroup(ureq, toAdd, null); } else { removeAsListenerAndDispose(addUserMailCtr); - addUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, addUserMailDefaultTempl, true, mandatoryEmail); + addUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, addUserMailDefaultTempl, true, mandatoryEmail, true); listenTo(addUserMailCtr); removeAsListenerAndDispose(cmc); diff --git a/src/main/java/org/olat/admin/securitygroup/gui/WaitingGroupController.java b/src/main/java/org/olat/admin/securitygroup/gui/WaitingGroupController.java index 1ce5817cce028772e7e4638d7853ec02c744f7e7..5bf177560c208781deb21d32856fd0517d0b64a3 100644 --- a/src/main/java/org/olat/admin/securitygroup/gui/WaitingGroupController.java +++ b/src/main/java/org/olat/admin/securitygroup/gui/WaitingGroupController.java @@ -96,7 +96,7 @@ public class WaitingGroupController extends GroupController { toTransfer = objects; removeAsListenerAndDispose(transferMailCtr); - transferMailCtr = new MailNotificationEditController(getWindowControl(), ureq, transferUserMailTempl, true, mandatoryEmail); + transferMailCtr = new MailNotificationEditController(getWindowControl(), ureq, transferUserMailTempl, true, mandatoryEmail, true); listenTo(transferMailCtr); removeAsListenerAndDispose(cmc); diff --git a/src/main/java/org/olat/admin/user/delete/SelectionController.java b/src/main/java/org/olat/admin/user/delete/SelectionController.java index 68b986dd4de036a09bb77bb955b0d447c713e83f..0207ee6da5520188daed4c71dd5ba6b4ffb4884a 100644 --- a/src/main/java/org/olat/admin/user/delete/SelectionController.java +++ b/src/main/java/org/olat/admin/user/delete/SelectionController.java @@ -208,7 +208,7 @@ public class SelectionController extends BasicController { deleteMailTemplate.addToContext("durationdeleteemail", Integer.toString(UserDeletionManager.getInstance().getDeleteEmailDuration() )); removeAsListenerAndDispose(deleteUserMailCtr); - deleteUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, deleteMailTemplate, true, false); + deleteUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, deleteMailTemplate, true, false, false); listenTo(deleteUserMailCtr); removeAsListenerAndDispose(cmc); diff --git a/src/main/java/org/olat/admin/user/delete/TabbedPaneController.java b/src/main/java/org/olat/admin/user/delete/TabbedPaneController.java index 3f4866031f7d6b343a0a2c218bff229c175d132b..15e3644fb4a8728c4dc31127af7cee83aaf752fb 100644 --- a/src/main/java/org/olat/admin/user/delete/TabbedPaneController.java +++ b/src/main/java/org/olat/admin/user/delete/TabbedPaneController.java @@ -40,7 +40,7 @@ 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.generic.dtabs.Activateable2; -import org.olat.core.gui.translator.PackageTranslator; +import org.olat.core.gui.translator.Translator; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.util.Util; @@ -60,7 +60,7 @@ public class TabbedPaneController extends DefaultController implements Controlle private VelocityContainer myContent; - private PackageTranslator translator; + private Translator translator; // controllers used in tabbed pane private TabbedPane userDeleteTabP; @@ -78,7 +78,7 @@ public class TabbedPaneController extends DefaultController implements Controlle public TabbedPaneController(UserRequest ureq, WindowControl wControl) { super(wControl); - translator = new PackageTranslator(PACKAGE, ureq.getLocale()); + translator = Util.createPackageTranslator(TabbedPaneController.class, ureq.getLocale()); Boolean canDelete = BaseSecurityModule.USERMANAGER_CAN_DELETE_USER; if ( canDelete.booleanValue() || ureq.getUserSession().getRoles().isOLATAdmin() ) { diff --git a/src/main/java/org/olat/basesecurity/ui/GroupController.java b/src/main/java/org/olat/basesecurity/ui/GroupController.java index fd50342809d873a53694b79d3dd96f36ce82c847..f4cdea58751e1309087c32b3d5a697d657106435 100644 --- a/src/main/java/org/olat/basesecurity/ui/GroupController.java +++ b/src/main/java/org/olat/basesecurity/ui/GroupController.java @@ -334,7 +334,7 @@ public class GroupController extends BasicController { doBuildConfirmDeleteDialog(ureq); } else { removeAsListenerAndDispose(removeUserMailCtr); - removeUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, removeUserMailDefaultTempl, true, false); + removeUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, removeUserMailDefaultTempl, true, false, true); listenTo(removeUserMailCtr); removeAsListenerAndDispose(cmc); @@ -415,7 +415,7 @@ public class GroupController extends BasicController { doAddIdentitiesToGroup(ureq, toAdd, null); } else { removeAsListenerAndDispose(addUserMailCtr); - addUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, addUserMailDefaultTempl, true, mandatoryEmail); + addUserMailCtr = new MailNotificationEditController(getWindowControl(), ureq, addUserMailDefaultTempl, true, mandatoryEmail, true); listenTo(addUserMailCtr); removeAsListenerAndDispose(cmc); 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 0b2393cc5ee744bce0144eecc725b1dfdf5f6c19..5aa1f4b4f5d6c71db7930be6ac98e319cd1667c7 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 @@ -270,6 +270,15 @@ public class FolderRunController extends BasicController implements Activateable putInitialPanel(folderContainer); } + /** + * Remove the subscription panel but let the subscription context active + */ + public void disableSubscriptionController() { + if(csController != null) { + folderContainer.remove(csController.getInitialComponent()); + } + } + public void setResourceURL(String resourceUrl) { if(searchC != null) { searchC.setResourceUrl(resourceUrl); diff --git a/src/main/java/org/olat/core/gui/components/chart/BarChartComponent.java b/src/main/java/org/olat/core/gui/components/chart/BarChartComponent.java index 7a4d779b70365d7559a2bd0d310acc788135b5aa..69ddcf7bbbf718794ad5f2ab301793cd503dc8bb 100644 --- a/src/main/java/org/olat/core/gui/components/chart/BarChartComponent.java +++ b/src/main/java/org/olat/core/gui/components/chart/BarChartComponent.java @@ -23,22 +23,21 @@ import java.util.ArrayList; import java.util.List; import org.olat.core.gui.components.ComponentRenderer; -import org.olat.core.logging.AssertException; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class BarChartComponent extends AbstractD3Component { +public class BarChartComponent extends DefaultD3Component { private static final ComponentRenderer renderer = new BarChartComponentRenderer(); - private Scale yScale = Scale.plain; private List<BarSeries> seriesList = new ArrayList<>(); private String defaultBarClass = "bar_default"; private String yLegend; + private String xLegend; public BarChartComponent(String name) { super(name); @@ -60,16 +59,12 @@ public class BarChartComponent extends AbstractD3Component { this.yLegend = yLegend; } - public Scale getYScale() { - return yScale; + public String getXLegend() { + return xLegend; } - public void setYScale(Scale yScale) { - if(yScale == Scale.plain || yScale == Scale.percent) { - this.yScale = yScale; - } else { - throw new AssertException("Scale not supported by bar chart: " + yScale); - } + public void setXLegend(String xLegend) { + this.xLegend = xLegend; } public List<BarSeries> getSeries() { diff --git a/src/main/java/org/olat/core/gui/components/chart/BarChartComponentRenderer.java b/src/main/java/org/olat/core/gui/components/chart/BarChartComponentRenderer.java index ddc2472949ad98b052664ba79234e665c85d17a4..02603b009f49bfc773937bc5fdd6d300690a6a19 100644 --- a/src/main/java/org/olat/core/gui/components/chart/BarChartComponentRenderer.java +++ b/src/main/java/org/olat/core/gui/components/chart/BarChartComponentRenderer.java @@ -29,6 +29,7 @@ import org.olat.core.gui.render.Renderer; 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.util.StringHelper; /** * @@ -41,14 +42,10 @@ public class BarChartComponentRenderer extends DefaultComponentRenderer { Translator translator, RenderResult renderResult, String[] args) { BarChartComponent chartCmp = (BarChartComponent)source; - renderD3js(renderer, sb, chartCmp); - } - - private void renderD3js(Renderer renderer, StringOutput sb, BarChartComponent chartCmp) { List<BarSeries> seriesList = chartCmp.getSeries(); - Scale yScale = chartCmp.getYScale(); String yLegend = chartCmp.getYLegend(); + String xLegend = chartCmp.getXLegend(); Stringuified infos = BarSeries.getDatasAndColors(seriesList, chartCmp.getDefaultBarClass()); @@ -79,9 +76,7 @@ public class BarChartComponentRenderer extends DefaultComponentRenderer { .append("var yAxis = d3.svg.axis()\n") .append(" .scale(y)\n") .append(" .orient('left')\n"); - if(yScale == Scale.percent) { - sb.append(" .ticks(10, '%');\n"); - } + sb.append("\n") .append("var svg = d3.select('#d").append(cmpId).append("d3holder').append('svg')\n") .append(" .attr('width', width + margin.left + margin.right)\n") @@ -92,32 +87,45 @@ public class BarChartComponentRenderer extends DefaultComponentRenderer { .append("var data = [").append(infos.getData()).append("]\n") .append("x.domain(data.map(function(d) { return d[0]; }));\n") .append("y.domain([0, d3.max(data, function(d) { return ").append(sum).append("; })]);\n") - .append("\n") - - .append("svg.append('g')\n") + .append("\n"); + + //append x axis and legend + sb.append("svg.append('g')\n") .append(" .attr('class', 'x axis')\n") .append(" .attr('transform', 'translate(0,' + height + ')')\n") - .append(" .call(xAxis);\n") - .append("\n") - //append y legend - .append("svg.append('g')\n") + .append(" .call(xAxis);\n"); + if(StringHelper.containsNonWhitespace(xLegend)) { + sb.append(" .append('text')\n") + .append(" .attr('y', 0)\n") + .append(" .attr('x', 0 - (width / 2))\n") + .append(" .attr('dy', '1em')\n") + .append(" .style('text-anchor', 'middle')\n") + .append(" .text('").append(xLegend).append("');\n"); + } + + //append y axis and legend + sb.append("svg.append('g')\n") .append(" .attr('class', 'y axis')\n") - .append(" .call(yAxis)\n") - .append(" .append('text')\n") - .append(" .attr('transform', 'rotate(-90)')\n") - .append(" .attr('y', 0 - margin.left)\n") - .append(" .attr('x', 0 - (height / 2))\n") - .append(" .attr('dy', '1em')\n") - .append(" .style('text-anchor', 'middle')\n") - .append(" .text('").append(yLegend).append("');\n") - - .append("\n"); + .append(" .call(yAxis)\n"); + if(StringHelper.containsNonWhitespace(yLegend)) { + sb.append(" .append('text')\n") + .append(" .attr('transform', 'rotate(-90)')\n") + .append(" .attr('y', 0 - margin.left)\n") + .append(" .attr('x', 0 - (height / 2))\n") + .append(" .attr('dy', '1em')\n") + .append(" .style('text-anchor', 'middle')\n") + .append(" .text('").append(yLegend).append("');\n") + .append("\n"); + } appendSeries(sb, infos.getColors(), chartCmp); sb.append("});\n") .append("/* ]]> */") .append("</script>\n"); + + //System.out.println("--------------------------"); + //System.out.println(sb.toString()); } private void appendSeries(StringOutput sb, StringBuilder colors, BarChartComponent chartCmp) { diff --git a/src/main/java/org/olat/core/gui/components/chart/BarSeries.java b/src/main/java/org/olat/core/gui/components/chart/BarSeries.java index a17c2f02bec946f7f6fe4d7bc77afc684c25092f..665184737c7fd74e53f0d12c5e0e231d2eeb1276 100644 --- a/src/main/java/org/olat/core/gui/components/chart/BarSeries.java +++ b/src/main/java/org/olat/core/gui/components/chart/BarSeries.java @@ -70,8 +70,26 @@ public class BarSeries { return Double.NaN; } + public static final String datasToString(double[] values) { + StringBuilder sb = new StringBuilder(); + for(double value:values) { + if(sb.length() > 0) sb.append(","); + sb.append(value); + } + return sb.toString(); + } + + + public static final String datasToString(long[] values) { + StringBuilder sb = new StringBuilder(); + for(long value:values) { + if(sb.length() > 0) sb.append(","); + sb.append((double)value); + } + return sb.toString(); + } - protected static Stringuified getDatasAndColors(List<BarSeries> seriesList, String defaultBarClass) { + public static Stringuified getDatasAndColors(List<BarSeries> seriesList, String defaultBarClass) { Map<Comparable<?>,String> thickSet = new HashMap<>(); Map<Comparable<?>,String> colorsMap = new HashMap<>(); for(BarSeries series:seriesList) { @@ -92,6 +110,7 @@ public class BarSeries { List<Comparable<?>> thickList = new ArrayList<>(thickSet.keySet()); Collections.sort(thickList); + Collections.reverse(thickList); StringBuilder data = new StringBuilder(); for(int i=0; i<thickList.size(); i++) { @@ -120,7 +139,7 @@ public class BarSeries { return new Stringuified(data, colors); } - protected static class Stringuified { + public static class Stringuified { private final StringBuilder colors; private final StringBuilder data; diff --git a/src/main/java/org/olat/core/gui/components/chart/AbstractD3Component.java b/src/main/java/org/olat/core/gui/components/chart/DefaultD3Component.java similarity index 87% rename from src/main/java/org/olat/core/gui/components/chart/AbstractD3Component.java rename to src/main/java/org/olat/core/gui/components/chart/DefaultD3Component.java index 3e1e6c184d1951d1758bd23bed985edc718ec6d0..5a6fe7e5b4bac09b81b2879d8df09b1d4be4c093 100644 --- a/src/main/java/org/olat/core/gui/components/chart/AbstractD3Component.java +++ b/src/main/java/org/olat/core/gui/components/chart/DefaultD3Component.java @@ -22,6 +22,7 @@ package org.olat.core.gui.components.chart; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.AbstractComponent; import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.components.DefaultComponentRenderer; import org.olat.core.gui.render.ValidationResult; /** @@ -31,9 +32,11 @@ import org.olat.core.gui.render.ValidationResult; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public abstract class AbstractD3Component extends AbstractComponent { +public class DefaultD3Component extends AbstractComponent { - public AbstractD3Component(String name) { + private static final ComponentRenderer EMPTY_RENDERER = new DefaultComponentRenderer(); + + public DefaultD3Component(String name) { super(name); } @@ -43,7 +46,9 @@ public abstract class AbstractD3Component extends AbstractComponent { } @Override - public abstract ComponentRenderer getHTMLRendererSingleton(); + public ComponentRenderer getHTMLRendererSingleton() { + return EMPTY_RENDERER; + } @Override public void validate(UserRequest ureq, ValidationResult vr) { @@ -78,5 +83,4 @@ public abstract class AbstractD3Component extends AbstractComponent { } return false; } - } diff --git a/src/main/java/org/olat/core/gui/components/chart/HistogramComponent.java b/src/main/java/org/olat/core/gui/components/chart/HistogramComponent.java deleted file mode 100644 index 3689373eafaf915dcf3d2fcbb5833d6324c2f6ee..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/core/gui/components/chart/HistogramComponent.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.core.gui.components.chart; - -import org.olat.core.gui.components.ComponentRenderer; - -/** - * - * Make an histogram from a list of values (doubles or longs but not booth) - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class HistogramComponent extends AbstractD3Component { - - private static final HistogramComponentRenderer RENDERER = new HistogramComponentRenderer(); - - private double[] doubleValues; - private long[] longValues; - - private double maxValue; - private String defaultBarClass = "bar_default"; - - private Scale xScale; - private String yLegend; - - private double cutValue; - private String lowBarClass; - private String highBarClass; - - public HistogramComponent(String name) { - super(name); - } - - public String getDefaultBarClass() { - return defaultBarClass; - } - - public void setDefaultBarClass(String defaultBarClass) { - this.defaultBarClass = defaultBarClass; - } - - public double getCutValue() { - return cutValue; - } - - public String getLowBarClass() { - return lowBarClass; - } - - public String getHighBarClass() { - return highBarClass; - } - - /** - * Set a cut value for the x axis. Value lower than the cut value - * will get the lowBarClass, and bigger the highBarClass. - * - * @param lowBarClass - * @param cutValue - * @param highBarClass - */ - public void setCutValue(String lowBarClass, double cutValue, String highBarClass) { - this.cutValue = cutValue; - this.lowBarClass = lowBarClass; - this.highBarClass = highBarClass; - } - - public Scale getXScale() { - return xScale; - } - - public void setXScale(Scale xScale) { - this.xScale = xScale; - } - - public String getYLegend() { - return yLegend; - } - - public void setYLegend(String yLegend) { - this.yLegend = yLegend; - } - - public double getMaxValue() { - return maxValue; - } - - public void setMaxValue(double maxValue) { - this.maxValue = maxValue; - } - - public double[] getDoubleValues() { - return doubleValues; - } - - public void setDoubleValues(double[] doubleValues) { - this.doubleValues = doubleValues; - } - - public long[] getLongValues() { - return longValues; - } - - public void setLongValues(long[] longValues) { - this.longValues = longValues; - } - - @Override - public ComponentRenderer getHTMLRendererSingleton() { - return RENDERER; - } -} diff --git a/src/main/java/org/olat/core/gui/components/chart/HistogramComponentRenderer.java b/src/main/java/org/olat/core/gui/components/chart/HistogramComponentRenderer.java deleted file mode 100644 index b9fbf4c248d0cd645f83a327cbdfac2416f86801..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/core/gui/components/chart/HistogramComponentRenderer.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.core.gui.components.chart; - -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.DefaultComponentRenderer; -import org.olat.core.gui.render.RenderResult; -import org.olat.core.gui.render.Renderer; -import org.olat.core.gui.render.StringOutput; -import org.olat.core.gui.render.URLBuilder; -import org.olat.core.gui.translator.Translator; - -/** - * - * Renderer a list of lons or doubles as an histogramm with d3js - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class HistogramComponentRenderer extends DefaultComponentRenderer { - - @Override - public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, - Translator translator, RenderResult renderResult, String[] args) { - - HistogramComponent chartCmp = (HistogramComponent)source; - renderD3js(renderer, sb, chartCmp); - } - - private StringBuilder getValues(HistogramComponent chartCmp) { - StringBuilder sb = new StringBuilder(); - if(chartCmp.getDoubleValues() != null) { - for(double value:chartCmp.getDoubleValues()) { - if(sb.length() > 0) sb.append(","); - sb.append(value); - } - } else if(chartCmp.getLongValues() != null) { - for(long value:chartCmp.getLongValues()) { - if(sb.length() > 0) sb.append(","); - sb.append((double)value); - } - } - return sb; - } - - private double getMaxValue(HistogramComponent chartCmp) { - double maxValue = 0.0; - if(chartCmp.getMaxValue() > 0) { - maxValue = chartCmp.getMaxValue(); - } else if(chartCmp.getDoubleValues() != null) { - for(double value:chartCmp.getDoubleValues()) { - maxValue = Math.max(maxValue, value); - } - } else if(chartCmp.getLongValues() != null) { - for(long value:chartCmp.getLongValues()) { - maxValue = Math.max(maxValue, value); - } - } - - double ceiledRoundedMaxValue = Math.ceil(maxValue); - return ceiledRoundedMaxValue; - } - - private StringOutput getFillFunction(HistogramComponent chartCmp) { - StringOutput sb = new StringOutput(); - if(chartCmp.getCutValue() > 0.0001) { - String cutValue = Double.toString(chartCmp.getCutValue()); - sb.append("function(d, i) { if(data[i].x < ").append(cutValue).append(") ") - .append(" return 'bar ").append(chartCmp.getLowBarClass()).append("';") - .append(" return 'bar ").append(chartCmp.getHighBarClass()).append("';}"); - } else { - sb.append("'bar ").append(chartCmp.getDefaultBarClass()).append("'"); - } - return sb; - } - - private void renderD3js(Renderer renderer, StringOutput sb, HistogramComponent chartCmp) { - - String cmpId = chartCmp.getDispatchID(); - StringBuilder values = getValues(chartCmp); - double maxValue = getMaxValue(chartCmp); - String yLegend = chartCmp.getYLegend(); - - if(maxValue <= 0.1) { - sb.append("No data"); - return;//no values - } - - sb.append("<div id='d").append(cmpId).append("d3holder' class='d3chart' style='width:600px;height:300px'></div>\n") - .append("<script type='text/javascript'>\n") - .append("/* <![CDATA[ */ ") - .append("jQuery(function () {\n") - .append("var placeholderheight = jQuery('#d").append(cmpId).append("d3holder').height();\n") - .append("var placeholderwidth = jQuery('#d").append(cmpId).append("d3holder').width();\n"); - - - sb.append("var values =[").append(values).append("];\n") - // Formatters for counts and times (converting numbers to Dates). - .append("var formatCount = d3.format(',.f'),\n") - .append(" formatTime = d3.time.format('%H:%M'),\n") - .append(" formatMinutes = function(d) { return formatTime(new Date(2012, 0, 1, 0, d)); };\n") - - .append("var margin = {top: 10, right: 40, bottom: 30, left: 70},\n") - .append(" width = placeholderwidth - margin.left - margin.right,\n") - .append(" height = placeholderheight - margin.top - margin.bottom;\n") - - .append("var x = d3.scale.linear()\n") - .append(" .domain([0, ").append(Double.toString(maxValue)).append("])\n") - .append(" .range([0, width]);\n") - - //generate a histogram using twenty uniformly-spaced bins. - .append("var data = d3.layout.histogram()\n") - .append(" .bins(x.ticks(20))\n") - .append(" (values);\n") - - .append("var sum = d3.sum(data, function(d) { return d.y; });\n") - - .append("var y = d3.scale.linear()\n") - .append(" .domain([0, d3.max(data, function(d) { return d.y; })])\n") - .append(" .range([height, 0]);\n") - - .append("var y2 = d3.scale.linear()\n") - .append(" .domain([0, d3.max(data, function(d) { return d.y / sum; })])\n") - .append(" .range([height, 0]);\n") - - .append("var xAxis = d3.svg.axis()\n") - .append(" .scale(x)\n") - .append(" .orient('bottom')\n"); - if(chartCmp.getXScale() == Scale.hour) { - sb.append(" .tickFormat(formatMinutes);\n"); - } else { - sb.append(" .tickFormat(d3.format('.01f'));\n"); - } - sb.append("var yAxis = d3.svg.axis()\n") - .append(" .scale(y)\n") - .append(" .orient('right')\n") - .append(" .ticks(10);\n") - - .append("var y2Axis = d3.svg.axis()\n") - .append(" .scale(y2)\n") - .append(" .orient('left')\n") - .append(" .ticks(10, '%');\n") - - .append("var svg = d3.select('#d").append(cmpId).append("d3holder').append('svg')\n") - .append(" .attr('width', width + margin.left + margin.right)\n") - .append(" .attr('height', height + margin.top + margin.bottom)\n") - .append(" .append('g')\n") - .append(" .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');\n") - - .append("var bar = svg.selectAll('.bar')\n") - .append(" .data(data)\n") - .append(" .enter().append('g')\n") - .append(" .attr('class', ").append(getFillFunction(chartCmp)).append(")\n") - - .append(" .attr('transform', function(d) { return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; })\n") - .append(" .append('rect')\n") - .append(" .attr('x', 1)\n") - .append(" .attr('width', x(data[0].dx) - 1)\n") - .append(" .attr('height', function(d) { return height - y(d.y); });\n") - - //x axis - .append("svg.append('g')\n") - .append(" .attr('class', 'x axis')\n") - .append(" .attr('transform', 'translate(0,' + height + ')')\n") - .append(" .call(xAxis);\n") - - //right y axis - .append("svg.append('g')\n") - .append(" .attr('class', 'y axis')\n") - .append(" .attr('transform', 'translate(' + width + ',0)')\n") - .append(" .call(yAxis)\n") - - //left y axis with legend - .append("svg.append('g')\n") - .append(" .attr('class', 'y axis')\n") - .append(" .call(y2Axis)\n") - .append(" .append('text')\n") - .append(" .attr('transform', 'rotate(-90)')\n") - .append(" .attr('y', 0 - margin.left)\n") - .append(" .attr('x', 0 - (height / 2))\n") - .append(" .attr('dy', '1em')\n") - .append(" .style('text-anchor', 'middle')\n") - .append(" .text('").append(yLegend).append("');\n"); - - sb.append("});\n") - .append("/* ]]> */") - .append("</script>\n"); - } -} diff --git a/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponent.java b/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponent.java deleted file mode 100644 index a83dcff1e736203e674e2545a35470aae9fca5c0..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponent.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.core.gui.components.chart; - -import java.util.ArrayList; -import java.util.List; - -import org.olat.core.gui.components.ComponentRenderer; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class HorizontalBarChartComponent extends AbstractD3Component { - - private static final HorizontalBarChartComponentRenderer RENDERER = new HorizontalBarChartComponentRenderer(); - - private Scale xScale = Scale.plain; - private List<BarSeries> seriesList = new ArrayList<>(); - - private String defaultBarClass = "bar_default"; - private String xLegend; - - public HorizontalBarChartComponent(String name) { - super(name); - } - - public Scale getXScale() { - return xScale; - } - - public void setXScale(Scale xScale) { - this.xScale = xScale; - } - - public String getXLegend() { - return xLegend; - } - - public void setXLegend(String xLegend) { - this.xLegend = xLegend; - } - - public List<BarSeries> getSeries() { - return seriesList; - } - - public void addSeries(BarSeries... series) { - if(series != null && series.length > 0 && series[0] != null) { - for(BarSeries s:series) { - seriesList.add(s); - } - } - } - - public String getDefaultBarClass() { - return defaultBarClass; - } - - public void setDefaultBarClass(String defaultBarClass) { - this.defaultBarClass = defaultBarClass; - } - - @Override - public ComponentRenderer getHTMLRendererSingleton() { - return RENDERER; - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponentRenderer.java b/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponentRenderer.java deleted file mode 100644 index 54c81edb19b71dea5c7c687bb94b4c3a4f9b2615..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/core/gui/components/chart/HorizontalBarChartComponentRenderer.java +++ /dev/null @@ -1,165 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.core.gui.components.chart; - -import java.util.List; - -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.DefaultComponentRenderer; -import org.olat.core.gui.components.chart.BarSeries.Stringuified; -import org.olat.core.gui.render.RenderResult; -import org.olat.core.gui.render.Renderer; -import org.olat.core.gui.render.StringOutput; -import org.olat.core.gui.render.URLBuilder; -import org.olat.core.gui.translator.Translator; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class HorizontalBarChartComponentRenderer extends DefaultComponentRenderer { - - @Override - public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, - Translator translator, RenderResult renderResult, String[] args) { - - HorizontalBarChartComponent chartCmp = (HorizontalBarChartComponent)source; - renderD3js(renderer, sb, chartCmp); - } - - private void renderD3js(Renderer renderer, StringOutput sb, HorizontalBarChartComponent chartCmp) { - String cmpId = chartCmp.getDispatchID(); - List<BarSeries> seriesList = chartCmp.getSeries(); - Stringuified infos = BarSeries.getDatasAndColors(seriesList, chartCmp.getDefaultBarClass()); - int maxNumOfPoints = getNumOfPoints(chartCmp); - int height = 50 + (25 * maxNumOfPoints); - sb.append("<div id='d").append(cmpId).append("d3holder' class='d3chart' style='width:600px;height:").append(height).append("px'></div>\n") - .append("<script type='text/javascript'>\n") - .append("/* <![CDATA[ */ ") - .append("jQuery(function () {\n") - .append("var placeholderheight = jQuery('#d").append(cmpId).append("d3holder').height();\n") - .append("var placeholderwidth = jQuery('#d").append(cmpId).append("d3holder').width();\n"); - - sb.append("var data = [").append(infos.getData()).append("];\n"); - - sb.append("var margin = {top: 20, right: 20, bottom: 30, left: 300},\n") - .append(" width = placeholderwidth - margin.left - margin.right,\n") - .append(" height = placeholderheight - margin.top - margin.bottom;\n") - - .append("var x = d3.scale.linear()\n") - .append(" .range([0, width], .1);\n") - - .append("var y = d3.scale.ordinal()\n") - .append(" .rangeRoundBands([height, 0]);\n") - - .append("var xAxis = d3.svg.axis()\n") - .append(" .scale(x)\n") - .append(" .orient('bottom')\n") - .append(" .ticks(5);\n") - - .append("var yAxis = d3.svg.axis()\n") - .append(" .scale(y)\n") - .append(" .orient('left');\n") - - .append("var svg = d3.select('#d").append(cmpId).append("d3holder').append('svg')\n") - .append(" .attr('width', width + margin.left + margin.right)\n") - .append(" .attr('height', height + margin.top + margin.bottom)\n") - .append(" .append('g')\n") - .append(" .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');\n") - - .append("x.domain([0, d3.max(data, function(d) { return d[1]; })]);\n") - .append("y.domain(data.map(function(d) { return d[0]; }));\n") - - .append("svg.append('g')\n") - .append(" .attr('class', 'x axis')\n") - .append(" .attr('transform', 'translate(0,' + height + ')')\n") - .append(" .call(xAxis);\n") - - .append("svg.append('g')\n") - .append(" .attr('class', 'y axis')\n") - .append(" .call(yAxis);\n"); - appendSeries( sb, infos.getColors(), chartCmp); -/* - .append("svg.selectAll('.bar')\n") - .append(" .data(data)\n") - .append(" .enter().append('rect')\n") - .append(" .attr('class', 'bar bar_default')\n") - .append(" .attr('x', 0)\n") - .append(" .attr('width', function(d) { return width - x(d[1]); })\n") - .append(" .attr('y', function(d) { return y(d[0]); })\n") - .append(" .attr('height', y.rangeBand() - 2);\n"); - */ - - sb.append("});\n") - .append("/* ]]> */") - .append("</script>\n"); - } - - private int getNumOfPoints(HorizontalBarChartComponent chartCmp) { - int maxNumOfPoints = 0; - List<BarSeries> seriesList = chartCmp.getSeries(); - for(BarSeries series:seriesList) { - int numOfPoints = series.getPoints().size(); - maxNumOfPoints = Math.max(maxNumOfPoints, numOfPoints); - } - return maxNumOfPoints; - } - - private void appendSeries(StringOutput sb, StringBuilder colors, HorizontalBarChartComponent chartCmp) { - if(colors.length() > 0) { - sb.append("var colors = [").append(colors).append("];"); - } - - List<BarSeries> seriesList = chartCmp.getSeries(); - for(int i=0; i<seriesList.size(); i++) { - String color = seriesList.get(i).getCssClass(); - if(color == null) { - color = chartCmp.getDefaultBarClass(); - } - - String correction = getCorrection(i); - - sb.append("svg.selectAll('.bar").append(i).append("')\n") - .append(" .data(data)\n") - .append(" .enter().append('rect')\n"); - - if(colors.length() == 0) { - sb.append(" .attr('class', 'bar bar").append(i).append(" ").append(color).append("')\n"); - } else { - sb.append(" .attr('class', function(d, i){ if(colors.length > i) { return colors[i]; } return 'bar bar").append(i).append(" ").append(color).append("'; })\n"); - } - - sb.append(" .attr('fill', '").append(color).append("')\n") - .append(" .attr('x', ").append(correction).append(")\n") - .append(" .attr('y', function(d) { return y(d[0]); })\n") - .append(" .attr('width', function(d) { return x(d[").append((i+1)).append("]); })\n") - //.append(" .attr('height', function(d) { return height - y(d[").append((i+1)).append("]); });\n"); - .append(" .attr('height', y.rangeBand() - 2);\n"); - } - } - - private String getCorrection(int i) { - if(i == 0) return "0"; - if(i == 1) return "function(d) { return x(d[1]); }"; - if(i == 2) return "function(d) { return x(d[1] + d[2]); }"; - return ""; - } -} diff --git a/src/main/java/org/olat/core/gui/components/chart/Scale.java b/src/main/java/org/olat/core/gui/components/chart/StatisticsComponent.java similarity index 56% rename from src/main/java/org/olat/core/gui/components/chart/Scale.java rename to src/main/java/org/olat/core/gui/components/chart/StatisticsComponent.java index 60dec5ba3295d24f21b1de3899f1839d90110a16..e1fbdceff58277e0bdc4cfdeda69097a34f049fc 100644 --- a/src/main/java/org/olat/core/gui/components/chart/Scale.java +++ b/src/main/java/org/olat/core/gui/components/chart/StatisticsComponent.java @@ -19,13 +19,33 @@ */ package org.olat.core.gui.components.chart; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.components.DefaultComponentRenderer; +import org.olat.core.gui.render.ValidationResult; + /** * + * Initial date: 07.03.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public enum Scale { - plain, - percent, - hour +public class StatisticsComponent extends DefaultD3Component { + + private static final ComponentRenderer RENDERER = new DefaultComponentRenderer(); + + public StatisticsComponent(String name) { + super(name); + } + + @Override + public ComponentRenderer getHTMLRendererSingleton() { + return RENDERER; + } + + @Override + public void validate(UserRequest ureq, ValidationResult vr) { + super.validate(ureq, vr); + vr.getJsAndCSSAdder().addRequiredStaticJsFile("js/jquery/openolat/jquery.statistics.chart.min.js"); + } } diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/elements/FormLink.java b/src/main/java/org/olat/core/gui/components/form/flexible/elements/FormLink.java index c765ceab03442f67755e8ffc5f6a59464a79b067..21933f8630f59ea60fddd39ab3458e00f8b9544d 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/elements/FormLink.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/elements/FormLink.java @@ -40,6 +40,11 @@ public interface FormLink extends FormItem{ * @param customDisabledLinkCSS */ public void setCustomDisabledLinkCSS(String customDisabledLinkCSS); + + /** + * @return The i18n key for the link text + */ + public String getI18nKey(); /** * Set the i18n key for the link text diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FormLinkImpl.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FormLinkImpl.java index 0a5cc7ab93fd81a1f4ddf6280681e889e1285d69..8981a6252945e8f6537053a751f5cc61625e2252 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FormLinkImpl.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/FormLinkImpl.java @@ -197,6 +197,10 @@ public class FormLinkImpl extends FormItemImpl implements FormLink { this.component.setCustomDisabledLinkCSS(customDisabledLinkCSS); } } + + public String getI18nKey() { + return i18n; + } /** * @see org.olat.core.gui.components.form.flexible.elements.FormLink#setI18nKey(java.lang.String) diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextBoxListElementImpl.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextBoxListElementImpl.java index 958c410c9897f0962aad0918820545614de76659..e50254d3be64d646fa4c7bad6092d320786825c0 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextBoxListElementImpl.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/TextBoxListElementImpl.java @@ -89,7 +89,7 @@ public class TextBoxListElementImpl extends AbstractTextElement implements TextB @Override public String getValue(){ // String paramVal = getRootForm().getRequestParameter("textboxlistinput" + getFormDispatchId()); - return StringUtils.join(this.component.getCurrentItemValues(),", "); + return StringUtils.join(component.getCurrentItemValues(),", "); } @Override diff --git a/src/main/java/org/olat/core/gui/components/image/ImageComponent.java b/src/main/java/org/olat/core/gui/components/image/ImageComponent.java index 5f72e399c5913571f3e3dd7d03b2eea62cece5fa..fd6e91170a76c21d2e845c0d9bb634a336995305 100644 --- a/src/main/java/org/olat/core/gui/components/image/ImageComponent.java +++ b/src/main/java/org/olat/core/gui/components/image/ImageComponent.java @@ -222,23 +222,24 @@ public class ImageComponent extends AbstractComponent { private Size getImageSize(String suffix) { Size result = null; - Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); - if (iter.hasNext()) { - ImageReader reader = iter.next(); - try { - ImageInputStream stream = new MemoryCacheImageInputStream(mediaResource.getInputStream()); - reader.setInput(stream); - int width = reader.getWidth(reader.getMinIndex()); - int height = reader.getHeight(reader.getMinIndex()); - result = new Size(width, height, false); - } catch (IOException e) { - log.error(e.getMessage()); - } finally { - reader.dispose(); - } - } else { - log.error("No reader found for given format: " + suffix); - } - return result; + Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); + if (iter.hasNext()) { + ImageReader reader = iter.next(); + try { + ImageInputStream stream = new MemoryCacheImageInputStream(mediaResource.getInputStream()); + reader.setInput(stream); + int readerMinIndex = reader.getMinIndex(); + int width = reader.getWidth(readerMinIndex); + int height = reader.getHeight(readerMinIndex); + result = new Size(width, height, false); + } catch (IOException e) { + log.error(e.getMessage()); + } finally { + reader.dispose(); + } + } else { + log.error("No reader found for given format: " + suffix); + } + return result; } } \ No newline at end of file diff --git a/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListComponent.java b/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListComponent.java index f8d3bef7b548fbf0a5100714db1de5a2794d6075..214de503c04b2dce5a3dd191e8ad66af34eae835 100644 --- a/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListComponent.java +++ b/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListComponent.java @@ -215,11 +215,14 @@ public abstract class TextBoxListComponent extends FormBaseComponentImpl { */ private String getAutoCompletionItemCaptionByValue(String itemValue) { String autoCompletionItemCaption = ""; - if (this.getAutoCompleteContent() == null) + Map<String,String> content = getAutoCompleteContent(); + if (content == null) { return autoCompletionItemCaption; - for (Entry<String, String> autoCompletionItemEntry : this.getAutoCompleteContent().entrySet()) { - if (autoCompletionItemEntry.getValue().equals(itemValue)) + } + for (Entry<String, String> autoCompletionItemEntry : content.entrySet()) { + if (autoCompletionItemEntry.getValue().equals(itemValue)) { autoCompletionItemCaption = autoCompletionItemEntry.getKey(); + } } return autoCompletionItemCaption; } @@ -442,7 +445,9 @@ public abstract class TextBoxListComponent extends FormBaseComponentImpl { List<String> filtered = new ArrayList<String>(); for(String item:content.keySet()) { String antiItem = filter.filter(item); - filtered.add(StringHelper.escapeHtml(antiItem)); + if(StringHelper.containsNonWhitespace(antiItem)) { + filtered.add(antiItem); + } } return StringUtils.join(filtered, ", "); } else diff --git a/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListRenderer.java b/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListRenderer.java index 29530308a021666b2df2ba69f2494d36e40efb2f..c66c32df486ee58b1c90b34dc2a94f8aad640b09 100644 --- a/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListRenderer.java +++ b/src/main/java/org/olat/core/gui/components/textboxlist/TextBoxListRenderer.java @@ -102,10 +102,11 @@ public class TextBoxListRenderer implements ComponentRenderer { TextBoxListElementImpl te = ((TextBoxListElementComponent)tblComponent).getTextElementImpl(); Form rootForm = te.getRootForm(); String dispatchId = tblComponent.getFormDispatchId(); + String initialValue = tblComponent.getInitialItemsAsString(); output.append("<input type=\"text\" id=\"textboxlistinput").append(dispatchId).append("\" ") .append("name='textboxlistinput").append(dispatchId).append("' ") - .append("value='").append(tblComponent.getInitialItemsAsString()).append("'") + .append("value='").append(initialValue).append("'") .append("/>\n"); // OO-137 : here, we display the currentItems. (at first render, this is equal to initialItems) diff --git a/src/main/java/org/olat/core/util/image/spi/ImageHelperImpl.java b/src/main/java/org/olat/core/util/image/spi/ImageHelperImpl.java index d53bc38e17f81332d28e9e2186fd3574b8656065..b38747605bf2b8bf4ed6eb9a7878d3d107a511bf 100644 --- a/src/main/java/org/olat/core/util/image/spi/ImageHelperImpl.java +++ b/src/main/java/org/olat/core/util/image/spi/ImageHelperImpl.java @@ -43,6 +43,7 @@ import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.FileImageInputStream; @@ -359,45 +360,55 @@ public class ImageHelperImpl implements ImageHelperSPI { return computeScaledSize(width, height, maxWidth, maxHeight); } + private static SizeAndBufferedImage calcScaledSize(ImageInputStream stream, + String suffix, int maxWidth, int maxHeight) { + Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); + if(iter.hasNext()) { + ImageReader reader = iter.next(); + try { + reader.setInput(stream, true, true); + int width = reader.getWidth(reader.getMinIndex()); + int height = reader.getHeight(reader.getMinIndex()); + Size size = new Size(width, height, false); + Size scaledSize = computeScaledSize(width, height, maxWidth, maxHeight); + SizeAndBufferedImage all = new SizeAndBufferedImage(size, scaledSize); + + int readerMinIndex = reader.getMinIndex(); + ImageReadParam param = reader.getDefaultReadParam(); + Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0); + while (imageTypes.hasNext()) { + ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); + int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); + if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) { + param.setDestinationType(imageTypeSpecifier); + break; + } + } - - private static SizeAndBufferedImage calcScaledSize(ImageInputStream stream, String suffix, int maxWidth, int maxHeight) { - Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix); - if (iter.hasNext()) { - ImageReader reader = iter.next(); - try { - reader.setInput(stream); - int width = reader.getWidth(reader.getMinIndex()); - int height = reader.getHeight(reader.getMinIndex()); - Size size = new Size(width, height, false); - Size scaledSize = computeScaledSize(width, height, maxWidth, maxHeight); - SizeAndBufferedImage all = new SizeAndBufferedImage(size, scaledSize); - - double memoryKB = (width * height * 4) / 1024d; - if(memoryKB > 2000) {//check limit at 20MB - double free = Runtime.getRuntime().freeMemory() / 1024d; - if(free > memoryKB) { - all.setImage(reader.read(reader.getMinIndex())); - } else { - //make sub sampling to save memory - int ratio = (int)Math.round(Math.sqrt(memoryKB / free)); - ImageReadParam param = reader.getDefaultReadParam(); - param.setSourceSubsampling(ratio, ratio, 0, 0); - all.setImage(reader.read(reader.getMinIndex(), param)); - } - } else { - all.setImage(reader.read(reader.getMinIndex())); - } - return all; - } catch (IOException e) { - log.error(e.getMessage()); - } finally { - reader.dispose(); - } - } else { - log.error("No reader found for given format: " + suffix, null); - } - return null; + double memoryKB = (width * height * 4) / 1024d; + if (memoryKB > 2000) {// check limit at 20MB + double free = Runtime.getRuntime().freeMemory() / 1024d; + if (free > memoryKB) { + all.setImage(reader.read(readerMinIndex, param)); + } else { + // make sub sampling to save memory + int ratio = (int) Math.round(Math.sqrt(memoryKB / free)); + param.setSourceSubsampling(ratio, ratio, 0, 0); + all.setImage(reader.read(readerMinIndex, param)); + } + } else { + all.setImage(reader.read(readerMinIndex, param)); + } + return all; + } catch (IOException e) { + log.error(e.getMessage(), e); + } finally { + reader.dispose(); + } + } else { + log.error("No reader found for given format: " + suffix, null); + } + return null; } private static Size computeScaledSize(int width, int height, int maxWidth, int maxHeight) { diff --git a/src/main/java/org/olat/core/util/mail/MailNotificationEditController.java b/src/main/java/org/olat/core/util/mail/MailNotificationEditController.java index 2870e6b4f31461654ad8cfdb4b1dce05043fb892..dcb77787a6eeefd220a497b00e11109a0ca7d0f0 100644 --- a/src/main/java/org/olat/core/util/mail/MailNotificationEditController.java +++ b/src/main/java/org/olat/core/util/mail/MailNotificationEditController.java @@ -65,7 +65,7 @@ public class MailNotificationEditController extends BasicController { * @param useCancel */ public MailNotificationEditController(WindowControl wControl, UserRequest ureq, MailTemplate mailTemplate, - boolean useCancel, boolean mandatory) { + boolean useCancel, boolean mandatory, boolean cc) { super(ureq, wControl); this.mailTemplate = mailTemplate; orgMailSubject = mailTemplate.getSubjectTemplate(); @@ -73,7 +73,7 @@ public class MailNotificationEditController extends BasicController { cpFrom = mailTemplate.getCpfrom(); mainVC = createVelocityContainer("mailnotification"); - mailForm = new MailTemplateForm(ureq, wControl, mailTemplate, useCancel, mandatory); + mailForm = new MailTemplateForm(ureq, wControl, mailTemplate, useCancel, mandatory, cc); listenTo(mailForm); mainVC.put("mailForm", mailForm.getInitialComponent()); diff --git a/src/main/java/org/olat/core/util/mail/MailTemplateForm.java b/src/main/java/org/olat/core/util/mail/MailTemplateForm.java index d05316f239c976871719114c7ff7e1afa155f811..9f559a4e51b89c4f9e93f2479479dea6b06c5d8d 100644 --- a/src/main/java/org/olat/core/util/mail/MailTemplateForm.java +++ b/src/main/java/org/olat/core/util/mail/MailTemplateForm.java @@ -55,6 +55,7 @@ public class MailTemplateForm extends FormBasicController { private SelectionElement ccSender; private final static String NLS_CONTACT_SEND_CP_FROM = "contact.cp.from"; + private final boolean cc; private final boolean useCancel; private final boolean useSubmit; private MailTemplate template; @@ -66,8 +67,10 @@ public class MailTemplateForm extends FormBasicController { * @param useCancel * @param listeningController Controller that listens to form events */ - public MailTemplateForm(UserRequest ureq, WindowControl wControl, MailTemplate template, boolean useCancel, boolean mandatoryEmail) { + public MailTemplateForm(UserRequest ureq, WindowControl wControl, MailTemplate template, + boolean useCancel, boolean mandatoryEmail, boolean cc) { super(ureq, wControl); + this.cc = cc; this.template = template; this.useCancel = useCancel; this.useSubmit = true; @@ -77,6 +80,7 @@ public class MailTemplateForm extends FormBasicController { public MailTemplateForm(UserRequest ureq, WindowControl wControl, MailTemplate template, boolean mandatoryEmail, Form rootForm) { super(ureq, wControl, LAYOUT_DEFAULT, null, rootForm); + this.cc = true; this.template = template; useCancel = useSubmit = false; this.mandatoryEmail = mandatoryEmail; @@ -90,7 +94,7 @@ public class MailTemplateForm extends FormBasicController { public void updateTemplateFromForm(MailTemplate template) { template.setSubjectTemplate(subjectElem.getValue()); template.setBodyTemplate(bodyElem.getValue()); - template.setCpfrom(ccSender.isSelected(0)); + template.setCpfrom(ccSender.isVisible() && ccSender.isSelected(0)); } @Override @@ -145,6 +149,7 @@ public class MailTemplateForm extends FormBasicController { bodyElem.setMandatory(true); ccSender = uifactory.addCheckboxesVertical("tcpfrom", "", formLayout, new String[]{"xx"}, new String[]{translate(NLS_CONTACT_SEND_CP_FROM)}, null, 1); + ccSender.setVisible(cc); FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); formLayout.add(buttonGroupLayout); @@ -163,7 +168,7 @@ public class MailTemplateForm extends FormBasicController { boolean sm = mandatoryEmail || sendMail.isSelected(0); subjectElem.setVisible(sm); bodyElem.setVisible(sm); - ccSender.setVisible(sm); + ccSender.setVisible(sm && cc); } @Override diff --git a/src/main/java/org/olat/course/assessment/AssessmentEditController.java b/src/main/java/org/olat/course/assessment/AssessmentEditController.java index d8e6c6ffbc69fe0a6751eb9702566cff8d5922de..64a7e40dc0dc20c6382c4a6dcebd148f2964725c 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentEditController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentEditController.java @@ -228,8 +228,9 @@ public class AssessmentEditController extends BasicController { } else if (source == detailsEditController) { //fxdiff FXOLAT-108: reset SCORM test if(event == Event.CHANGED_EVENT) { - doUpdateAssessmentData(ureq.getIdentity()); - fireEvent(ureq, Event.CHANGED_EVENT); + assessmentForm.reloadData(); + } else if(event == Event.DONE_EVENT) { + fireEvent(ureq, Event.DONE_EVENT); } } else if (source == alreadyLockedDialogController) { if (event == Event.CANCELLED_EVENT || DialogBoxUIFactory.isOkEvent(event)) { @@ -252,10 +253,9 @@ public class AssessmentEditController extends BasicController { ScoreEvaluation scoreEval = null; Float newScore = null; Boolean newPassed = null; - //String userName = userCourseEnvironment.getIdentityEnvironment().getIdentity().getName(); - + if (assessmentForm.isHasAttempts() && assessmentForm.isAttemptsDirty()) { - this.courseNode.updateUserAttempts(new Integer(assessmentForm.getAttempts()), userCourseEnvironment, coachIdentity); + courseNode.updateUserAttempts(new Integer(assessmentForm.getAttempts()), userCourseEnvironment, coachIdentity); } if (assessmentForm.isHasScore() && assessmentForm.isScoreDirty()) { @@ -295,11 +295,11 @@ public class AssessmentEditController extends BasicController { if (assessmentForm.isCoachCommentDirty()) { String newCoachComment = assessmentForm.getCoachComment().getValue(); // Update properties in db - this.courseNode.updateUserCoachComment(newCoachComment, userCourseEnvironment); + courseNode.updateUserCoachComment(newCoachComment, userCourseEnvironment); } // Refresh score view - userCourseEnvironment.getScoreAccounting().scoreInfoChanged(this.courseNode, scoreEval); + userCourseEnvironment.getScoreAccounting().scoreInfoChanged(courseNode, scoreEval); } /** diff --git a/src/main/java/org/olat/course/assessment/AssessmentForm.java b/src/main/java/org/olat/course/assessment/AssessmentForm.java index 02ba7d22aa993dd6cff9d1b5219e5cdf075cf61b..9bcbc40541e1c4864b9b0357480ab4948922f3ab 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentForm.java +++ b/src/main/java/org/olat/course/assessment/AssessmentForm.java @@ -80,10 +80,10 @@ public class AssessmentForm extends FormBasicController { AssessmentForm(UserRequest ureq, WindowControl wControl, AssessableCourseNode assessableCourseNode, AssessedIdentityWrapper assessedIdentityWrapper) { super(ureq, wControl); - this.hasAttempts = assessableCourseNode.hasAttemptsConfigured(); - this.hasScore = assessableCourseNode.hasScoreConfigured(); - this.hasPassed = assessableCourseNode.hasPassedConfigured(); - this.hasComment = assessableCourseNode.hasCommentConfigured(); + hasAttempts = assessableCourseNode.hasAttemptsConfigured(); + hasScore = assessableCourseNode.hasScoreConfigured(); + hasPassed = assessableCourseNode.hasPassedConfigured(); + hasComment = assessableCourseNode.hasCommentConfigured(); this.assessedIdentityWrapper = assessedIdentityWrapper; this.assessableCourseNode = assessableCourseNode; @@ -204,6 +204,30 @@ public class AssessmentForm extends FormBasicController { } return Float.parseFloat(scoreStr); } + + protected void reloadData() { + UserCourseEnvironment userCourseEnv = assessedIdentityWrapper.getUserCourseEnvironment(); + ScoreEvaluation scoreEval = userCourseEnv.getScoreAccounting().evalCourseNode(assessableCourseNode); + if (scoreEval == null) scoreEval = new ScoreEvaluation(null, null); + + if (hasAttempts) { + attemptsValue = assessableCourseNode.getUserAttempts(userCourseEnv); + attempts.setIntValue(attemptsValue == null ? 0 : attemptsValue.intValue()); + } + + if (hasScore) { + scoreValue = scoreEval.getScore(); + if (scoreValue != null) { + score.setValue(AssessmentHelper.getRoundedScore(scoreValue)); + } + } + + if (hasPassed) { + Boolean passedValue = scoreEval.getPassed(); + passed.select(passedValue == null ? "undefined" :passedValue.toString(), true); + passed.setEnabled(cut == null); + } + } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { diff --git a/src/main/java/org/olat/course/assessment/AssessmentMainController.java b/src/main/java/org/olat/course/assessment/AssessmentMainController.java index 862b17df0b274144cee5972b886947adf38fc062..ca80d081712b76571888a1043e9f5eec83b69a25 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMainController.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMainController.java @@ -562,7 +562,7 @@ public class AssessmentMainController extends MainLayoutBasicController implemen } } else if (source == assessmentEditController) { - if (event.equals(Event.CHANGED_EVENT)) { + if (event.equals(Event.CHANGED_EVENT) || event.equals(Event.DONE_EVENT)) { // refresh identity in list model if (userListCtr != null && userListCtr.getTableDataModel() instanceof AssessedIdentitiesTableDataModel) { @@ -571,8 +571,8 @@ public class AssessmentMainController extends MainLayoutBasicController implemen if (aiwList.contains(this.assessedIdentityWrapper)) { ICourse course = CourseFactory.loadCourse(ores); aiwList.remove(this.assessedIdentityWrapper); - this.assessedIdentityWrapper = AssessmentHelper.wrapIdentity(this.assessedIdentityWrapper.getIdentity(), - this.localUserCourseEnvironmentCache, initialLaunchDates, course, currentCourseNode); + assessedIdentityWrapper = AssessmentHelper.wrapIdentity(assessedIdentityWrapper.getIdentity(), + localUserCourseEnvironmentCache, initialLaunchDates, course, currentCourseNode); aiwList.add(this.assessedIdentityWrapper); userListCtr.modelChanged(); } diff --git a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java index 0373834e7a7c3bb297eacf8749439ea94d46ee07..6219e8b9e59f434380d29b23efd37cbe111c1364 100644 --- a/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java +++ b/src/main/java/org/olat/course/assessment/IdentityAssessmentEditController.java @@ -154,6 +154,9 @@ public class IdentityAssessmentEditController extends BasicController { } else if (event.equals(Event.CHANGED_EVENT)) { doIdentityAssessmentOverview(ureq, true); fireEvent(ureq, Event.CHANGED_EVENT); + } else if (event.equals(Event.DONE_EVENT)) { + doIdentityAssessmentOverview(ureq, true); + fireEvent(ureq, Event.DONE_EVENT); } } } diff --git a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java index 28bbfa55010e28eb1b51201ba551fd2273b58d3b..16367b38d307c9810ed28a380c44d77e8125cf44 100644 --- a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java +++ b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java @@ -340,10 +340,7 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements */ @Override public boolean isEditableConfigured() { - // manual scoring fields can be edited manually - ModuleConfiguration config = getModuleConfiguration(); - Boolean manualCorr = (Boolean)config.get(CheckListCourseNode.CONFIG_KEY_PASSED_MANUAL_CORRECTION); - return true;//manualCorr != null && manualCorr.booleanValue(); + return true; } /** diff --git a/src/main/java/org/olat/course/nodes/IQSELFCourseNode.java b/src/main/java/org/olat/course/nodes/IQSELFCourseNode.java index 51f2f7c8c92164c88c370fcf0c6b0bfe9c4e7fb9..a699250b1d6a383e5f3f8c50c84a5b948d603b5f 100644 --- a/src/main/java/org/olat/course/nodes/IQSELFCourseNode.java +++ b/src/main/java/org/olat/course/nodes/IQSELFCourseNode.java @@ -219,7 +219,7 @@ public class IQSELFCourseNode extends AbstractAccessableCourseNode implements Se try { QTIExportFormatter qef = new QTIExportFormatterCSVType2(locale, null, "\t", "\"", "\\", "\r\n", false); - return qem.selectAndExportResults(qef, course.getResourceableId(), getShortTitle(), getIdent(), re, exportStream, charset, ".xls"); + return qem.selectAndExportResults(qef, course.getResourceableId(), getShortTitle(), getIdent(), re, exportStream, ".xls"); } catch (IOException e) { log.error("", e); return false; diff --git a/src/main/java/org/olat/course/nodes/IQSURVCourseNode.java b/src/main/java/org/olat/course/nodes/IQSURVCourseNode.java index fa7012ef0663121a164243eb086e7cad072af292..d7e31842536d81a1103ab6e1c79d7c2198ef5fc7 100644 --- a/src/main/java/org/olat/course/nodes/IQSURVCourseNode.java +++ b/src/main/java/org/olat/course/nodes/IQSURVCourseNode.java @@ -137,7 +137,7 @@ public class IQSURVCourseNode extends AbstractAccessableCourseNode implements QT QTIStatisticSearchParams searchParams = new QTIStatisticSearchParams(courseOres.getResourceableId(), getIdent()); searchParams.setLimitToGroups(options.getParticipantsGroups()); - QTIStatisticResourceResult result = new QTIStatisticResourceResult(this, searchParams); + QTIStatisticResourceResult result = new QTIStatisticResourceResult(courseOres, this, searchParams); return result; } @@ -243,7 +243,7 @@ public class IQSURVCourseNode extends AbstractAccessableCourseNode implements QT QTIExportFormatter qef = new QTIExportFormatterCSVType3(locale, null,"\t", "\"", "\\", "\r\n", false); try { - return qem.selectAndExportResults(qef, course.getResourceableId(), getShortTitle(), getIdent(), re, exportStream, charset, ".xls"); + return qem.selectAndExportResults(qef, course.getResourceableId(), getShortTitle(), getIdent(), re, exportStream, ".xls"); } catch (IOException e) { log.error("", e); return false; diff --git a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java index 304180e0293a6cbc711b83b4e8a405ebecf17fdc..86fedd266d89977f051e974ab5a9affd597f893b 100644 --- a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java +++ b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java @@ -151,7 +151,7 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements As QTIStatisticSearchParams searchParams = new QTIStatisticSearchParams(courseOres.getResourceableId(), getIdent()); searchParams.setLimitToGroups(options.getParticipantsGroups()); - QTIStatisticResourceResult result = new QTIStatisticResourceResult(this, searchParams); + QTIStatisticResourceResult result = new QTIStatisticResourceResult(courseOres, this, searchParams); return result; } @@ -424,7 +424,7 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements As String shortTitle = getShortTitle(); QTIExportManager qem = QTIExportManager.getInstance(); QTIExportFormatter qef = new QTIExportFormatterCSVType1(locale, "\t", "\"", "\\", "\r\n", false); - return qem.selectAndExportResults(qef, courseResourceableId, shortTitle, getIdent(), re, exportStream, charset, ".xls"); + return qem.selectAndExportResults(qef, courseResourceableId, shortTitle, getIdent(), re, exportStream, ".xls"); } } catch (IOException e) { log.error("", e); diff --git a/src/main/java/org/olat/course/nodes/cl/manager/CheckboxManagerImpl.java b/src/main/java/org/olat/course/nodes/cl/manager/CheckboxManagerImpl.java index 0a5a25fecf9dc020e72e9ee3ac41af9f66255c6f..a9819cc49fb5d47414e2702e2490931a07a71cf3 100644 --- a/src/main/java/org/olat/course/nodes/cl/manager/CheckboxManagerImpl.java +++ b/src/main/java/org/olat/course/nodes/cl/manager/CheckboxManagerImpl.java @@ -265,11 +265,11 @@ public class CheckboxManagerImpl implements CheckboxManager { DBCheckbox lockedCheckbox = loadForUpdate(checkbox); if(lockedCheckbox != null) { //locked -> reload to make sure nobody create it - DBCheck reloaedCheck = loadCheck(checkbox, owner); - if(reloaedCheck == null) { + DBCheck reloadedCheck = loadCheck(checkbox, owner); + if(reloadedCheck == null) { createCheck(lockedCheckbox, owner, score, checked); } else { - currentCheck = reloaedCheck; + currentCheck = reloadedCheck; } } } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/AssessedIdentityCheckListController.java b/src/main/java/org/olat/course/nodes/cl/ui/AssessedIdentityCheckListController.java index 5a848d6a837fa236373735ac40abcca653985194..a64ea1f3da16054283586e3a1a2352851d6616fb 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/AssessedIdentityCheckListController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/AssessedIdentityCheckListController.java @@ -27,6 +27,7 @@ import java.util.Map; 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.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.TextElement; @@ -139,12 +140,22 @@ public class AssessedIdentityCheckListController extends FormBasicController { boxEl.addActionListener(FormEvent.ONCHANGE); TextElement pointEl = null; - if(withScore) { + if(withScore && checkbox.getPoints() != null) { String pointId = "point_" + checkbox.getCheckboxId(); - String points = AssessmentHelper.getRoundedScore(check.getScore()); + String points; + if(check != null && check.getChecked() != null && check.getChecked().booleanValue()) { + points = AssessmentHelper.getRoundedScore(check.getScore()); + } else { + points = null; + } pointEl = uifactory.addTextElement(pointId, null, 16, points, formLayout); pointEl.setDisplaySize(5); - pointEl.setExampleKey("checklist.point.example", new String[]{ "0", "1"}); + + Float maxScore = checkbox.getPoints(); + if(maxScore != null) { + String maxValue = AssessmentHelper.getRoundedScore(maxScore); + pointEl.setExampleKey("checklist.point.example", new String[]{ "0", maxValue}); + } } CheckboxWrapper wrapper = new CheckboxWrapper(checkbox, check, boxEl, pointEl); @@ -201,7 +212,34 @@ public class AssessedIdentityCheckListController extends FormBasicController { checkboxManager.check(courseOres, courseNode.getIdent(), batchElements); courseNode.updateScoreEvaluation(userCourseEnv, assessedIdentity); - fireEvent(ureq, Event.DONE_EVENT); + fireEvent(ureq, Event.CHANGED_EVENT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source instanceof MultipleSelectionElement) { + MultipleSelectionElement boxEl = (MultipleSelectionElement)source; + CheckboxWrapper wrapper = (CheckboxWrapper)boxEl.getUserObject(); + doUpdateCheck(wrapper, boxEl.isAtLeastSelected(1)); + } + super.formInnerEvent(ureq, source, event); + } + + private void doUpdateCheck(CheckboxWrapper wrapper, boolean check) { + if(wrapper.getPointEl() == null) return;//nothing to do + + if(check) { + if(!StringHelper.containsNonWhitespace(wrapper.getPointEl().getValue())) { + Checkbox checkbox = wrapper.getCheckbox(); + Float points = checkbox.getPoints(); + if(points != null) { + String val = AssessmentHelper.getRoundedScore(points); + wrapper.getPointEl().setValue(val); + } + } + } else if(wrapper.getPointEl() != null) { + wrapper.getPointEl().setValue(""); + } } @Override diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java index e2013a080baa004454718c1d08a04a78b58fc66d..6aecd793c2c0deb37a5a8085dce60e831a603368 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java @@ -68,6 +68,8 @@ 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.course.CourseFactory; +import org.olat.course.ICourse; import org.olat.course.nodes.CheckListCourseNode; import org.olat.course.nodes.MSCourseNode; import org.olat.course.nodes.cl.CheckboxManager; @@ -567,11 +569,13 @@ public class CheckListAssessmentController extends FormBasicController implement private void doExportPDF(UserRequest ureq) { try { + ICourse course = CourseFactory.loadCourse(courseOres); + String name = courseNode.getShortTitle(); CheckboxPDFExport pdfExport = new CheckboxPDFExport(name, getTranslator(), userPropertyHandlers); pdfExport.setAuthor(userManager.getUserDisplayName(getIdentity())); pdfExport.setCourseNodeTitle(courseNode.getShortTitle()); - pdfExport.setCourseTitle(courseNode.getLongTitle()); + pdfExport.setCourseTitle(course.getCourseTitle()); pdfExport.setCourseNodeTitle(courseNode.getShortTitle()); String groupName = table.getSelectedFilterValue(); pdfExport.setGroupName(groupName); @@ -584,11 +588,13 @@ public class CheckListAssessmentController extends FormBasicController implement private void doCheckedExportPDF(UserRequest ureq) { try { + ICourse course = CourseFactory.loadCourse(courseOres); + String name = courseNode.getShortTitle(); CheckedPDFExport pdfExport = new CheckedPDFExport(name, getTranslator(), withScore, userPropertyHandlers); pdfExport.setAuthor(userManager.getUserDisplayName(getIdentity())); pdfExport.setCourseNodeTitle(courseNode.getShortTitle()); - pdfExport.setCourseTitle(courseNode.getLongTitle()); + pdfExport.setCourseTitle(course.getCourseTitle()); pdfExport.create(checkboxList, model.getObjects()); ureq.getDispatchResult().setResultingMediaResource(pdfExport); } catch (IOException | COSVisitorException | TransformerException e) { diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListBoxListEditController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListBoxListEditController.java index 64ac429fea17a8a29ebc52a7d96d07859866d8b1..fbd90d6b5a0f2abe7ab985e0c341f20d38267a68 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckListBoxListEditController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListBoxListEditController.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.form.flexible.FormItem; @@ -36,18 +37,28 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFle import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer; import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer; 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.id.OLATResourceable; +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.CourseFactory; +import org.olat.course.ICourse; import org.olat.course.nodes.CheckListCourseNode; import org.olat.course.nodes.MSCourseNode; +import org.olat.course.nodes.cl.CheckboxManager; import org.olat.course.nodes.cl.model.Checkbox; import org.olat.course.nodes.cl.model.CheckboxList; import org.olat.course.nodes.cl.ui.CheckboxConfigDataModel.Cols; +import org.olat.course.run.environment.CourseEnvironment; import org.olat.modules.ModuleConfiguration; /** @@ -71,6 +82,8 @@ public class CheckListBoxListEditController extends FormBasicController { private ModuleConfiguration config; private final OLATResourceable courseOres; private final CheckListCourseNode courseNode; + + private final CheckboxManager checkboxManager; public CheckListBoxListEditController(UserRequest ureq, WindowControl wControl, OLATResourceable courseOres, CheckListCourseNode courseNode, boolean inUse) { @@ -79,6 +92,7 @@ public class CheckListBoxListEditController extends FormBasicController { this.courseOres = courseOres; this.courseNode = courseNode; config = courseNode.getModuleConfiguration(); + checkboxManager = CoreSpringFactory.getImpl(CheckboxManager.class); initForm(ureq); } @@ -106,7 +120,9 @@ public class CheckListBoxListEditController extends FormBasicController { pointColModel = new DefaultFlexiColumnModel(visible, Cols.points.i18nKey(), Cols.points.ordinal(), false, null); columnsModel.addFlexiColumnModel(pointColModel); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.release.i18nKey(), Cols.release.ordinal())); - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.file.i18nKey(), Cols.file.ordinal())); + columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel(Cols.file.i18nKey(), + Cols.file.ordinal(), "download", false, null, + new StaticFlexiCellRenderer("download", new TextFlexiCellRenderer()))); columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("edit", translate("edit"), "edit")); CheckboxList list = (CheckboxList)config.get(CheckListCourseNode.CONFIG_KEY_CHECKBOX); @@ -141,6 +157,9 @@ public class CheckListBoxListEditController extends FormBasicController { if("edit".equals(cmd)) { Checkbox row = model.getObject(se.getIndex()); doOpenEdit(ureq, row, false, translate("edit.checkbox")); + } else if("download".equals(cmd)) { + Checkbox row = model.getObject(se.getIndex()); + doDownloadFile(ureq, row); } } } @@ -183,6 +202,18 @@ public class CheckListBoxListEditController extends FormBasicController { boxTable.reset(); } + private void doDownloadFile(UserRequest ureq, Checkbox checkbox) { + ICourse course = CourseFactory.loadCourse(courseOres); + CourseEnvironment courseEnv = course.getCourseEnvironment(); + VFSContainer container = checkboxManager.getFileContainer(courseEnv, courseNode, checkbox); + VFSItem item = container.resolve(checkbox.getFilename()); + if(item instanceof VFSLeaf) { + VFSMediaResource rsrc = new VFSMediaResource((VFSLeaf)item); + rsrc.setDownloadable(true); + ureq.getDispatchResult().setResultingMediaResource(rsrc); + } + } + private void doEdit(UserRequest ureq, Checkbox checkbox) { CheckboxList list = (CheckboxList)config.get(CheckListCourseNode.CONFIG_KEY_CHECKBOX); if(list == null) { diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListRunController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListRunController.java index 379d0d9d6ab44f42e5c9ece5a89418786e56064a..fac9c416407a445ff2d14fc68f85a2a868061b64 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckListRunController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListRunController.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -135,6 +134,9 @@ public class CheckListRunController extends FormBasicController implements Contr layoutCont.contextPut("readOnly", new Boolean(readOnly)); if(dueDate != null) { layoutCont.contextPut("dueDate", dueDate); + if(dueDate.compareTo(new Date()) < 0) { + layoutCont.contextPut("afterDueDate", Boolean.TRUE); + } } layoutCont.contextPut("withScore", new Boolean(withScore)); @@ -158,7 +160,6 @@ public class CheckListRunController extends FormBasicController implements Contr layoutCont.contextPut("enableScoreInfo", Boolean.TRUE); exposeConfigToVC(layoutCont); exposeUserDataToVC(layoutCont); - } else { layoutCont.contextPut("enableScoreInfo", Boolean.FALSE); } @@ -269,9 +270,11 @@ public class CheckListRunController extends FormBasicController implements Contr theOne = wrapper.getDbCheckbox(); } - Float score = wrapper.getCheckbox().getPoints(); + + Float score = checked ? wrapper.getCheckbox().getPoints() : 0f; checkboxManager.check(theOne, getIdentity(), score, new Boolean(checked)); - DBFactory.getInstance().commit();//make sure all results is on the database before calculating some scores + //make sure all results is on the database before calculating some scores + //manager commit already DBFactory.getInstance().commit(); courseNode.updateScoreEvaluation(userCourseEnv, getIdentity()); diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxConfigDataModel.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxConfigDataModel.java index 5f7f7e7e943b940a6b508b6835e6706e20102683..ebeec874f50279f547a8da30997f14244c9b386d 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxConfigDataModel.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxConfigDataModel.java @@ -60,9 +60,7 @@ public class CheckboxConfigDataModel extends DefaultFlexiTableDataModel<Checkbox } return translator.translate("release." + release.name()); } - case file: { - return ""; - } + case file: return box.getFilename(); } return box; } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java index 7313f880ef7208961291e036812d5315590ce7a5..f0aaaabed9e7b5661b9767ceadc85abd50fc2ebe 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java @@ -46,9 +46,12 @@ import org.olat.core.logging.activity.CourseLoggingAction; import org.olat.core.logging.activity.ILoggingAction; import org.olat.core.logging.activity.StringResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; +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.VFSManager; +import org.olat.core.util.vfs.VFSMediaResource; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.nodes.CheckListCourseNode; @@ -65,12 +68,15 @@ import org.olat.util.logging.activity.LoggingResourceable; */ public class CheckboxEditController extends FormBasicController { - private FormLink deleteLink; + private FormLink deleteLink, deleteFileLink, downloadFileLink; private TextElement titleEl, pointsEl; private SingleSelection releaseEl, labelEl; private MultipleSelectionElement awardPointEl; private RichTextElement descriptionEl; private FileElement fileEl; + private FormLayoutContainer deleteFileCont; + + private Boolean deleteFile; private final Checkbox checkbox; private final boolean withScore; @@ -143,6 +149,14 @@ public class CheckboxEditController extends FormBasicController { ureq.getUserSession(), getWindowControl()); fileEl = uifactory.addFileElement("file", formLayout); + fileEl.addActionListener(FormEvent.ONCHANGE); + + String template = velocity_root + "/delete_file.html"; + deleteFileCont = FormLayoutContainer.createCustomFormLayout("delete", getTranslator(), template); + formLayout.add(deleteFileCont); + downloadFileLink = uifactory.addFormLink("download", checkbox.getFilename(), null, deleteFileCont, Link.NONTRANSLATED); + deleteFileLink = uifactory.addFormLink("deleteFile", "delete", null, deleteFileCont, Link.BUTTON); + deleteFileCont.setVisible(StringHelper.containsNonWhitespace(checkbox.getFilename())); FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); formLayout.add(buttonsCont); @@ -197,6 +211,17 @@ public class CheckboxEditController extends FormBasicController { } checkbox.setDescription(descriptionEl.getValue()); + if(Boolean.TRUE.equals(deleteFile)) { + checkbox.setFilename(null); + + ICourse course = CourseFactory.loadCourse(courseOres); + CourseEnvironment courseEnv = course.getCourseEnvironment(); + VFSContainer container = checkboxManager.getFileContainer(courseEnv, courseNode, checkbox); + for (VFSItem chd:container.getItems()) { + chd.delete(); + } + } + File uploadedFile = fileEl.getUploadFile(); if(uploadedFile != null) { String filename = fileEl.getUploadFileName(); @@ -236,9 +261,39 @@ public class CheckboxEditController extends FormBasicController { ThreadLocalUserActivityLogger.log(CourseLoggingAction.CHECKLIST_CHECKBOX_DELETED, getClass(), LoggingResourceable.wrap(courseNode), LoggingResourceable.wrapNonOlatResource(StringResourceableType.checkbox, checkbox.getCheckboxId(), checkbox.getTitle())); } + } else if(downloadFileLink == source) { + doDownloadFile(ureq); + } else if(deleteFileLink == source) { + deleteFile(); } else if(awardPointEl == source) { pointsEl.setVisible(withScore && awardPointEl.isAtLeastSelected(1)); + } else if(fileEl == source) { + String filename = fileEl.getUploadFileName(); + downloadFileLink.setI18nKey(filename); + downloadFileLink.setEnabled(false); } super.formInnerEvent(ureq, source, event); } + + private void deleteFile() { + deleteFile = Boolean.TRUE; + deleteFileCont.setVisible(false); + + String filename = fileEl.getUploadFileName(); + if(filename != null && filename.equals(downloadFileLink.getI18nKey())) { + fileEl.reset(); + } + } + + private void doDownloadFile(UserRequest ureq) { + ICourse course = CourseFactory.loadCourse(courseOres); + CourseEnvironment courseEnv = course.getCourseEnvironment(); + VFSContainer container = checkboxManager.getFileContainer(courseEnv, courseNode, checkbox); + VFSItem item = container.resolve(checkbox.getFilename()); + if(item instanceof VFSLeaf) { + VFSMediaResource rsrc = new VFSMediaResource((VFSLeaf)item); + rsrc.setDownloadable(true); + ureq.getDispatchResult().setResultingMediaResource(rsrc); + } + } } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_content/delete_file.html b/src/main/java/org/olat/course/nodes/cl/ui/_content/delete_file.html new file mode 100644 index 0000000000000000000000000000000000000000..37d190a789fe94ea974ffa0ddb9faeedb78968be --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cl/ui/_content/delete_file.html @@ -0,0 +1 @@ +$r.render("download") $r.render("deleteFile") \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_content/run.html b/src/main/java/org/olat/course/nodes/cl/ui/_content/run.html index 046959022bd78a5c60497a00fb441afbf786bca1..96aa94b3054f28cb71bb4ec39fde72343763479e 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_content/run.html +++ b/src/main/java/org/olat/course/nodes/cl/ui/_content/run.html @@ -4,26 +4,11 @@ <h4>$r.translate("score.title")</h4> <table> <tbody> - <tr> - <td> - $r.translate("score.min"): - </td> - <td> - $scoreMin - </td> - </tr> - <tr> - <td> - $r.translate("score.max"): - </td> - <td> - $scoreMax - </td> - </tr> - <tr> - <td> - $r.translate("score.yourscore"): - </td> + <tr><td>$r.translate("score.min"):</td> + <td>$scoreMin</td></tr> + <tr><td>$r.translate("score.max"):</td> + <td>$scoreMax</td></tr> + <tr><td>$r.translate("score.yourscore"):</td> <td> #if($score) $score @@ -41,19 +26,10 @@ <table> <tbody> #if ($passedCutValue) - <tr> - <td> - $r.translate("passed.cut"): - </td> - <td> - $passedCutValue - </td> - </tr> + <tr><td>$r.translate("passed.cut"):</td> + <td>$passedCutValue</td></tr> #end - <tr> - <td> - $r.translate("passed.yourpassed"): - </td> + <tr><td>$r.translate("passed.yourpassed"):</td> <td> #if($hasPassedValue && $passed == true) <span class="o_passed">$r.translate("passed.yes")</span> @@ -88,7 +64,7 @@ #if($dueDate) <p>$r.translate("run.due.date.desc")</p> - <p class="o_cl_duedate">$r.translate("run.due.date",$r.formatDateAndTime($dueDate))</p> + <p class="o_cl_duedate #if($afterDueDate) o_cl_duedate_passed #end">$r.translate("run.due.date", $r.formatDateAndTime($dueDate))</p> #end <div class="b_clearfix"> diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties index 5d1185e88b80e71103c3c1ab1feb6419dfc7016f..b42ec5b3ef348bee280a5e20096d3493e7863816 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties @@ -30,7 +30,7 @@ pane.tab.accessibility=Zugang pane.tab.clconfig=Konfiguration pane.tab.checkbox=Checkboxen config.title=Konfiguration der Checkliste -config.description=W\u00E4hlen Sie ob die Checkliste bis zu einem bestimmten Datum ausgef\u00FCllt sein muss und ob der Benutzer daf\u00FCr Punkte oder eine Bestanden Information erhalten soll. +config.description=W\u00E4hlen Sie ob die Checkliste bis zu einem bestimmten Datum ausgef\u00FCllt sein muss und ob der Benutzer daf\u00FCr Punkte oder eine Information zum Bestanden-Status erhalten soll. config.due.date=Abgabedatum config.due.date.on=Checkliste bei Abgabedatum f\u00FCr Benutzer sperren config.passed=Bestanden / Nicht bestanden ausgeben @@ -47,10 +47,12 @@ config.comment=Individueller Kommentar config.tip.user=Hinweis f\u00FCr alle Benutzer config.tip.coach=Hinweis f\u00FCr Betreuer config.checkbox.title=$\:pane.tab.checkbox -config.checkbox.description=W\u00E4hlen Sie "$\:add.checkbox" um eine neue Checkbox zu erzeugen oder "$\:edit.checkbox" um eine bestehende Checkbox zu ver\u00E4ndern. -config.warning.inuse=Es gibt schon Benutzer, die eine Checkbox geklickt haben. Eine Anderung der Punkte kann deren Resultate beinflussen. +config.checkbox.description=W\u00E4hlen Sie "$\:add.checkbox" um eine neue Checkbox zu erzeugen oder "$org.olat.core\:edit" um eine bestehende Checkbox zu ver\u00E4ndern. +config.warning.inuse=Es gibt schon Benutzer, die eine Checkbox geklickt haben. Eine \u00C4nderung der Punkte kann deren Resultate beinflussen. checkbox.title=Titel +comment.title=$org.olat.course.nodes.ms\:comment.title +comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment score.title=$org.olat.course.nodes.ms\:score.title score.noscoreinfoyet=$org.olat.course.nodes.ms\:score.noscore score.min=$org.olat.course.nodes.ms\:score.min @@ -60,6 +62,7 @@ passed.title=$org.olat.course.nodes.ms\:passed.title passed.cut=$org.olat.course.nodes.ms\:passed.cut passed.yourpassed=$org.olat.course.nodes.ms\:passed.yourpassed passed.no=$org.olat.course.nodes.ms\:passed.no +passed.nopassed=$org.olat.course.nodes.ms\:passed.nopassed passed.yes=$org.olat.course.nodes.ms\:passed.yes info.title=$org.olat.course.nodes.ms\:info.title @@ -103,7 +106,7 @@ run.mark=Markierung run.info=Information run.due.date=Abgabedatum: <span class="o_cl_duedate">{0}</span> run.due.date.desc=Diese Checkliste ist mit einem Abgabedatum versehen. Nach dem Abgabedatum kann die Checkliste nicht mehr von Ihnen ver\u00E4ndert werden. -coach.desc=In der unten stehenden Listen finden Sie die von Ihnen betreuten Teilnehmer dieses Kurses. Wählen Sie bearbeiten um die Checkbox-Auswahl oder Punkte eines Teilnehmers zu verändern. +coach.desc=In der unten stehenden Listen finden Sie die von Ihnen betreuten Teilnehmer dieses Kurses. Wählen Sie "$\:table.header.edit.checkbox" um die Checkbox-Auswahl oder Punkte eines Teilnehmers zu verändern. coach.due.date.desc=Diese Checkliste ist mit einem Abgabedatum versehen. Als Betreuer sollten Sie Änderungen erst nach Ablauf des Abgabedatums vornehmen. checklist=Checkliste checklist.point.example=Punkte (min: {0} / max: {1}) diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties index 1a1c974c6d96f3c0a26f7545766ec74e264b45ec..4406b9f34d9440284039689d0fa5952a7d83e36f 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties @@ -1,13 +1,19 @@ #Mon Mar 03 11:18:52 CET 2014 add.checkbox=Add checkbox +assessment=Assessment award.point.on=Grant score on check box.check=Check box.points=Score box.points.info=({0} Points) +box.assessment=Edit per checkbox checkbox.title=Title checklist=Checklists checklist.point.example=Points (min\: {0} / max\: {1}) chelp.cl-checkbox.title=Checklist configuration +coach.desc=In the list below you will find all participants of this course coached by you. Select "$\:table.header.edit.checkbox" in order to change check marks or the score of a participant. +coach.due.date.desc=Please notice that this checklist has a deadline. As a coach, you should make changes only after the deadline expiration. +comment.title=$org.olat.course.nodes.ms\:comment.title +comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment condition.accessibility.title=Access config.checkbox.title=$\:pane.tab.checkbox config.comment=Individual comment @@ -26,6 +32,9 @@ config.points.on=Checkboxes total config.tip.coach=Notice for tutors config.tip.user=Notice for all users config.title=Checklist configuration +config.warning.inuse=Users have already checked one or more boxes. Changing scores may affect their results. +config.checkbox.description=Click "$\:add.checkbox" to create a new checkbox or select "$org.olat.core\:edit" to configure an existing checkbox. +config.description=Select whether a deadline applies to the checklist, after which it will be locked for users. Information on passed status or score can be activated here. description=Description file=File filter.all=Show all @@ -46,11 +55,17 @@ passed.no=$org.olat.course.nodes.ms\:passed.no passed.title=$org.olat.course.nodes.ms\:passed.title passed.yes=$org.olat.course.nodes.ms\:passed.yes passed.yourpassed=$org.olat.course.nodes.ms\:passed.yourpassed +pdf.export=PDF overview +pdf.export.checked=PDF marked checkboxes points=Score release=Access release.coachOnly=Coach only release.userAndCoach=Participant and coach run.due.date=Deadline\: <span class\="o_cl_duedate">{0}</span> +run.due.date.desc=Please notice that this checklist has a deadline. You will not be able to make changes to the checklist after the deadline expiration. +run.mark=Check marks +run.run=My checklist +run.coach=Manage checklists score.max=$org.olat.course.nodes.ms\:score.max score.min=$org.olat.course.nodes.ms\:score.min score.noscoreinfoyet=$org.olat.course.nodes.ms\:score.noscore diff --git a/src/main/java/org/olat/course/nodes/iq/IQEditController.java b/src/main/java/org/olat/course/nodes/iq/IQEditController.java index 0600e27a3bd75a6f6366b673e148d1172cca678b..2558f3e48d8f195529587cc1f543da97d352fc3f 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQEditController.java +++ b/src/main/java/org/olat/course/nodes/iq/IQEditController.java @@ -511,10 +511,7 @@ public class IQEditController extends ActivateableTabbableDefaultController impl onyxSuccess = surveyDir.listFiles().length; } } else { - List<QTIResult> results = QTIResultManager.getInstance().selectResults(course.getResourceableId(), courseNode.getIdent(), repKey, 1); - if (results != null && results.size() > 0) { - onyxSuccess = results.size(); - } + onyxSuccess = QTIResultManager.getInstance().countResults(course.getResourceableId(), courseNode.getIdent(), repKey); } } if (moduleConfiguration.get(CONFIG_KEY_TYPE_QTI) != null @@ -524,7 +521,7 @@ public class IQEditController extends ActivateableTabbableDefaultController impl replaceWizard.addControllerListener(this); cmc = new CloseableModalController(getWindowControl(), translate("close"), replaceWizard.getInitialComponent()); } else { - List<QTIResult> results = QTIResultManager.getInstance().selectResults(course.getResourceableId(), courseNode.getIdent(), repKey, 1); + List<QTIResult> results = QTIResultManager.getInstance().selectResults(course.getResourceableId(), courseNode.getIdent(), repKey, null, 1); // test was passed from an user boolean passed = (results != null && results.size() > 0) ? true : false; // test was started and not passed diff --git a/src/main/java/org/olat/course/nodes/iq/IQEditReplaceWizard.java b/src/main/java/org/olat/course/nodes/iq/IQEditReplaceWizard.java index 22b98af4ea1b71a8ef7f5198b57528f7cc5ef860..7a6abea65951f50eeea04f7375f5bea5b10fb03a 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQEditReplaceWizard.java +++ b/src/main/java/org/olat/course/nodes/iq/IQEditReplaceWizard.java @@ -276,7 +276,7 @@ public class IQEditReplaceWizard extends WizardController { }; removeAsListenerAndDispose(mailCtr); - mailCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false); + mailCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false, true); listenTo(mailCtr); vcStep3 = createVelocityContainer("replacewizard_step3"); diff --git a/src/main/java/org/olat/course/nodes/ta/DropboxScoringViewController.java b/src/main/java/org/olat/course/nodes/ta/DropboxScoringViewController.java index 58c3d357af5352f553cf1e411811096e158e2d11..3e3093cb19f943a94f349ccb5737edaf94d64e76 100644 --- a/src/main/java/org/olat/course/nodes/ta/DropboxScoringViewController.java +++ b/src/main/java/org/olat/course/nodes/ta/DropboxScoringViewController.java @@ -170,6 +170,7 @@ public class DropboxScoringViewController extends BasicController { namedReturnbox.setLocalSecurityCallback(getReturnboxVfsSecurityCallback(rootReturnbox.getRelPath())); returnboxFolderRunController = new FolderRunController(namedReturnbox, false, ureq, getWindowControl()); + returnboxFolderRunController.disableSubscriptionController(); listenTo(returnboxFolderRunController); myContent.put("returnbox", returnboxFolderRunController.getInitialComponent()); @@ -201,7 +202,9 @@ public class DropboxScoringViewController extends BasicController { } protected VFSSecurityCallback getReturnboxVfsSecurityCallback(String returnboxRelPath) { - return new ReturnboxFullAccessCallback(returnboxRelPath); + SubscriptionContext subscriptionContext = ReturnboxFileUploadNotificationHandler + .getSubscriptionContext(userCourseEnv.getCourseEnvironment(), node, getIdentity()); + return new ReturnboxFullAccessCallback(returnboxRelPath, subscriptionContext); } /** @@ -402,8 +405,10 @@ class ReadOnlyAndDeleteCallback implements VFSSecurityCallback { class ReturnboxFullAccessCallback implements VFSSecurityCallback { private Quota quota; + private final SubscriptionContext subscriptionContext; - public ReturnboxFullAccessCallback(String relPath) { + public ReturnboxFullAccessCallback(String relPath, SubscriptionContext subscriptionContext) { + this.subscriptionContext = subscriptionContext; QuotaManager qm = QuotaManager.getInstance(); quota = qm.getCustomQuota(relPath); if (quota == null) { // if no custom quota set, use the default quotas... @@ -454,6 +459,6 @@ class ReturnboxFullAccessCallback implements VFSSecurityCallback { * @see org.olat.modules.bc.callbacks.SecurityCallback#getSubscriptionContext() */ public SubscriptionContext getSubscriptionContext() { - return null; + return subscriptionContext; } } diff --git a/src/main/java/org/olat/course/nodes/ta/ReturnboxController.java b/src/main/java/org/olat/course/nodes/ta/ReturnboxController.java index 4227cb297b522048801b3b02ca0f2d6cc23d5c3f..216e0c07e1191703928ee815d9b9f261eac193fb 100644 --- a/src/main/java/org/olat/course/nodes/ta/ReturnboxController.java +++ b/src/main/java/org/olat/course/nodes/ta/ReturnboxController.java @@ -103,7 +103,7 @@ public class ReturnboxController extends BasicController { // notification if ( !previewMode && !ureq.getUserSession().getRoles().isGuestOnly()) { // offer subscription, but not to guests - subsContext = ReturnboxFileUploadNotificationHandler.getSubscriptionContext(userCourseEnv, node, ureq.getIdentity()); + subsContext = ReturnboxFileUploadNotificationHandler.getSubscriptionContext(userCourseEnv.getCourseEnvironment(), node, ureq.getIdentity()); if (subsContext != null) { contextualSubscriptionCtr = AbstractTaskNotificationHandler.createContextualSubscriptionController(ureq, wControl, getReturnboxPathFor( userCourseEnv.getCourseEnvironment(), node,ureq.getIdentity()), subsContext, ReturnboxController.class); diff --git a/src/main/java/org/olat/course/nodes/ta/ReturnboxFileUploadNotificationHandler.java b/src/main/java/org/olat/course/nodes/ta/ReturnboxFileUploadNotificationHandler.java index e301c0a78147940ba7f0327299c1bf28a1b39b85..2a9f209f568ba43cc6c982843cd4ab15537eef6c 100644 --- a/src/main/java/org/olat/course/nodes/ta/ReturnboxFileUploadNotificationHandler.java +++ b/src/main/java/org/olat/course/nodes/ta/ReturnboxFileUploadNotificationHandler.java @@ -33,7 +33,7 @@ import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.course.CourseModule; import org.olat.course.nodes.CourseNode; -import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.course.run.environment.CourseEnvironment; /** * Description:<br> @@ -52,8 +52,8 @@ public class ReturnboxFileUploadNotificationHandler extends AbstractTaskNotifica //empty block } - protected static SubscriptionContext getSubscriptionContext(UserCourseEnvironment userCourseEnv, CourseNode node, Identity identity) { - return CourseModule.createSubscriptionContext(userCourseEnv.getCourseEnvironment(), node, "Returnbox-" + identity.getKey()); + protected static SubscriptionContext getSubscriptionContext(CourseEnvironment courseEnv, CourseNode node, Identity identity) { + return CourseModule.createSubscriptionContext(courseEnv, node, "Returnbox-" + identity.getKey()); } protected String getCssClassIcon() { diff --git a/src/main/java/org/olat/course/nodes/ta/TACourseNodeEditController.java b/src/main/java/org/olat/course/nodes/ta/TACourseNodeEditController.java index d92abd4fb9d0838a625d8a410f0bc7a4a84120c7..77abf5efcaa9711b22561b81f8c426c38e34fed4 100644 --- a/src/main/java/org/olat/course/nodes/ta/TACourseNodeEditController.java +++ b/src/main/java/org/olat/course/nodes/ta/TACourseNodeEditController.java @@ -449,7 +449,7 @@ public class TACourseNodeEditController extends ActivateableTabbableDefaultContr RepositoryEntry repositoryEntry = RepositoryManager.getInstance().lookupRepositoryEntry(course, true); String courseURL = Settings.getServerContextPathURI() + "/url/RepositoryEntry/" + repositoryEntry.getKey(); MailTemplate mailTemplate = this.createTaskDeletedMailTemplate(urequest, course.getCourseTitle(), courseURL, deletedTaskFile); - mailCtr = new MailNotificationEditController(getWindowControl(), urequest, mailTemplate, true, false); + mailCtr = new MailNotificationEditController(getWindowControl(), urequest, mailTemplate, true, false, true); listenTo(mailCtr); cmc = new CloseableModalController(getWindowControl(), translate("close"), mailCtr.getInitialComponent()); listenTo(cmc); diff --git a/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java b/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java index fba7ec95f742902abeb9bd649657c427aefff3cb..ef667860e56ce7b58f7ca8217b181d9314805ded 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupImportExport.java @@ -139,6 +139,8 @@ public class BusinessGroupImportExport { try { Field field = toolsConfig.getClass().getField(availableTools[i]); field.setBoolean(toolsConfig, ct.isToolEnabled(availableTools[i])); + } catch(NoSuchFieldException e) { + //no field to fill (hasOpenMeetings is not set for backwards compatibility) } catch (Exception e) { log.error("", e); } diff --git a/src/main/java/org/olat/ims/qti/QTIChangeLogMessage.java b/src/main/java/org/olat/ims/qti/QTIChangeLogMessage.java index 37f82c2ef8925eb15cd64127f9a04466f15cc5c6..ae51256d2939994467d075751e963a834080b942 100644 --- a/src/main/java/org/olat/ims/qti/QTIChangeLogMessage.java +++ b/src/main/java/org/olat/ims/qti/QTIChangeLogMessage.java @@ -40,7 +40,7 @@ package org.olat.ims.qti; * * @author patrick */ -public class QTIChangeLogMessage implements Comparable{ +public class QTIChangeLogMessage implements Comparable<QTIChangeLogMessage> { private boolean isPublic; private String logMessage; @@ -87,8 +87,8 @@ public class QTIChangeLogMessage implements Comparable{ * * @see java.lang.Comparable#compareTo(java.lang.Object) */ - public int compareTo(Object arg0) { - QTIChangeLogMessage b = (QTIChangeLogMessage)arg0; + @Override + public int compareTo(QTIChangeLogMessage b) { long diff = this.getTimestmp() - b.getTimestmp(); //this ordering makes Arrays.sort(..) to sort the change log messages ascending //whereas ascending means older timestamp before newer timestamp diff --git a/src/main/java/org/olat/ims/qti/QTIResult.java b/src/main/java/org/olat/ims/qti/QTIResult.java index 4cf75293e30ac15efbfa2fe5f54ce9ceb737ef21..92aa342c1e8dd3b6de1777c092b267528781441a 100644 --- a/src/main/java/org/olat/ims/qti/QTIResult.java +++ b/src/main/java/org/olat/ims/qti/QTIResult.java @@ -34,7 +34,9 @@ import org.olat.core.commons.persistence.PersistentObject; * * @author gnaegi */ -public class QTIResult extends PersistentObject{ +public class QTIResult extends PersistentObject{ + + private static final long serialVersionUID = 5999697754463201896L; private QTIResultSet resultSet; @@ -169,4 +171,20 @@ public class QTIResult extends PersistentObject{ this.lastModified = lastModified; } + @Override + public int hashCode() { + return getKey() == null ? 97520 : getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof QTIResult) { + QTIResult result = (QTIResult)obj; + return getKey() != null && getKey().equals(result.getKey()); + } + return false; + } } diff --git a/src/main/java/org/olat/ims/qti/QTIResultManager.java b/src/main/java/org/olat/ims/qti/QTIResultManager.java index 099ea36d5a461c61f86f31ae59d10a82af4b9e43..48f7825641c1a3be7e0234662a873ed4fcb50920 100644 --- a/src/main/java/org/olat/ims/qti/QTIResultManager.java +++ b/src/main/java/org/olat/ims/qti/QTIResultManager.java @@ -27,18 +27,19 @@ package org.olat.ims.qti; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import org.hibernate.type.StandardBasicTypes; -import org.hibernate.type.Type; +import javax.persistence.EntityManager; +import javax.persistence.Query; +import javax.persistence.TypedQuery; + +import org.olat.basesecurity.Group; import org.olat.core.commons.persistence.DB; -import org.olat.core.commons.persistence.DBFactory; import org.olat.core.id.Identity; import org.olat.core.id.UserConstants; +import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; -import org.olat.core.manager.BasicManager; import org.olat.user.UserDataDeletable; /** @@ -46,9 +47,13 @@ import org.olat.user.UserDataDeletable; * * @author Alexander Schneider */ -public class QTIResultManager extends BasicManager implements UserDataDeletable { +public class QTIResultManager implements UserDataDeletable { + + private static final OLog log = Tracing.createLoggerFor(QTIResultManager.class); private static QTIResultManager instance; + + private DB dbInstance; /** * Constructor for QTIResultManager. @@ -63,6 +68,14 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable public static QTIResultManager getInstance() { return instance; } + + /** + * [user by Spring] + * @param dbInstance + */ + public void setDbInstance(DB dbInstance) { + this.dbInstance = dbInstance; + } /** * @param olatResource @@ -71,7 +84,16 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @return True if true, false otherwise. */ public boolean hasResultSets(Long olatResource, String olatResourceDetail, Long repositoryRef) { - return (getResultSets(olatResource, olatResourceDetail, repositoryRef, null).size() > 0); + StringBuilder sb = new StringBuilder(); + sb.append("select count(rset.key) from ").append(QTIResultSet.class.getName()).append(" as rset ") + .append("where rset.olatResource=:resId and rset.olatResourceDetail=:resSubPath and rset.repositoryRef=:repoKey"); + + Number count = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Number.class) + .setParameter("resId", olatResource) + .setParameter("resSubPath", olatResourceDetail) + .setParameter("repoKey", repositoryRef).getSingleResult(); + return count == null ? false : count.intValue() > 0; } /** @@ -83,27 +105,22 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @return List of resultsets */ public List<QTIResultSet> getResultSets(Long olatResource, String olatResourceDetail, Long repositoryRef, Identity identity) { - Long olatRes = olatResource; - String olatResDet = olatResourceDetail; - Long repRef = repositoryRef; - - DB db = DBFactory.getInstance(); - - StringBuilder slct = new StringBuilder(); - slct.append("select rset from "); - slct.append("org.olat.ims.qti.QTIResultSet rset "); - slct.append("where "); - slct.append("rset.olatResource=? "); - slct.append("and rset.olatResourceDetail=? "); - slct.append("and rset.repositoryRef=? "); + StringBuilder sb = new StringBuilder(); + sb.append("select rset from ").append(QTIResultSet.class.getName()).append(" as rset ") + .append("where rset.olatResource=:resId and rset.olatResourceDetail=:resSubPath and rset.repositoryRef=:repoKey"); if (identity != null) { - slct.append("and rset.identity.key=? "); - return db.find(slct.toString(), new Object[] { olatRes, olatResDet, repRef, identity.getKey() }, new Type[] { StandardBasicTypes.LONG, StandardBasicTypes.STRING, - StandardBasicTypes.LONG, StandardBasicTypes.LONG }); - } else { - return db.find(slct.toString(), new Object[] { olatRes, olatResDet, repRef }, new Type[] { StandardBasicTypes.LONG, StandardBasicTypes.STRING, - StandardBasicTypes.LONG }); + sb.append(" and rset.identity.key=:identityKey "); } + + TypedQuery<QTIResultSet> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QTIResultSet.class) + .setParameter("resId", olatResource) + .setParameter("resSubPath", olatResourceDetail) + .setParameter("repoKey", repositoryRef); + if (identity != null) { + query.setParameter("identityKey", identity.getKey()); + } + return query.getResultList(); } /** @@ -114,43 +131,66 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param repositoryRef * @return List of QTIResult objects */ - public List<QTIResult> selectResults(Long olatResource, String olatResourceDetail, Long repositoryRef, int type) { - Long olatRes = olatResource; - String olatResDet = olatResourceDetail; - Long repRef = repositoryRef; - - DB db = DBFactory.getInstance(); - // join with user to sort by name - StringBuilder slct = new StringBuilder(); - slct.append("select res from "); - slct.append("org.olat.ims.qti.QTIResultSet rset, "); - slct.append("org.olat.ims.qti.QTIResult res, "); - slct.append("org.olat.core.id.Identity identity, "); - slct.append("org.olat.user.UserImpl usr "); - slct.append("where "); - slct.append("rset.key = res.resultSet "); - slct.append("and rset.identity = identity.key "); - slct.append("and identity.user = usr.key "); - slct.append("and rset.olatResource=? "); - slct.append("and rset.olatResourceDetail=? "); - slct.append("and rset.repositoryRef=? "); - // 1 -> iqtest, 2 -> iqself - if(type == 1 || type == 2) - slct.append("order by usr.userProperties['").append(UserConstants.LASTNAME).append("'] , rset.assessmentID, res.itemIdent"); + public List<QTIResult> selectResults(Long olatResource, String olatResourceDetail, Long repositoryRef, + List<Group> limitToSecGroups, int type) { + StringBuilder sb = new StringBuilder(); + sb.append("select res from ").append(QTIResult.class.getName()).append(" as res ") + .append(" inner join res.resultSet as rset") + .append(" inner join rset.identity as ident") + .append(" inner join ident.user as usr") + .append(" where rset.olatResource=:resId and rset.olatResourceDetail=:resSubPath and rset.repositoryRef=:repoKey"); + if(limitToSecGroups != null && limitToSecGroups.size() > 0) { + sb.append(" and rset.identity.key in ( select membership.identity.key from bgroupmember membership ") + .append(" where membership.group in (:baseGroups)") + .append(" )"); + } + + if(type == 1 || type == 2) { + // 1 -> iqtest, 2 -> iqself + sb.append(" order by usr.userProperties['").append(UserConstants.LASTNAME).append("'] , rset.assessmentID, res.itemIdent"); + } else { //3 -> iqsurv: the alphabetical assortment above could destroy the anonymization - // if names and quantity of the persons is well-known - else - slct.append("order by rset.creationDate, rset.assessmentID, res.itemIdent"); + // if names and quantity of the persons is well-known + sb.append(" order by rset.creationDate, rset.assessmentID, res.itemIdent"); + } - List results = null; - results = db.find(slct.toString(), new Object[] { olatRes, olatResDet, repRef }, new Type[] { StandardBasicTypes.LONG, StandardBasicTypes.STRING, - StandardBasicTypes.LONG }); + TypedQuery<QTIResult> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), QTIResult.class) + .setParameter("resId", olatResource) + .setParameter("resSubPath", olatResourceDetail) + .setParameter("repoKey", repositoryRef); - return results; + if(limitToSecGroups != null && limitToSecGroups.size() > 0) { + query.setParameter("baseGroups", limitToSecGroups); + } + + return query.getResultList(); + } + + /** + * Same as above but only count the number of results + * @param olatResource + * @param olatResourceDetail + * @param repositoryRef + * @return + */ + public int countResults(Long olatResource, String olatResourceDetail, Long repositoryRef) { + StringBuilder sb = new StringBuilder(); + sb.append("select count(res.key) from ").append(QTIResult.class.getName()).append(" as res ") + .append(" inner join res.resultSet as rset") + .append(" inner join rset.identity as ident") + .append(" inner join ident.user as usr") + .append(" where rset.olatResource=:resId and rset.olatResourceDetail=:resSubPath and rset.repositoryRef=:repoKey"); + + Number count = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Number.class) + .setParameter("resId", olatResource) + .setParameter("resSubPath", olatResourceDetail) + .setParameter("repoKey", repositoryRef) + .getSingleResult(); + return count == null ? 0 : count.intValue(); } /** - * deletes all Results and ResultSets of a test, selftest or survey + * Deletes all Results and ResultSets of a test, selftest or survey * * @param olatRes * @param olatResDet @@ -158,37 +198,28 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @return deleted ResultSets */ public int deleteAllResults(Long olatRes, String olatResDet, Long repRef) { - DB db = DBFactory.getInstance(); - - StringBuilder slct = new StringBuilder(); - slct.append("select rset from "); - slct.append("org.olat.ims.qti.QTIResultSet rset "); - slct.append("where "); - slct.append("rset.olatResource=? "); - slct.append("and rset.olatResourceDetail=? "); - slct.append("and rset.repositoryRef=? "); - - List results = null; - results = db.find(slct.toString(), new Object[] { olatRes, olatResDet, repRef }, new Type[] { StandardBasicTypes.LONG, StandardBasicTypes.STRING, - StandardBasicTypes.LONG }); - - String delRes = "from res in class org.olat.ims.qti.QTIResult where res.resultSet.key = ?"; - String delRset = "from rset in class org.olat.ims.qti.QTIResultSet where rset.key = ?"; - - int deletedRset = 0; + StringBuilder sb = new StringBuilder(); + sb.append("select rset from ").append(QTIResultSet.class.getName()).append(" as rset "); + sb.append(" where rset.olatResource=:resId and rset.olatResourceDetail=:resSubPath and rset.repositoryRef=:repoKey "); + + EntityManager em = dbInstance.getCurrentEntityManager(); + List<QTIResultSet> sets = em.createQuery(sb.toString(), QTIResultSet.class).setParameter("resId", olatRes) + .setParameter("resSubPath", olatResDet) + .setParameter("repoKey", repRef) + .getResultList(); - for (Iterator iter = results.iterator(); iter.hasNext();) { - QTIResultSet rSet = (QTIResultSet) iter.next(); - Long rSetKey = rSet.getKey(); - db.delete(delRes, rSetKey, StandardBasicTypes.LONG); - db.delete(delRset, rSetKey, StandardBasicTypes.LONG); - deletedRset++; + StringBuilder delSb = new StringBuilder(); + delSb.append("delete from ").append(QTIResult.class.getName()).append(" as res where res.resultSet.key=:setKey"); + Query delResults = em.createQuery(delSb.toString()); + for (QTIResultSet set:sets) { + delResults.setParameter("setKey", set.getKey()).executeUpdate(); + em.remove(set); } - return deletedRset; + return sets.size(); } /** - * Deletes all Results and ResultSets for certain QTI-ResultSet. + * Deletes all Results AND all ResultSets for certain QTI-ResultSet. * @param qtiResultSet */ public void deleteResults(QTIResultSet qtiResultSet) { @@ -201,13 +232,13 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param answerCode * @return translation */ - public static Map parseResponseStrAnswers(String answerCode) { + public static Map<String,String> parseResponseStrAnswers(String answerCode) { // calculate the correct answer, if eventually needed int modus = 0; int startIdentPosition = 0; int startCharacterPosition = 0; String tempIdent = null; - Map result = new HashMap(); + Map<String,String> result = new HashMap<String,String>(); char c; for (int i = 0; i < answerCode.length(); i++) { @@ -248,11 +279,11 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param answerCode * @return translation */ - public static List parseResponseLidAnswers(String answerCode) { + public static List<String> parseResponseLidAnswers(String answerCode) { // calculate the correct answer, if eventually needed int modus = 0; int startCharacterPosition = 0; - List result = new ArrayList(); + List<String> result = new ArrayList<String>(); char c; for (int i = 0; i < answerCode.length(); i++) { @@ -287,8 +318,14 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param assessmentID * @return */ - public List findQtiResultSets(Identity identity) { - return DBFactory.getInstance().find("from q in class org.olat.ims.qti.QTIResultSet where q.identity =?", identity.getKey(), StandardBasicTypes.LONG); + public List<QTIResultSet> findQtiResultSets(Identity identity) { + StringBuilder sb = new StringBuilder(); + sb.append("select rset from ").append(QTIResultSet.class.getName()).append(" as rset") + .append(" where rset.identity.key=:identityKey"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QTIResultSet.class) + .setParameter("identityKey", identity.getKey()) + .getResultList(); } /** @@ -296,22 +333,29 @@ public class QTIResultManager extends BasicManager implements UserDataDeletable * @param identity */ public void deleteUserData(Identity identity, String newDeletedUserName) { - List qtiResults = findQtiResultSets(identity); - for (Iterator iter = qtiResults.iterator(); iter.hasNext();) { - deleteResultSet((QTIResultSet)iter.next()); - } - Tracing.logDebug("Delete all QTI result data in db for identity=" + identity, this.getClass()); + List<QTIResultSet> qtiResults = findQtiResultSets(identity); + for (QTIResultSet set:qtiResults) { + deleteResultSet(set); + } + if(log.isDebug()) { + log.debug("Delete all QTI result data in db for identity=" + identity); + } } /** * Delete all qti-results and qti-result-set entry for certain result-set. * @param rSet */ - private void deleteResultSet(QTIResultSet rSet) { - Long rSetKey = rSet.getKey(); - DB db = DBFactory.getInstance(); - db.delete("from res in class org.olat.ims.qti.QTIResult where res.resultSet.key = ?", rSetKey, StandardBasicTypes.LONG); - db.delete("from rset in class org.olat.ims.qti.QTIResultSet where rset.key = ?", rSetKey, StandardBasicTypes.LONG); + public void deleteResultSet(QTIResultSet rSet) { + EntityManager em = dbInstance.getCurrentEntityManager(); + + StringBuilder delResultsSb = new StringBuilder(); + delResultsSb.append("delete from ").append(QTIResult.class.getName()).append(" as res where res.resultSet.key=:setKey"); + em.createQuery(delResultsSb.toString()).setParameter("setKey", rSet.getKey()).executeUpdate(); + + StringBuilder delSetSb = new StringBuilder(); + delSetSb.append("delete from ").append(QTIResultSet.class.getName()).append(" as rset where rset.key=:setKey"); + em.createQuery(delSetSb.toString()).setParameter("setKey", rSet.getKey()).executeUpdate(); } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/_spring/qtiContext.xml b/src/main/java/org/olat/ims/qti/_spring/qtiContext.xml index a02d63b37d678c2f06fab685f06e30360133465d..a9d3775a7f90b2d4802591b4dc3af7eb66161ccc 100644 --- a/src/main/java/org/olat/ims/qti/_spring/qtiContext.xml +++ b/src/main/java/org/olat/ims/qti/_spring/qtiContext.xml @@ -35,6 +35,8 @@ </property> </bean> - <bean id="qtiResultManager" class="org.olat.ims.qti.QTIResultManager"/> + <bean id="qtiResultManager" class="org.olat.ims.qti.QTIResultManager"> + <property name="dbInstance" ref="database"/> + </bean> </beans> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java b/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java index 42a03bd02f35cc434a0c2f1424ea7ed2b44af03f..c664170d8682d1a3ff227d9a79e24584fa81dd19 100644 --- a/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java +++ b/src/main/java/org/olat/ims/qti/editor/QTIEditorMainController.java @@ -93,7 +93,6 @@ import org.olat.course.tree.TreePosition; import org.olat.fileresource.types.FileResource; import org.olat.ims.qti.QTIChangeLogMessage; import org.olat.ims.qti.QTIConstants; -import org.olat.ims.qti.QTIResult; import org.olat.ims.qti.QTIResultManager; import org.olat.ims.qti.editor.beecom.objects.Assessment; import org.olat.ims.qti.editor.beecom.objects.ChoiceQuestion; @@ -257,9 +256,8 @@ public class QTIEditorMainController extends MainLayoutBasicController implement CourseNode courseNode = course.getEditorTreeModel().getCourseNode(ref.getUserdata()); String repositorySoftKey = (String) courseNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_REPOSITORY_SOFTKEY); Long repKey = RepositoryManager.getInstance().lookupRepositoryEntryBySoftkey(repositorySoftKey, true).getKey(); - List<QTIResult> results = QTIResultManager.getInstance().selectResults(course.getResourceableId(), courseNode.getIdent(), repKey, 1); restrictedEdit = ((CoordinatorManager.getInstance().getCoordinator().getLocker().isLocked(course, null)) - || (results != null && results.size() > 0)) ? true : false; + || QTIResultManager.getInstance().countResults(course.getResourceableId(), courseNode.getIdent(), repKey) > 0) ? true : false; } if(restrictedEdit) { break; diff --git a/src/main/java/org/olat/ims/qti/export/QTIArchiveWizardController.java b/src/main/java/org/olat/ims/qti/export/QTIArchiveWizardController.java index 6246846ab609ad3c3feb2712d7e7060d952d5a4f..9a1682ce389be1dce00c5cb259f65f75988c4879 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIArchiveWizardController.java +++ b/src/main/java/org/olat/ims/qti/export/QTIArchiveWizardController.java @@ -123,7 +123,7 @@ public class QTIArchiveWizardController extends BasicController { private File exportDir; private String targetFileName; private int type; - private Map qtiItemConfigs; + private Map<Class<?>, QTIExportItemFormatConfig> qtiItemConfigs; private List<QTIResult> results; private List<QTIItemObject> qtiItemObjectList; private Link showFileButton; @@ -258,7 +258,7 @@ public class QTIArchiveWizardController extends BasicController { // </OLATBPS-498> } } else { - success = hasResultSets(ureq); + success = hasResultSets(); } if (success) { @@ -273,12 +273,12 @@ public class QTIArchiveWizardController extends BasicController { } else { QTIResultManager qrm = QTIResultManager.getInstance(); - results = qrm.selectResults(olatResource, currentCourseNode.getIdent(), repKey, type); + results = qrm.selectResults(olatResource, currentCourseNode.getIdent(), repKey, null, type); QTIResult res0 = results.get(0); qtiItemObjectList = new QTIObjectTreeBuilder().getQTIItemObjectList(new Long(res0.getResultSet().getRepositoryRef())); - qtiItemConfigs = getQTIItemConfigs(); + qtiItemConfigs = getQTIItemConfigs(qtiItemObjectList); if(dummyMode){ finishedVC = createVelocityContainer("finished"); @@ -431,7 +431,7 @@ public class QTIArchiveWizardController extends BasicController { return filename; } - private boolean hasResultSets(UserRequest ureq) { + private boolean hasResultSets() { if (currentCourseNode instanceof IQTESTCourseNode) { type = 1; } else if (currentCourseNode instanceof IQSELFCourseNode) { @@ -454,7 +454,7 @@ public class QTIArchiveWizardController extends BasicController { } } - private String convert2CtrlChars(String source) { + public static String convert2CtrlChars(String source) { if (source == null) return null; StringBuilder sb = new StringBuilder(300); int len = source.length(); @@ -481,59 +481,51 @@ public class QTIArchiveWizardController extends BasicController { return sb.toString(); } - private Map getQTIItemConfigs(){ - Map itConfigs = new HashMap(); + public static Map<Class<?>, QTIExportItemFormatConfig> getQTIItemConfigs(List<QTIItemObject> itemList){ + Map<Class<?>, QTIExportItemFormatConfig> itConfigs = new HashMap<>(); - for (Iterator iter = qtiItemObjectList.iterator(); iter.hasNext();) { - QTIItemObject item = (QTIItemObject) iter.next(); + for (Iterator<QTIItemObject> iter = itemList.iterator(); iter.hasNext();) { + QTIItemObject item = iter.next(); if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_SCQ)){ if (itConfigs.get(QTIExportSCQItemFormatConfig.class) == null){ QTIExportSCQItemFormatConfig confSCQ = new QTIExportSCQItemFormatConfig(true, false, false, false); - itConfigs.put(QTIExportSCQItemFormatConfig.class, confSCQ); + itConfigs.put(QTIExportSCQItemFormatConfig.class, confSCQ); } - } - else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_MCQ)){ + } else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_MCQ)){ if (itConfigs.get(QTIExportMCQItemFormatConfig.class) == null){ QTIExportMCQItemFormatConfig confMCQ = new QTIExportMCQItemFormatConfig(true, false, false, false); - itConfigs.put(QTIExportMCQItemFormatConfig.class, confMCQ ); + itConfigs.put(QTIExportMCQItemFormatConfig.class, confMCQ ); } - } - else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_KPRIM)){ + } else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_KPRIM)){ if (itConfigs.get(QTIExportKPRIMItemFormatConfig.class) == null){ QTIExportKPRIMItemFormatConfig confKPRIM = new QTIExportKPRIMItemFormatConfig(true, false, false, false); - itConfigs.put(QTIExportKPRIMItemFormatConfig.class, confKPRIM); + itConfigs.put(QTIExportKPRIMItemFormatConfig.class, confKPRIM); } - } - else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_ESSAY)){ + } else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_ESSAY)){ if (itConfigs.get(QTIExportEssayItemFormatConfig.class) == null){ QTIExportEssayItemFormatConfig confEssay = new QTIExportEssayItemFormatConfig(true, false); - itConfigs.put(QTIExportEssayItemFormatConfig.class, confEssay); + itConfigs.put(QTIExportEssayItemFormatConfig.class, confEssay); } - } - else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_FIB)){ + } else if (item.getItemIdent().startsWith(ItemParser.ITEM_PREFIX_FIB)){ if (itConfigs.get(QTIExportFIBItemFormatConfig.class) == null){ QTIExportFIBItemFormatConfig confFIB = new QTIExportFIBItemFormatConfig(true, false, false); - itConfigs.put(QTIExportFIBItemFormatConfig.class, confFIB); + itConfigs.put(QTIExportFIBItemFormatConfig.class, confFIB); } } //if cannot find the type via the ItemParser, look for the QTIItemObject type else if (item.getItemType().equals(QTIItemObject.TYPE.A)){ QTIExportEssayItemFormatConfig confEssay = new QTIExportEssayItemFormatConfig(true, false); - itConfigs.put(QTIExportEssayItemFormatConfig.class, confEssay); - } - else if (item.getItemType().equals(QTIItemObject.TYPE.R)){ + itConfigs.put(QTIExportEssayItemFormatConfig.class, confEssay); + } else if (item.getItemType().equals(QTIItemObject.TYPE.R)){ QTIExportSCQItemFormatConfig confSCQ = new QTIExportSCQItemFormatConfig(true, false, false, false); - itConfigs.put(QTIExportSCQItemFormatConfig.class, confSCQ); - } - else if (item.getItemType().equals(QTIItemObject.TYPE.C)){ + itConfigs.put(QTIExportSCQItemFormatConfig.class, confSCQ); + } else if (item.getItemType().equals(QTIItemObject.TYPE.C)){ QTIExportMCQItemFormatConfig confMCQ = new QTIExportMCQItemFormatConfig(true, false, false, false); - itConfigs.put(QTIExportMCQItemFormatConfig.class, confMCQ ); - } - else if (item.getItemType().equals(QTIItemObject.TYPE.B)){ + itConfigs.put(QTIExportMCQItemFormatConfig.class, confMCQ ); + } else if (item.getItemType().equals(QTIItemObject.TYPE.B)){ QTIExportFIBItemFormatConfig confFIB = new QTIExportFIBItemFormatConfig(true, false, false); - itConfigs.put(QTIExportFIBItemFormatConfig.class, confFIB); - } - else{ + itConfigs.put(QTIExportFIBItemFormatConfig.class, confFIB); + } else { throw new OLATRuntimeException(null,"Can not resolve QTIItem type", null); } } diff --git a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType1.java b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType1.java index 38e590b000ac9ddf30ed7e4e511ca8be66590861..9bf5738c324f1f312d2982f5dc055fe719239313 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType1.java +++ b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType1.java @@ -118,12 +118,12 @@ public class QTIExportFormatterCSVType1 extends QTIExportFormatter { hR1.append(emb); if (qeif.getExportItemConfig(item).hasResponseCols()) { - List responseColumnHeaders = item.getResponseColumnHeaders(); - for (Iterator iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { + List<String> responseColumnHeaders = item.getResponseColumnHeaders(); + for (Iterator<String> iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { // HeaderRow1 hR1.append(sep); // HeaderRow2 - String columnHeader = (String) iterator.next(); + String columnHeader = iterator.next(); hR2.append(i); hR2.append("_"); hR2.append(columnHeader); @@ -231,14 +231,14 @@ public class QTIExportFormatterCSVType1 extends QTIExportFormatter { } public void visit(QTIExportItem eItem) { - List responseColumns = eItem.getResponseColumns(); + List<String> responseColumns = eItem.getResponseColumns(); QTIExportItemFormatConfig itemFormatConfig = eItem.getConfig(); if (displayItem(itemFormatConfig)) { if (itemFormatConfig.hasResponseCols()) { - for (Iterator iter = responseColumns.iterator(); iter.hasNext();) { - String responseColumn = (String) iter.next(); + for (Iterator<String> iter = responseColumns.iterator(); iter.hasNext();) { + String responseColumn = iter.next(); sb.append(emb); sb.append(escape(responseColumn)); sb.append(emb); @@ -337,7 +337,7 @@ public class QTIExportFormatterCSVType1 extends QTIExportFormatter { sb.append(car); // CELFI#107 END - List responseLabelMaterials = element.getResponseLabelMaterials(); + List<String> responseLabelMaterials = element.getResponseLabelMaterials(); for (int i = 0; i < element.getResponseIdentifier().size(); i++) { sb.append(sep + sep); diff --git a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType2.java b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType2.java index 3b78ebb91f712140552f5e5402c0b452147674e7..fdbf68f78eae9d3ae180990e9779c45e4b4421ae 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType2.java +++ b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType2.java @@ -98,8 +98,8 @@ public class QTIExportFormatterCSVType2 extends QTIExportFormatter { StringBuilder hR2 = new StringBuilder(); int i = 1; - for (Iterator iter = qtiItemObjectList.iterator(); iter.hasNext();) { - QTIItemObject item = (QTIItemObject) iter.next(); + for (Iterator<QTIItemObject> iter = qtiItemObjectList.iterator(); iter.hasNext();) { + QTIItemObject item = iter.next(); if (displayItem(qeif.getExportItemConfig(item))) { hR1.append(emb); @@ -120,12 +120,12 @@ public class QTIExportFormatterCSVType2 extends QTIExportFormatter { hR1.append(emb); if (qeif.getExportItemConfig(item).hasResponseCols()) { - List responseColumnHeaders = item.getResponseColumnHeaders(); - for (Iterator iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { + List<String> responseColumnHeaders = item.getResponseColumnHeaders(); + for (Iterator<String> iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { // HeaderRow1 hR1.append(sep); // HeaderRow2 - String columnHeader = (String) iterator.next(); + String columnHeader = iterator.next(); hR2.append(i); hR2.append("_"); hR2.append(columnHeader); @@ -221,13 +221,13 @@ public class QTIExportFormatterCSVType2 extends QTIExportFormatter { } public void visit(QTIExportItem eItem) { - List responseColumns = eItem.getResponseColumns(); + List<String> responseColumns = eItem.getResponseColumns(); QTIExportItemFormatConfig itemFormatConfig = eItem.getConfig(); if (displayItem(itemFormatConfig)) { if (itemFormatConfig.hasResponseCols()) { - for (Iterator iter = responseColumns.iterator(); iter.hasNext();) { - String responseColumn = (String) iter.next(); + for (Iterator<String> iter = responseColumns.iterator(); iter.hasNext();) { + String responseColumn = iter.next(); sb.append(emb); sb.append(escape(responseColumn)); sb.append(emb); @@ -285,8 +285,8 @@ public class QTIExportFormatterCSVType2 extends QTIExportFormatter { sb.append(legend); sb.append(car + car); int y = 1; - for (Iterator iter = qtiItemObjectList.iterator(); iter.hasNext();) { - QTIItemObject element = (QTIItemObject) iter.next(); + for (Iterator<QTIItemObject> iter = qtiItemObjectList.iterator(); iter.hasNext();) { + QTIItemObject element = iter.next(); sb.append(element.getItemIdent()); sb.append(sep); @@ -325,7 +325,7 @@ public class QTIExportFormatterCSVType2 extends QTIExportFormatter { sb.append(car); // CELFI#107 END - List responseLabelMaterials = element.getResponseLabelMaterials(); + List<String> responseLabelMaterials = element.getResponseLabelMaterials(); for (int i = 0; i < element.getResponseIdentifier().size(); i++) { sb.append(sep + sep); diff --git a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType3.java b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType3.java index 53503c69490b1c82f8baaee802d5b6ed56425d5c..698d03274d374ff5596d886dbd297b7d45a2d83f 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType3.java +++ b/src/main/java/org/olat/ims/qti/export/QTIExportFormatterCSVType3.java @@ -121,12 +121,12 @@ public class QTIExportFormatterCSVType3 extends QTIExportFormatter{ hR1.append(emb); if (qeif.getExportItemConfig(item).hasResponseCols()){ - List responseColumnHeaders = item.getResponseColumnHeaders(); - for (Iterator iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { + List<String> responseColumnHeaders = item.getResponseColumnHeaders(); + for (Iterator<String> iterator = responseColumnHeaders.iterator(); iterator.hasNext();) { // HeaderRow1 hR1.append(sep); // HeaderRow2 - String columnHeader = (String) iterator.next(); + String columnHeader = iterator.next(); hR2.append(i); hR2.append("_"); hR2.append(columnHeader); @@ -198,13 +198,13 @@ public class QTIExportFormatterCSVType3 extends QTIExportFormatter{ } public void visit(QTIExportItem eItem) { - List responseColumns = eItem.getResponseColumns(); + List<String> responseColumns = eItem.getResponseColumns(); QTIExportItemFormatConfig itemFormatConfig = eItem.getConfig(); if(displayItem(itemFormatConfig)){ if (itemFormatConfig.hasResponseCols()){ - for (Iterator iter = responseColumns.iterator(); iter.hasNext();) { - String responseColumn = (String) iter.next(); + for (Iterator<String> iter = responseColumns.iterator(); iter.hasNext();) { + String responseColumn = iter.next(); sb.append(emb); sb.append(escape(responseColumn)); sb.append(emb); @@ -284,7 +284,7 @@ public class QTIExportFormatterCSVType3 extends QTIExportFormatter{ sb.append(car); // CELFI#107 END - List responseLabelMaterials = element.getResponseLabelMaterials(); + List<String> responseLabelMaterials = element.getResponseLabelMaterials(); for (int i = 0; i < element.getResponseIdentifier().size() ; i++) { sb.append(sep+sep); diff --git a/src/main/java/org/olat/ims/qti/export/QTIExportItem.java b/src/main/java/org/olat/ims/qti/export/QTIExportItem.java index a2ca4db785a92cad555b7bbfa9ed1cff798d3142..951ec0413b85328164822ade3fd4f33eb542f865 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIExportItem.java +++ b/src/main/java/org/olat/ims/qti/export/QTIExportItem.java @@ -48,8 +48,8 @@ public class QTIExportItem { this.item = item; } - public List getResponseColumns(){ - return this.item.getResponseColumns(this.qtir); + public List<String> getResponseColumns(){ + return item.getResponseColumns(qtir); } public boolean hasResult(){ diff --git a/src/main/java/org/olat/ims/qti/export/QTIExportManager.java b/src/main/java/org/olat/ims/qti/export/QTIExportManager.java index e2afdcbcd8ef5d164e6356f13c2e8905c5910bf4..fd1359a563bf038a8b00f6120e70f5182ce79474 100644 --- a/src/main/java/org/olat/ims/qti/export/QTIExportManager.java +++ b/src/main/java/org/olat/ims/qti/export/QTIExportManager.java @@ -27,11 +27,11 @@ package org.olat.ims.qti.export; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; @@ -91,7 +91,7 @@ public class QTIExportManager extends BasicManager{ Long testReKey, File exportDirectory, String charset, String fileNameSuffix) { boolean resultsFoundAndExported = false; QTIResultManager qrm = QTIResultManager.getInstance(); - List<QTIResult> results = qrm.selectResults(courseResId, olatResourceDetail, testReKey, qef.getType()); + List<QTIResult> results = qrm.selectResults(courseResId, olatResourceDetail, testReKey, null, qef.getType()); if(results.size() > 0){ QTIResult res0 = results.get(0); List<QTIItemObject> qtiItemObjectList = new QTIObjectTreeBuilder().getQTIItemObjectList(new Long(res0.getResultSet().getRepositoryRef())); @@ -106,11 +106,11 @@ public class QTIExportManager extends BasicManager{ } public boolean selectAndExportResults(QTIExportFormatter qef, Long courseResId, String shortTitle, - String olatResourceDetail, RepositoryEntry testRe, ZipOutputStream exportStream, String charset, + String olatResourceDetail, RepositoryEntry testRe, ZipOutputStream exportStream, String fileNameSuffix) throws IOException { boolean resultsFoundAndExported = false; QTIResultManager qrm = QTIResultManager.getInstance(); - List<QTIResult> results = qrm.selectResults(courseResId, olatResourceDetail, testRe.getKey(), qef.getType()); + List<QTIResult> results = qrm.selectResults(courseResId, olatResourceDetail, testRe.getKey(), null, qef.getType()); if(results.size() > 0){ List<QTIItemObject> qtiItemObjectList = new QTIObjectTreeBuilder().getQTIItemObjectList(testRe); qef.setQTIItemObjectList(qtiItemObjectList); @@ -138,17 +138,27 @@ public class QTIExportManager extends BasicManager{ * @param fileNameSuffix * @return */ - public String exportResults(QTIExportFormatter qef, List<QTIResult> results, List<QTIItemObject> qtiItemObjectList, String shortTitle, File exportDirectory, String charset, String fileNameSuffix) { - String targetFileName = null; - - qef.setQTIItemObjectList(qtiItemObjectList); - if (results.size() > 0) { - createContentOfExportFile(results,qtiItemObjectList,qef); - targetFileName = writeContentToFile(shortTitle, exportDirectory, charset, qef, fileNameSuffix); - } - return targetFileName; + public String exportResults(QTIExportFormatter qef, List<QTIResult> results, List<QTIItemObject> qtiItemObjectList, String shortTitle, File exportDirectory, String charset, String fileNameSuffix) { + String targetFileName = null; + + qef.setQTIItemObjectList(qtiItemObjectList); + if (results.size() > 0) { + createContentOfExportFile(results,qtiItemObjectList,qef); + targetFileName = writeContentToFile(shortTitle, exportDirectory, charset, qef, fileNameSuffix); } + return targetFileName; + } + public void exportResults(QTIExportFormatter qef, List<QTIResult> results, List<QTIItemObject> qtiItemObjectList, + OutputStream exportStream) + throws IOException { + qef.setQTIItemObjectList(qtiItemObjectList); + if (results.size() > 0) { + createContentOfExportFile(results,qtiItemObjectList,qef); + IOUtils.write(qef.getReport(), exportStream); + } + } + /** * @param locale Locale used for export file headers / default values @@ -158,7 +168,6 @@ public class QTIExportManager extends BasicManager{ * @return String */ private void createContentOfExportFile(List<QTIResult> qtiResults, List<QTIItemObject> qtiItemObjectList, QTIExportFormatter qef) { - qef.openReport(); //formatter has information about how to format the different qti objects @@ -167,12 +176,8 @@ public class QTIExportManager extends BasicManager{ while (qtiResults.size() > 0){ List<QTIResult> assessIDresults = stripNextAssessID(qtiResults); - qef.openResultSet(new QTIExportSet(assessIDresults.get(0))); - - for (Iterator<QTIItemObject> iter = qtiItemObjectList.iterator(); iter.hasNext();) { - QTIItemObject element = iter.next(); - + for(QTIItemObject element:qtiItemObjectList) { QTIResult qtir = element.extractQTIResult(assessIDresults); qef.visit(qeif.getExportItem(qtir,element)); } diff --git a/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseLid.java b/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseLid.java index 3782d9f479d8330a0ed3f24214c847c5466da2ab..6e9aa5d9786c25ca3077789437763eade62db377 100644 --- a/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseLid.java +++ b/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseLid.java @@ -52,10 +52,10 @@ public class ItemWithResponseLid implements QTIItemObject { private String questionText = ""; // CELFI#107 END - private String positionsOfResponses = null; - private List<String> responseColumnHeaders = new ArrayList<String>(5); - private List<String> responseLabelIdents = new ArrayList<String>(5); - private List<String> responseLabelMaterials = new ArrayList<String>(5); + private String positionsOfResponses = null; + private List<String> responseColumnHeaders = new ArrayList<String>(5); + private List<String> responseLabelIdents = new ArrayList<String>(5); + private List<String> responseLabelMaterials = new ArrayList<String>(5); /** * Constructor for ItemWithResponseLid. @@ -116,7 +116,7 @@ public class ItemWithResponseLid implements QTIItemObject { /** * @see org.olat.ims.qti.export.helper.QTIItemObject#extractQTIResult(java.util.List) */ - public QTIResult extractQTIResult(List resultSet) { + public QTIResult extractQTIResult(List<QTIResult> resultSet) { for (Iterator<QTIResult> iter = resultSet.iterator(); iter.hasNext();) { QTIResult element = iter.next(); if (element.getItemIdent().equals(itemIdent)) { diff --git a/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseStr.java b/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseStr.java index 398959afed02812930dca2aba29091526b0552ea..971423a8ecef7ed2a70e587888b9cfcb6853354c 100644 --- a/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseStr.java +++ b/src/main/java/org/olat/ims/qti/export/helper/ItemWithResponseStr.java @@ -51,11 +51,11 @@ public class ItemWithResponseStr implements QTIItemObject { private String quetionText = ""; // CELFI#107 END - private List responseColumnHeaders = new ArrayList(5); - private List responseStrIdents = new ArrayList(5); + private List<String> responseColumnHeaders = new ArrayList<>(5); + private List<String> responseStrIdents = new ArrayList<>(5); // CELFI#107 - private List responseLabelMaterials = new ArrayList(5); + private List<String> responseLabelMaterials = new ArrayList<>(5); // CELFI#107 END @@ -125,9 +125,9 @@ public class ItemWithResponseStr implements QTIItemObject { /** * @see org.olat.ims.qti.export.helper.QTIItemObject#extractQTIResult(java.util.List) */ - public QTIResult extractQTIResult(List resultSet) { - for (Iterator iter = resultSet.iterator(); iter.hasNext();) { - QTIResult element = (QTIResult) iter.next(); + public QTIResult extractQTIResult(List<QTIResult> resultSet) { + for (Iterator<QTIResult> iter = resultSet.iterator(); iter.hasNext();) { + QTIResult element = iter.next(); if (element.getItemIdent().equals(itemIdent)) { resultSet.remove(element); return element; @@ -136,7 +136,7 @@ public class ItemWithResponseStr implements QTIItemObject { return null; } - private void addTextAndTabs(List responseColumns, String s, int num) { + private void addTextAndTabs(List<String> responseColumns, String s, int num) { for (int i = 0; i < num; i++) { responseColumns.add(s); } @@ -154,15 +154,15 @@ public class ItemWithResponseStr implements QTIItemObject { return this.quetionText; } - public List getResponseColumnHeaders() { + public List<String> getResponseColumnHeaders() { return responseColumnHeaders; } /** * @see org.olat.ims.qti.export.helper.QTIItemObject#getResponseColumns(org.olat.ims.qti.QTIResult) */ - public List getResponseColumns(QTIResult qtiresult) { - List responseColumns = new ArrayList(); + public List<String> getResponseColumns(QTIResult qtiresult) { + List<String> responseColumns = new ArrayList<String>(); if (qtiresult == null) { // item has not been choosen addTextAndTabs(responseColumns, "", getNumColumnHeaders()); @@ -170,9 +170,9 @@ public class ItemWithResponseStr implements QTIItemObject { String answer = qtiresult.getAnswer(); if (answer.length() == 0) addTextAndTabs(responseColumns, ".", getNumColumnHeaders()); else { - Map answerMap = QTIResultManager.parseResponseStrAnswers(answer); + Map<String,String> answerMap = QTIResultManager.parseResponseStrAnswers(answer); - for (Iterator iter = responseStrIdents.iterator(); iter.hasNext();) { + for (Iterator<String> iter = responseStrIdents.iterator(); iter.hasNext();) { String element = (String) iter.next(); if (answerMap.containsKey(element)) { responseColumns.add(answerMap.get(element)); @@ -193,11 +193,11 @@ public class ItemWithResponseStr implements QTIItemObject { /** * @see org.olat.ims.qti.export.helper.QTIItemObject#getResponseIdentifier() */ - public List getResponseIdentifier() { + public List<String> getResponseIdentifier() { return responseStrIdents; } - public List getResponseLabelMaterials() { + public List<String> getResponseLabelMaterials() { // CELFI#107 return responseLabelMaterials; } diff --git a/src/main/java/org/olat/ims/qti/export/helper/QTIItemObject.java b/src/main/java/org/olat/ims/qti/export/helper/QTIItemObject.java index 537760b75f5b3eafdcb0812abb869b3abc62f755..490b5ca19911000cfb08403cf9bb71535eaef962 100644 --- a/src/main/java/org/olat/ims/qti/export/helper/QTIItemObject.java +++ b/src/main/java/org/olat/ims/qti/export/helper/QTIItemObject.java @@ -46,7 +46,7 @@ public interface QTIItemObject { * @param resultSet * @return */ - public QTIResult extractQTIResult(List resultSet); + public QTIResult extractQTIResult(List<QTIResult> resultSet); /** * @return @@ -75,13 +75,13 @@ public interface QTIItemObject { * * @return List responseColumnHeaders */ - public List getResponseColumnHeaders(); + public List<String> getResponseColumnHeaders(); /** * * @return List responseColumns */ - public List getResponseColumns(QTIResult qtiresult); + public List<String> getResponseColumns(QTIResult qtiresult); /** * @return String @@ -99,14 +99,14 @@ public interface QTIItemObject { * - in case of ItemWithResponseStr --> response_str ident * - in case of ItemWithResponseLid --> response_label ident */ - public List getResponseIdentifier(); + public List<String> getResponseIdentifier(); /** * * @return Null, if the item has no material, otherwise a list of materials */ - public List getResponseLabelMaterials(); + public List<String> getResponseLabelMaterials(); /** * diff --git a/src/main/java/org/olat/ims/qti/export/helper/QTIObjectTreeBuilder.java b/src/main/java/org/olat/ims/qti/export/helper/QTIObjectTreeBuilder.java index ea79cb9942dc1f97a44bd12f76eff07836d546d9..195365fe63e936aeb66fb6038c8099973f341acb 100644 --- a/src/main/java/org/olat/ims/qti/export/helper/QTIObjectTreeBuilder.java +++ b/src/main/java/org/olat/ims/qti/export/helper/QTIObjectTreeBuilder.java @@ -95,7 +95,7 @@ public class QTIObjectTreeBuilder { return getQTIItemObjectList(resolver); } - private final List<QTIItemObject> getQTIItemObjectList(Resolver resolver) { + public final List<QTIItemObject> getQTIItemObjectList(Resolver resolver) { Document doc = resolver.getQTIDocument(); Element root = doc.getRootElement(); @SuppressWarnings("unchecked") diff --git a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java index 91a0b03e68ac96384728e1118119c5069ccdccb4..44954dc3efa197d83f3617193bc40e40c1c1ebb6 100644 --- a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java +++ b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticResourceResult.java @@ -28,6 +28,8 @@ import org.olat.core.gui.components.tree.TreeModel; import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.resource.OresHelper; import org.olat.course.nodes.CourseNodeConfiguration; import org.olat.course.nodes.CourseNodeFactory; import org.olat.course.nodes.IQSELFCourseNode; @@ -47,8 +49,6 @@ import org.olat.ims.qti.process.ImsRepositoryResolver; import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.ui.QTI12AssessmentStatisticsController; import org.olat.ims.qti.statistics.ui.QTI12ItemStatisticsController; -import org.olat.ims.qti.statistics.ui.QTI12SurveyItemStatisticsController; -import org.olat.ims.qti.statistics.ui.QTI12SurveyStatisticsController; import org.olat.ims.qti.statistics.ui.QTI21OnyxAssessmentStatisticsController; import org.olat.repository.RepositoryEntry; @@ -62,6 +62,7 @@ import de.bps.onyx.plugin.OnyxModule; public class QTIStatisticResourceResult implements StatisticResourceResult { private final QTICourseNode courseNode; + private final OLATResourceable courseOres; private final QTIStatisticsManager qtiStatisticsManager; private StatisticAssessment statisticAssessment; @@ -72,9 +73,10 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { private QTIType type; - public QTIStatisticResourceResult(QTICourseNode courseNode, QTIStatisticSearchParams searchParams) { + public QTIStatisticResourceResult(OLATResourceable courseOres, QTICourseNode courseNode, QTIStatisticSearchParams searchParams) { this.courseNode = courseNode; this.searchParams = searchParams; + this.courseOres = OresHelper.clone(courseOres); qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); qtiRepositoryEntry = courseNode.getReferencedRepositoryEntry(); @@ -99,6 +101,10 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { return type; } + public OLATResourceable getCourseOres() { + return courseOres; + } + public QTICourseNode getTestCourseNode() { return courseNode; } @@ -111,6 +117,10 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { return qtiDocument; } + public String getMediaBaseURL() { + return getResolver().getStaticsBaseURI() + "/"; + } + public QTIStatisticSearchParams getSearchParams() { return searchParams; } @@ -156,9 +166,7 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { private Controller createAssessmentController(UserRequest ureq, WindowControl wControl, boolean printMode) { Controller ctrl; - if(type == QTIType.survey) { - ctrl = new QTI12SurveyStatisticsController(ureq, wControl, this, printMode); - } else if (type == QTIType.onyx){ + if (type == QTIType.onyx){ ctrl = new QTI21OnyxAssessmentStatisticsController(ureq, wControl, this, printMode); } else { ctrl = new QTI12AssessmentStatisticsController(ureq, wControl, this, printMode); @@ -171,7 +179,7 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { private Controller createItemController(UserRequest ureq, WindowControl wControl, Item item, boolean printMode) { if(type == QTIType.survey) { - return new QTI12SurveyItemStatisticsController(ureq, wControl, item, this, printMode); + return new QTI12ItemStatisticsController(ureq, wControl, item, this, printMode); } else { return new QTI12ItemStatisticsController(ureq, wControl, item, this, printMode); } @@ -184,6 +192,9 @@ public class QTIStatisticResourceResult implements StatisticResourceResult { rootNode.addChild(sectionNode); for (Item item : section.getItems()) { GenericTreeNode itemNode = new ItemNode(item); + if(sectionNode.getDelegate() == null) { + sectionNode.setDelegate(itemNode); + } itemNode.setUserObject(item); sectionNode.addChild(itemNode); } diff --git a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsManager.java b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsManager.java index 138211a91d86ce2807014a2464ad2af336493bd8..5ceb1493c9114f424e501c17259e1c8442bcbd59 100644 --- a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsManager.java +++ b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsManager.java @@ -23,15 +23,16 @@ package org.olat.ims.qti.statistics; import java.util.List; import org.olat.ims.qti.editor.beecom.objects.Item; -import org.olat.ims.qti.statistics.model.StatisticsItem; import org.olat.ims.qti.statistics.model.QTIStatisticResult; import org.olat.ims.qti.statistics.model.QTIStatisticResultSet; import org.olat.ims.qti.statistics.model.StatisticAnswerOption; import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.model.StatisticChoiceOption; +import org.olat.ims.qti.statistics.model.StatisticFIBOption; import org.olat.ims.qti.statistics.model.StatisticItem; import org.olat.ims.qti.statistics.model.StatisticKPrimOption; import org.olat.ims.qti.statistics.model.StatisticSurveyItem; +import org.olat.ims.qti.statistics.model.StatisticsItem; /** * @@ -93,6 +94,8 @@ public interface QTIStatisticsManager { */ public List<StatisticAnswerOption> getStatisticAnswerOptionsOfItem(String itemIdent, QTIStatisticSearchParams searchParams); + public List<StatisticFIBOption> getStatisticAnswerOptionsFIB(Item itemIdent, QTIStatisticSearchParams searchParams); + /** * * @param itemIdent diff --git a/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsResource.java b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..80f6004697c548487acef4aeb3e6ec760a4b104d --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/QTIStatisticsResource.java @@ -0,0 +1,146 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.statistics; + +import java.io.InputStream; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import javax.servlet.http.HttpServletResponse; + +import org.olat.basesecurity.Group; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.CourseNode; +import org.olat.ims.qti.QTIResult; +import org.olat.ims.qti.QTIResultManager; +import org.olat.ims.qti.export.QTIArchiveWizardController; +import org.olat.ims.qti.export.QTIExportFormatter; +import org.olat.ims.qti.export.QTIExportFormatterCSVType1; +import org.olat.ims.qti.export.QTIExportFormatterCSVType2; +import org.olat.ims.qti.export.QTIExportManager; +import org.olat.ims.qti.export.helper.QTIItemObject; +import org.olat.ims.qti.export.helper.QTIObjectTreeBuilder; + +/** + * + * Initial date: 17.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTIStatisticsResource implements MediaResource { + + private static final OLog log = Tracing.createLoggerFor(QTIStatisticsResource.class); + + private final Locale locale; + private final String encoding = "UTF-8"; + + private final QTIStatisticResourceResult resourceResult; + + public QTIStatisticsResource(QTIStatisticResourceResult resourceResult, Locale locale) { + this.resourceResult = resourceResult; + this.locale = locale; + } + + @Override + public String getContentType() { + return "application/zip"; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public InputStream getInputStream() { + return null; + } + + @Override + public Long getLastModified() { + return null; + } + + @Override + public void prepare(HttpServletResponse hres) { + try { + hres.setCharacterEncoding(encoding); + } catch (Exception e) { + log.error("", e); + } + CourseNode courseNode = resourceResult.getTestCourseNode(); + String label = courseNode.getType() + "_" + + StringHelper.transformDisplayNameToFileSystemName(courseNode.getShortName()) + + "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())) + + ".csv"; + String urlEncodedLabel = StringHelper.urlEncodeUTF8(label); + hres.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + urlEncodedLabel); + hres.setHeader("Content-Description", urlEncodedLabel); + + try { + String sep = "\\t"; // fields separated by + String emb = "\""; // fields embedded by + String esc = "\\"; // fields escaped by + String car = "\\r\\n"; // carriage return + + sep = QTIArchiveWizardController.convert2CtrlChars(sep); + car = QTIArchiveWizardController.convert2CtrlChars(car); + + int exportType = 1; + QTIExportFormatter formatter; + if (QTIType.test.equals(resourceResult.getType())){ + exportType = 1; + formatter = new QTIExportFormatterCSVType1(locale, sep, emb, esc, car, true); + } else if (QTIType.survey.equals(resourceResult.getType())) { + exportType = 2; + formatter = new QTIExportFormatterCSVType2(locale, null, sep, emb, esc, car, true); + } else { + return; + } + + Long qtiRepoEntryKey = resourceResult.getQTIRepositoryEntry().getKey(); + List<QTIItemObject> itemList = new QTIObjectTreeBuilder().getQTIItemObjectList(resourceResult.getResolver()); + formatter.setMapWithExportItemConfigs(QTIArchiveWizardController.getQTIItemConfigs(itemList)); + + QTIResultManager qrm = QTIResultManager.getInstance(); + + QTIStatisticSearchParams params = resourceResult.getSearchParams(); + List<Group> limitToGroups = params.isMayViewAllUsersAssessments() + ? null : params.getLimitToGroups(); + + List<QTIResult> results = qrm.selectResults(resourceResult.getCourseOres().getResourceableId(), + courseNode.getIdent(), qtiRepoEntryKey, limitToGroups, exportType); + + QTIExportManager.getInstance().exportResults(formatter, results, itemList, hres.getOutputStream()); + } catch (Exception e) { + log.error("", e); + } + } + + @Override + public void release() { + // + } +} diff --git a/src/main/java/org/olat/ims/qti/statistics/manager/QTIStatisticsManagerImpl.java b/src/main/java/org/olat/ims/qti/statistics/manager/QTIStatisticsManagerImpl.java index 0be0e39061674f6ed14e88e635f549247e83fa9b..fb1bd3bae6bf8a587853646bc7c0d69c94ad8bfd 100644 --- a/src/main/java/org/olat/ims/qti/statistics/manager/QTIStatisticsManagerImpl.java +++ b/src/main/java/org/olat/ims/qti/statistics/manager/QTIStatisticsManagerImpl.java @@ -21,6 +21,7 @@ package org.olat.ims.qti.statistics.manager; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +30,8 @@ import javax.persistence.TypedQuery; import org.olat.core.commons.persistence.DB; import org.olat.course.assessment.AssessmentManager; +import org.olat.ims.qti.QTIResultManager; +import org.olat.ims.qti.editor.beecom.objects.FIBResponse; import org.olat.ims.qti.editor.beecom.objects.Item; import org.olat.ims.qti.editor.beecom.objects.Response; import org.olat.ims.qti.statistics.QTIStatisticSearchParams; @@ -38,6 +41,7 @@ import org.olat.ims.qti.statistics.model.QTIStatisticResultSet; import org.olat.ims.qti.statistics.model.StatisticAnswerOption; import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.model.StatisticChoiceOption; +import org.olat.ims.qti.statistics.model.StatisticFIBOption; import org.olat.ims.qti.statistics.model.StatisticItem; import org.olat.ims.qti.statistics.model.StatisticKPrimOption; import org.olat.ims.qti.statistics.model.StatisticSurveyItem; @@ -373,7 +377,6 @@ public class QTIStatisticsManagerImpl implements QTIStatisticsManager { @Override public List<StatisticKPrimOption> getNumbersInKPrim(Item item, QTIStatisticSearchParams searchParams) { List<StatisticAnswerOption> rawDatas = getStatisticAnswerOptionsOfItem(item.getIdent(), searchParams); - List<Response> responses = item.getQuestion().getResponses(); List<StatisticKPrimOption> kprimPoints = new ArrayList<>(); for(Response response:responses) { @@ -401,6 +404,74 @@ public class QTIStatisticsManagerImpl implements QTIStatisticsManager { } return kprimPoints; } + + @Override + public List<StatisticFIBOption> getStatisticAnswerOptionsFIB(Item item, QTIStatisticSearchParams searchParams) { + + List<StatisticFIBOption> options = new ArrayList<>(); + Map<String,StatisticFIBOption> optionMap = new HashMap<>(); + + List<Response> responses = item.getQuestion().getResponses(); + for(Response response:responses) { + if(response instanceof FIBResponse) { + FIBResponse fibResponse = (FIBResponse)response; + if(FIBResponse.TYPE_BLANK.equals(fibResponse.getType())) { + String ident = fibResponse.getIdent(); + String[] correctFIBs = fibResponse.getCorrectBlank().split(";"); + if(correctFIBs == null || correctFIBs.length == 0) { + continue; + } + + StatisticFIBOption option = new StatisticFIBOption(); + option.setCorrectBlank(correctFIBs[0]); + option.setAlternatives(Arrays.asList(correctFIBs)); + option.setCaseSensitive("Yes".equals(fibResponse.getCaseSensitive())); + option.setPoints(fibResponse.getPoints()); + options.add(option); + optionMap.put(ident, option); + } + } + } + + + List<StatisticAnswerOption> answerOptions = getStatisticAnswerOptionsOfItem(item.getIdent(), searchParams); + + for(StatisticAnswerOption answerOption:answerOptions) { + long count = answerOption.getCount(); + String concatenedAnswer = answerOption.getAnswer(); + Map<String,String> parsedAnswerMap = QTIResultManager.parseResponseStrAnswers(concatenedAnswer); + for(Map.Entry<String, String> parsedAnswerEntry: parsedAnswerMap.entrySet()) { + String ident = parsedAnswerEntry.getKey(); + + StatisticFIBOption option = optionMap.get(ident); + if(option == null) { + continue; + } + + String text = parsedAnswerEntry.getValue(); + boolean correct; + if(option.isCaseSensitive()) { + correct = option.getAlternatives().contains(text); + } else { + correct = false; + for(String alt:option.getAlternatives()) { + if(alt.equalsIgnoreCase(text)) { + correct = true; + } + } + } + + if(correct) { + option.setNumOfCorrect(option.getNumOfCorrect() + count); + } else { + option.setNumOfIncorrect(option.getNumOfIncorrect() + count); + option.getWrongAnswers().add(text); + } + } + } + + return options; + } @Override public List<StatisticAnswerOption> getStatisticAnswerOptionsOfItem(String itemIdent, QTIStatisticSearchParams searchParams) { diff --git a/src/main/java/org/olat/ims/qti/statistics/model/StatisticFIBOption.java b/src/main/java/org/olat/ims/qti/statistics/model/StatisticFIBOption.java new file mode 100644 index 0000000000000000000000000000000000000000..59c85226cf4a64deb058af4be4f32e80370dd140 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/model/StatisticFIBOption.java @@ -0,0 +1,97 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.statistics.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * Initial date: 10.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class StatisticFIBOption { + + private long numOfCorrect = 0l; + private long numOfIncorrect = 0l; + private float points; + + private boolean caseSensitive; + private String correctBlank; + private List<String> alternatives; + private List<String> wrongAnswers = new ArrayList<>(); + + public long getNumOfCorrect() { + return numOfCorrect; + } + + public void setNumOfCorrect(long numOfCorrect) { + this.numOfCorrect = numOfCorrect; + } + + public long getNumOfIncorrect() { + return numOfIncorrect; + } + + public void setNumOfIncorrect(long numOfIncorrect) { + this.numOfIncorrect = numOfIncorrect; + } + + public float getPoints() { + return points; + } + + public void setPoints(float points) { + this.points = points; + } + + public boolean isCaseSensitive() { + return caseSensitive; + } + + public void setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public String getCorrectBlank() { + return correctBlank; + } + + public void setCorrectBlank(String correctBlank) { + this.correctBlank = correctBlank; + } + + public List<String> getAlternatives() { + return alternatives; + } + + public void setAlternatives(List<String> alternatives) { + this.alternatives = alternatives; + } + + public List<String> getWrongAnswers() { + return wrongAnswers; + } + + public void setWrongAnswers(List<String> wrongAnswers) { + this.wrongAnswers = wrongAnswers; + } +} diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/AbstractAssessmentStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/AbstractAssessmentStatisticsController.java deleted file mode 100644 index 9a55a4f04f62bef9b306f589045edd5b07f2346f..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/AbstractAssessmentStatisticsController.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.ims.qti.statistics.ui; - -import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; -import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.chart.HistogramComponent; -import org.olat.core.gui.components.chart.Scale; -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.creator.ControllerCreator; -import org.olat.ims.qti.statistics.QTIStatisticResourceResult; -import org.olat.ims.qti.statistics.QTIStatisticsManager; -import org.olat.ims.qti.statistics.model.StatisticAssessment; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class AbstractAssessmentStatisticsController extends BasicController { - - protected final VelocityContainer mainVC; - protected final QTIStatisticResourceResult resourceResult; - - protected final QTIStatisticsManager qtiStatisticsManager; - - public AbstractAssessmentStatisticsController(UserRequest ureq, WindowControl wControl, - QTIStatisticResourceResult resourceResult, boolean printMode, String page) { - super(ureq, wControl); - - this.resourceResult = resourceResult; - qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); - - mainVC = createVelocityContainer(page); - mainVC.contextPut("printMode", new Boolean(printMode)); - - initDurationHistogram(resourceResult.getQTIStatisticAssessment()); - - putInitialPanel(mainVC); - } - - private void initDurationHistogram(StatisticAssessment stats) { - HistogramComponent scoreHistogram = new HistogramComponent("scoreHistogram"); - scoreHistogram.setLongValues(stats.getDurations()); - scoreHistogram.setYLegend(translate("chart.percent.participants")); - scoreHistogram.setXScale(Scale.hour); - mainVC.put("durationHistogram", scoreHistogram); - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void event(UserRequest ureq, Component source, Event event) { - if("print".equals(event.getCommand())){ - printPages(ureq); - } - } - - private void printPages(UserRequest ureq) { - ControllerCreator printControllerCreator = new ControllerCreator() { - public Controller createController(UserRequest lureq, WindowControl lwControl) { - Controller printCtr = new QTI12PrintController(lureq, lwControl, resourceResult); - Component view = printCtr.getInitialComponent(); - LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, lwControl, null, null, view, null); - return layoutCtr; - } - }; - ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createPrintPopupLayout(printControllerCreator); - openInNewBrowserWindow(ureq, layoutCtrlr); - } - - public static class ItemInfos { - - private final String label; - private final String text; - - public ItemInfos(String label, String text) { - this.label = label; - this.text = text; - } - - public String getLabel() { - return label; - } - - public String getText() { - return text; - } - } -} diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/AbstractItemStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/AbstractItemStatisticsController.java deleted file mode 100644 index c98f71380e63bda9a76fa78cb84152f7505d6edd..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/AbstractItemStatisticsController.java +++ /dev/null @@ -1,216 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.ims.qti.statistics.ui; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.lang.StringUtils; -import org.olat.core.CoreSpringFactory; -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.chart.BarChartComponent; -import org.olat.core.gui.components.chart.BarSeries; -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.ims.qti.editor.beecom.objects.Item; -import org.olat.ims.qti.editor.beecom.objects.Question; -import org.olat.ims.qti.statistics.QTIStatisticResourceResult; -import org.olat.ims.qti.statistics.QTIStatisticSearchParams; -import org.olat.ims.qti.statistics.QTIStatisticsManager; -import org.olat.ims.qti.statistics.QTIType; -import org.olat.ims.qti.statistics.model.StatisticsItem; -import org.olat.ims.qti.statistics.model.StatisticAnswerOption; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -abstract class AbstractItemStatisticsController extends BasicController { - - - protected final VelocityContainer mainVC; - - protected final Item item; - protected final QTIType type; - protected final QTIStatisticSearchParams searchParams; - protected final QTIStatisticsManager qtiStatisticsManager; - - protected final int numOfParticipants; - protected final String mediaBaseURL; - - public AbstractItemStatisticsController(UserRequest ureq, WindowControl wControl, - Item item, QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl); - this.item = item; - numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); - searchParams = resourceResult.getSearchParams(); - qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); - - mediaBaseURL = resourceResult.getResolver().getStaticsBaseURI() + "/"; - type = resourceResult.getType(); - - int questionType = item.getQuestion().getType(); - switch(questionType) { - case Question.TYPE_SC: - mainVC = createVelocityContainer("statistics_item_" + type.name()); - initSingleChoice(); - initChoice(); - break; - case Question.TYPE_MC: - mainVC = createVelocityContainer("statistics_item_" + type.name()); - StatisticsItem itemstats = initChoice(); - initMultipleChoice(itemstats); - break; - case Question.TYPE_KPRIM: - mainVC = createVelocityContainer("statistics_item_" + type.name()); - initKPrim(); - initChoice(); - break; - case Question.TYPE_FIB: - mainVC = createVelocityContainer("statistics_item_" + type.name()); - initFIB(); - initChoice(); - break; - case Question.TYPE_ESSAY: - mainVC = createVelocityContainer("statistics_essai"); - initEssay(); - break; - default: - mainVC = createVelocityContainer("statistics_item_" + type.name()); - break; - } - - mainVC.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); - mainVC.contextPut("questionType", questionType); - mainVC.contextPut("title", item.getTitle()); - mainVC.contextPut("printMode", new Boolean(printMode)); - putInitialPanel(mainVC); - } - - protected abstract void initSingleChoice(); - - protected abstract void initMultipleChoice(StatisticsItem itemstats); - - protected abstract void initKPrim(); - - protected void initFIB() { - List<StatisticAnswerOption> processedAnswers = qtiStatisticsManager - .getStatisticAnswerOptionsOfItem(item.getIdent(), searchParams); - - BarSeries d1 = new BarSeries(); - for (StatisticAnswerOption entry : processedAnswers) { - String answerString = getAllBlanksFromAnswer(entry.getAnswer()); - d1.add(entry.getCount(), answerString); - } - - BarChartComponent durationChart = new BarChartComponent("questionChart"); - durationChart.addSeries(d1); - mainVC.put("questionChart", durationChart); - } - - protected static String getAllBlanksFromAnswer(String answerString) { - List<String> blanks = new ArrayList<String>(); - Pattern p = Pattern.compile("\\[\\[([^\\[\\[,\\]]*)\\]\\]"); - Matcher m = p.matcher(answerString); - while (m.find()) { - blanks.add(m.group().replace("]]", "").replace("[[", "")); - } - return StringUtils.join(blanks, ", "); - } - - - - protected abstract StatisticsItem initChoice(); - - protected void initEssay() { - mainVC.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); - mainVC.contextPut("title", item.getTitle()); - - List<String> answers = qtiStatisticsManager.getAnswers(item.getIdent(), searchParams); - - List<String> cleanedAnswers = new ArrayList<String>(); - for (String string : answers) { - cleanedAnswers.add(stripAnswerText(string)); - } - mainVC.contextPut("studentAnswers", cleanedAnswers); - } - - private String stripAnswerText(String answerTextFromDB){ - String result =""; - int start = answerTextFromDB.indexOf("["); - result = answerTextFromDB.substring(start+2); - result = result.substring(0, result.length()-2); - result = result.replaceAll("\\\\r\\\\n", "<br />"); - return result; - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void event(UserRequest ureq, Component source, Event event) { - // - } - - public static class ResponseInfos { - - private final String label; - private final String text; - private final float points; - private final boolean correct; - private final boolean survey; - - public ResponseInfos(String label, String text, float points, boolean correct, boolean survey) { - this.label = label; - this.text = text; - this.points = points; - this.survey = survey; - this.correct = correct; - } - - public String getLabel() { - return label; - } - - public String getText() { - return text; - } - - public float getPoints() { - return points; - } - - public boolean isSurvey() { - return survey; - } - - public boolean isCorrect() { - return correct; - } - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12AssessmentStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12AssessmentStatisticsController.java index 5e6dcc5b0090e05e39507c9b470d3b380654e12f..7378394aef895b09cc47a5ea15466c55c8b74ced 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12AssessmentStatisticsController.java +++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12AssessmentStatisticsController.java @@ -23,50 +23,78 @@ import static org.olat.ims.qti.statistics.ui.StatisticFormatter.duration; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.format; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; +import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.chart.BarChartComponent; +import org.olat.core.gui.components.Component; import org.olat.core.gui.components.chart.BarSeries; -import org.olat.core.gui.components.chart.HistogramComponent; -import org.olat.core.gui.components.chart.HorizontalBarChartComponent; -import org.olat.core.gui.components.chart.Scale; +import org.olat.core.gui.components.chart.BarSeries.Stringuified; +import org.olat.core.gui.components.chart.StatisticsComponent; +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.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.creator.ControllerCreator; +import org.olat.core.gui.media.MediaResource; import org.olat.course.nodes.QTICourseNode; import org.olat.course.nodes.iq.IQEditController; import org.olat.ims.qti.editor.beecom.objects.Item; import org.olat.ims.qti.editor.beecom.objects.QTIDocument; import org.olat.ims.qti.editor.beecom.objects.Section; import org.olat.ims.qti.statistics.QTIStatisticResourceResult; +import org.olat.ims.qti.statistics.QTIStatisticsManager; +import org.olat.ims.qti.statistics.QTIStatisticsResource; +import org.olat.ims.qti.statistics.QTIType; import org.olat.ims.qti.statistics.model.StatisticAssessment; import org.olat.ims.qti.statistics.model.StatisticItem; +import org.olat.ims.qti.statistics.model.StatisticSurveyItem; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class QTI12AssessmentStatisticsController extends AbstractAssessmentStatisticsController { - +public class QTI12AssessmentStatisticsController extends BasicController { + + private final QTIType type; private final Float maxScore; private final Float cutValue; private final String mediaBaseURL; + + private final Link downloadRawLink; + private final VelocityContainer mainVC; + + private final SeriesFactory seriesfactory; + private final QTIStatisticResourceResult resourceResult; + private final QTIStatisticsManager qtiStatisticsManager; public QTI12AssessmentStatisticsController(UserRequest ureq, WindowControl wControl, QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl, resourceResult, printMode, "statistics_assessment"); + super(ureq, wControl); - mediaBaseURL = resourceResult.getResolver().getStaticsBaseURI() + "/"; + type = resourceResult.getType(); + this.resourceResult = resourceResult; + mediaBaseURL = resourceResult.getMediaBaseURL(); + seriesfactory = new SeriesFactory(resourceResult); + qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); + + mainVC = createVelocityContainer("statistics_assessment"); + mainVC.put("loadd3js", new StatisticsComponent("d3loader")); + mainVC.contextPut("printMode", new Boolean(printMode)); + downloadRawLink = LinkFactory.createLink("download.raw.data", mainVC, this); + downloadRawLink.setCustomEnabledLinkCSS("b_content_download"); + mainVC.put("download", downloadRawLink); //cut value QTICourseNode testNode = resourceResult.getTestCourseNode(); - Object cutScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_CUTVALUE); - if (cutScoreObj instanceof Float) { - cutValue = (Float)cutScoreObj; - } else { - cutValue = null; - } - + StatisticAssessment stats = resourceResult.getQTIStatisticAssessment(); List<Item> items = new ArrayList<>(); @@ -76,50 +104,96 @@ public class QTI12AssessmentStatisticsController extends AbstractAssessmentStati items.add(item); } } + + cutValue = getCutValueSetting(testNode); + maxScore = getMaxScoreSetting(testNode, items); - Object maxScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_MAXSCORE); - if (maxScoreObj instanceof Float) { - maxScore = (Float)maxScoreObj; + initCourseNodeInformation(stats); + initDurationHistogram(resourceResult.getQTIStatisticAssessment()); + if(QTIType.test.equals(type)) { + initScoreHistogram(stats); + initScoreStatisticPerItem(items, stats.getNumOfParticipants()); } else { - // try to calculate max - float max = 0; - for (Item item: items) { - if(item.getQuestion() != null) { - max += item.getQuestion().getMaxValue(); + initItemsOverview(items); + } + + putInitialPanel(mainVC); + } + + @Override + protected void doDispose() { + // + } + + private Float getCutValueSetting(QTICourseNode testNode) { + Float cutValueSetting; + if(QTIType.test.equals(type)) { + Object cutScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_CUTVALUE); + if (cutScoreObj instanceof Float) { + cutValueSetting = (Float)cutScoreObj; + } else { + cutValueSetting = null; + } + } else { + cutValueSetting = null; + } + return cutValueSetting; + } + + private Float getMaxScoreSetting(QTICourseNode testNode, List<Item> items) { + Float maxScoreSetting; + if(QTIType.test.equals(type)) { + Object maxScoreObj = testNode.getModuleConfiguration().get(IQEditController.CONFIG_KEY_MAXSCORE); + if (maxScoreObj instanceof Float) { + maxScoreSetting = (Float)maxScoreObj; + } else { + // try to calculate max + float max = 0; + for (Item item: items) { + if(item.getQuestion() != null) { + max += item.getQuestion().getMaxValue(); + } } + maxScoreSetting = max > 0 ? max : null; } - maxScore = max > 0 ? max : null; + } else { + maxScoreSetting = null; } - - initCourseNodeInformation(stats); - initScoreHistogram(stats); - initScoreStatisticPerItem(items, stats.getNumOfParticipants()); + return maxScoreSetting; } private void initCourseNodeInformation(StatisticAssessment stats) { mainVC.contextPut("numOfParticipants", stats.getNumOfParticipants()); - mainVC.contextPut("type", resourceResult.getType()); - mainVC.contextPut("numOfPassed", stats.getNumOfPassed()); - mainVC.contextPut("numOfFailed", stats.getNumOfFailed()); - - if (cutValue != null) { - mainVC.contextPut("cutScore", format(cutValue)); - } else { - mainVC.contextPut("cutScore", "-"); + + if(QTIType.test.equals(type)) { + mainVC.contextPut("numOfPassed", stats.getNumOfPassed()); + mainVC.contextPut("numOfFailed", stats.getNumOfFailed()); + + if (cutValue != null) { + mainVC.contextPut("cutScore", format(cutValue)); + } else { + mainVC.contextPut("cutScore", "-"); + } + + mainVC.contextPut("maxScore", format(maxScore)); + mainVC.contextPut("average", format(stats.getAverage())); + mainVC.contextPut("range", format(stats.getRange())); + mainVC.contextPut("standardDeviation", format(stats.getStandardDeviation())); + mainVC.contextPut("mode", getModeString(stats.getMode())); + mainVC.contextPut("median", format(stats.getMedian())); } - - mainVC.contextPut("maxScore", format(maxScore)); - mainVC.contextPut("average", format(stats.getAverage())); - mainVC.contextPut("range", format(stats.getRange())); - mainVC.contextPut("standardDeviation", format(stats.getStandardDeviation())); - mainVC.contextPut("mode", getModeString(stats.getMode())); - mainVC.contextPut("median", format(stats.getMedian())); String duration = duration(stats.getAverageDuration()); mainVC.contextPut("averageDuration", duration); } + private void initDurationHistogram(StatisticAssessment stats) { + VelocityContainer durationHistogramVC = createVelocityContainer("histogram_duration"); + durationHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getDurations())); + mainVC.put("durationHistogram", durationHistogramVC); + } + private String getModeString(List<Double> modes) { StringBuilder sb = new StringBuilder(); for(Double mode:modes) { @@ -140,12 +214,12 @@ public class QTI12AssessmentStatisticsController extends AbstractAssessmentStati for (StatisticItem statisticItem: statisticItems) { Item item = statisticItem.getItem(); - String label = StatisticFormatter.getLabel(i++); + String label = Integer.toString(++i); String text = item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL); d1.add(statisticItem.getAverageScore(), label); double numOfRightAnswers = statisticItem.getNumOfCorrectAnswers(); - double res = numOfRightAnswers / numOfParticipants; + double res = numOfRightAnswers; d2.add(res, label); itemInfos.add(new ItemInfos(label, text)); @@ -153,29 +227,89 @@ public class QTI12AssessmentStatisticsController extends AbstractAssessmentStati mainVC.contextPut("itemInfoList", itemInfos); - HorizontalBarChartComponent averageScorePerItemChart = new HorizontalBarChartComponent("questionPoint"); - averageScorePerItemChart.addSeries(d1); - averageScorePerItemChart.setXLegend(translate("chart.answer.averageScoreQuestions.y")); - mainVC.put("averageScorePerItemChart", averageScorePerItemChart); + VelocityContainer averageScorePeritemVC = createVelocityContainer("hbar_average_score_per_item"); + Stringuified data1 = BarSeries.getDatasAndColors(Collections.singletonList(d1), "bar_default"); + averageScorePeritemVC.contextPut("datas", data1); + mainVC.put("averageScorePerItemChart", averageScorePeritemVC); - BarChartComponent percentRightAnswersPerItemChart = new BarChartComponent("correctQuestion"); - percentRightAnswersPerItemChart.addSeries(d2); - percentRightAnswersPerItemChart.setDefaultBarClass("bar_green"); - percentRightAnswersPerItemChart.setYScale(Scale.percent); - percentRightAnswersPerItemChart.setYLegend(translate("chart.percent.participants")); - mainVC.put("percentRightAnswersPerItemChart", percentRightAnswersPerItemChart); + VelocityContainer percentRightAnswersPerItemVC = createVelocityContainer("hbar_right_answer_per_item"); + Stringuified data2 = BarSeries.getDatasAndColors(Collections.singletonList(d2), "bar_green"); + percentRightAnswersPerItemVC.contextPut("datas", data2); + mainVC.put("percentRightAnswersPerItemChart", percentRightAnswersPerItemVC); } private void initScoreHistogram(StatisticAssessment stats) { - HistogramComponent scoreHistogram = new HistogramComponent("scoreHistogram"); - scoreHistogram.setDoubleValues(stats.getScores()); - scoreHistogram.setYLegend(translate("chart.percent.participants")); - if(maxScore != null) { - scoreHistogram.setMaxValue(maxScore.doubleValue()); + VelocityContainer scoreHistogramVC = createVelocityContainer("histogram_score"); + scoreHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getScores())); + scoreHistogramVC.contextPut("cutValue", cutValue); + mainVC.put("scoreHistogram", scoreHistogramVC); + } + + private void initItemsOverview(List<Item> items) { + List<StatisticSurveyItem> surveyItems = qtiStatisticsManager + .getStatisticAnswerOptions(resourceResult.getSearchParams(), items); + + int count = 0; + List<String> overviewList = new ArrayList<>(); + for(StatisticSurveyItem surveyItem:surveyItems) { + Item item = surveyItem.getItem(); + Series series = seriesfactory.getSeries(item, null); + String name = "overview_" + count++; + VelocityContainer vc = createVelocityContainer(name, "hbar_item_overview"); + vc.contextPut("series", series); + vc.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); + vc.contextPut("questionType", item.getQuestion().getType()); + vc.contextPut("title", item.getTitle()); + mainVC.put(vc.getDispatchID(), vc); + overviewList.add(vc.getDispatchID()); } - if(cutValue != null) { - scoreHistogram.setCutValue("bar_red", cutValue.doubleValue(), "bar_green"); + + mainVC.contextPut("overviewList", overviewList); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if("print".equals(event.getCommand())){ + printPages(ureq); + } else if(downloadRawLink == source) { + doDownloadRawData(ureq); + } + } + + private void doDownloadRawData(UserRequest ureq) { + MediaResource resource = new QTIStatisticsResource(resourceResult, getLocale()); + ureq.getDispatchResult().setResultingMediaResource(resource); + } + + private void printPages(UserRequest ureq) { + ControllerCreator printControllerCreator = new ControllerCreator() { + public Controller createController(UserRequest lureq, WindowControl lwControl) { + Controller printCtr = new QTI12PrintController(lureq, lwControl, resourceResult); + Component view = printCtr.getInitialComponent(); + LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, lwControl, null, null, view, null); + return layoutCtr; + } + }; + ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createPrintPopupLayout(printControllerCreator); + openInNewBrowserWindow(ureq, layoutCtrlr); + } + + public static class ItemInfos { + + private final String label; + private final String text; + + public ItemInfos(String label, String text) { + this.label = label; + this.text = text; + } + + public String getLabel() { + return label; + } + + public String getText() { + return text; } - mainVC.put("scoreHistogram", scoreHistogram); } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12ItemStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12ItemStatisticsController.java index 77c6ef3e11cda454f9d6d0d832d0dc42560d2128..7e399e6ddb066ad12da1ecfcbc0bc107d0144fd4 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12ItemStatisticsController.java +++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12ItemStatisticsController.java @@ -19,200 +19,139 @@ */ package org.olat.ims.qti.statistics.ui; +import static org.olat.ims.qti.statistics.ui.StatisticFormatter.duration; import static org.olat.ims.qti.statistics.ui.StatisticFormatter.formatTwo; import java.util.ArrayList; import java.util.List; +import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.chart.BarChartComponent; -import org.olat.core.gui.components.chart.BarSeries; -import org.olat.core.gui.components.chart.Scale; +import org.olat.core.gui.components.chart.StatisticsComponent; +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.ims.qti.editor.beecom.objects.Item; -import org.olat.ims.qti.editor.beecom.objects.Response; +import org.olat.ims.qti.editor.beecom.objects.Question; import org.olat.ims.qti.statistics.QTIStatisticResourceResult; +import org.olat.ims.qti.statistics.QTIStatisticSearchParams; +import org.olat.ims.qti.statistics.QTIStatisticsManager; import org.olat.ims.qti.statistics.QTIType; import org.olat.ims.qti.statistics.model.StatisticsItem; -import org.olat.ims.qti.statistics.model.StatisticAnswerOption; -import org.olat.ims.qti.statistics.model.StatisticChoiceOption; -import org.olat.ims.qti.statistics.model.StatisticKPrimOption; /** * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class QTI12ItemStatisticsController extends AbstractItemStatisticsController { +public class QTI12ItemStatisticsController extends BasicController { - public QTI12ItemStatisticsController(UserRequest ureq, WindowControl wControl, - Item item, QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl, item, resourceResult, printMode); - } + private final QTIStatisticSearchParams searchParams; + private final QTIStatisticResourceResult resourceResult; + private final QTIStatisticsManager qtiStatisticsManager; + private final VelocityContainer mainVC; - @Override - protected StatisticsItem initChoice() { - double maxScore = item.getQuestion().getMaxValue(); - StatisticsItem itemStats = qtiStatisticsManager - .getItemStatistics(item.getIdent(), maxScore, searchParams); - - long rightAnswers = itemStats.getNumOfCorrectAnswers(); - long wrongAnswers = itemStats.getNumOfIncorrectAnswers(); - long notAnswered = numOfParticipants - rightAnswers - wrongAnswers; + private final int numOfParticipants; + private final String mediaBaseURL; + + private final Item item; + private final SeriesFactory seriesfactory; + + public QTI12ItemStatisticsController(UserRequest ureq, WindowControl wControl, + Item item, QTIStatisticResourceResult resourceResult, boolean printMode) { + super(ureq, wControl); + this.item = item; + seriesfactory = new SeriesFactory(resourceResult); + qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); + + this.resourceResult = resourceResult; + searchParams = resourceResult.getSearchParams(); + mediaBaseURL = resourceResult.getResolver().getStaticsBaseURI() + "/"; + numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); + + int questionType = item.getQuestion().getType(); + if(Question.TYPE_ESSAY == questionType) { + mainVC = createVelocityContainer("statistics_item_essai"); + initEssay(); + } else { + mainVC = createVelocityContainer("statistics_item"); + StatisticsItem itemStats = initItemStatistics(); + initItem(itemStats); + } + mainVC.put("d3loader", new StatisticsComponent("d3loader")); mainVC.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); - mainVC.contextPut("questionType", item.getQuestion().getType()); + mainVC.contextPut("questionType", questionType); mainVC.contextPut("title", item.getTitle()); - mainVC.contextPut("maxScore", maxScore); - mainVC.contextPut("rightAnswers", rightAnswers); - mainVC.contextPut("wrongAnswers", wrongAnswers); - mainVC.contextPut("notAnswered", notAnswered); - mainVC.contextPut("itemDifficulty", formatTwo(itemStats.getDifficulty())); - mainVC.contextPut("averageScore", formatTwo(itemStats.getAverageScore())); - mainVC.contextPut("averageDuration", formatTwo(itemStats.getAverageDuration())); - return itemStats; + mainVC.contextPut("printMode", new Boolean(printMode)); + putInitialPanel(mainVC); } @Override - protected void initSingleChoice() { - List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager - .getNumOfAnswersPerSingleChoiceAnswerOption(item, searchParams); - - int i = 0; - BarSeries d1 = new BarSeries(); - List<ResponseInfos> responseInfos = new ArrayList<>(); - for (StatisticChoiceOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); - double ans_count = statisticResponse.getCount(); - double ans_count_percent = ans_count / numOfParticipants; - float points = response.getPoints(); - String cssColor = response.isCorrect() ? "bar_green" : "bar_red"; - - String label = StatisticFormatter.getLabel(i++);; - d1.add(ans_count_percent, label, cssColor); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, points, true, QTIType.survey.equals(type))); - } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.addSeries(d1); - chart.setYLegend(translate("chart.percent.participants")); - chart.setYScale(Scale.percent); - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); + protected void doDispose() { + // } @Override - protected void initMultipleChoice(StatisticsItem itemStats) { - List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager - .getNumOfRightAnsweredMultipleChoice(item, searchParams); - - BarSeries d1 = new BarSeries("bar_green"); - BarSeries d2 = new BarSeries("bar_red"); - BarSeries d3 = new BarSeries("bar_grey"); - - double wrongFactor = itemStats.getNumOfResults() / (double)numOfParticipants; - - int i = 0; - List<ResponseInfos> responseInfos = new ArrayList<>(); - for(StatisticChoiceOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); + protected void event(UserRequest ureq, Component source, Event event) { + // + } - float points = response.getPoints(); - double answersPerAnswerOption = statisticResponse.getCount(); - double percentageRight; - if (points > 0) { - percentageRight = answersPerAnswerOption / numOfParticipants; - } else { - percentageRight = 1.0d - answersPerAnswerOption / numOfParticipants; - } - - String label = StatisticFormatter.getLabel(i++); - - double rightA = percentageRight; - d1.add(rightA, label); - double wrongA = wrongFactor - percentageRight; - d2.add(wrongA, label); - - d3.add(1.0d - wrongFactor, label); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, points, true, QTIType.survey.equals(type))); - } + protected StatisticsItem initItemStatistics() { + boolean survey = QTIType.survey.equals(resourceResult.getType()); + double maxScore = survey ? 1.0d : item.getQuestion().getMaxValue(); + StatisticsItem itemStats = qtiStatisticsManager + .getItemStatistics(item.getIdent(), maxScore, searchParams); - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.setYScale(Scale.percent); - chart.setYLegend(translate("chart.percent.participants")); - chart.addSeries(d1, d2); - if(wrongFactor < 1.0) { - chart.addSeries(d3); + mainVC.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); + mainVC.contextPut("questionType", item.getQuestion().getType()); + mainVC.contextPut("title", item.getTitle()); + if(!survey) { + long rightAnswers = itemStats.getNumOfCorrectAnswers(); + long wrongAnswers = itemStats.getNumOfIncorrectAnswers(); + long notAnswered = numOfParticipants - rightAnswers - wrongAnswers; + + mainVC.contextPut("maxScore", maxScore); + mainVC.contextPut("rightAnswers", rightAnswers); + mainVC.contextPut("wrongAnswers", wrongAnswers); + mainVC.contextPut("notAnswered", notAnswered); + mainVC.contextPut("itemDifficulty", formatTwo(itemStats.getDifficulty())); + mainVC.contextPut("averageScore", formatTwo(itemStats.getAverageScore())); } - - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); + mainVC.contextPut("averageDuration", duration(itemStats.getAverageDuration())); + return itemStats; } + + protected void initItem(StatisticsItem itemStats) { + Series series = seriesfactory.getSeries(item, itemStats); - @Override - protected void initKPrim() { - List<StatisticKPrimOption> statisticResponses = qtiStatisticsManager - .getNumbersInKPrim(item, searchParams); - - int i = 0; - BarSeries d1 = new BarSeries("bar_green"); - BarSeries d2 = new BarSeries("bar_red"); - BarSeries d3 = new BarSeries("bar_grey"); - - List<ResponseInfos> responseInfos = new ArrayList<>(); - for (StatisticKPrimOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); - float points = response.getPoints(); - double right = ((double)statisticResponse.getNumOfCorrect() / numOfParticipants); - double wrong = ((double)statisticResponse.getNumOfIncorrect() / numOfParticipants); - double notanswered = 1.0 - right - wrong; - - String label = StatisticFormatter.getLabel(i++); - d1.add(right, label); - d2.add(wrong, label); - d3.add(notanswered, label); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, points, true, QTIType.survey.equals(type))); - } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.setYScale(Scale.percent); - chart.setYLegend(translate("chart.percent.participants")); - chart.addSeries(d1, d2, d3); - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); + VelocityContainer vc = createVelocityContainer("hbar_item"); + vc.contextPut("series", series); + mainVC.put("questionChart", vc); + mainVC.contextPut("series", series); } - @Override - protected void initFIB() { - List<StatisticAnswerOption> processedAnswers = qtiStatisticsManager - .getStatisticAnswerOptionsOfItem(item.getIdent(), searchParams); + protected void initEssay() { + mainVC.contextPut("question", item.getQuestion().getQuestion().renderAsHtml(mediaBaseURL)); + mainVC.contextPut("title", item.getTitle()); + + List<String> answers = qtiStatisticsManager.getAnswers(item.getIdent(), searchParams); - BarSeries d1 = new BarSeries(); - for (StatisticAnswerOption entry : processedAnswers) { - String answerString = getAllBlanksFromAnswer(entry.getAnswer()); - d1.add(entry.getCount(), answerString); + List<String> cleanedAnswers = new ArrayList<String>(); + for (String string : answers) { + cleanedAnswers.add(stripAnswerText(string)); } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.addSeries(d1); - mainVC.put("questionChart", chart); + mainVC.contextPut("studentAnswers", cleanedAnswers); } - @Override - protected void doDispose() { - // - } - - @Override - protected void event(UserRequest ureq, Component source, Event event) { - // + private String stripAnswerText(String answerTextFromDB){ + String result =""; + int start = answerTextFromDB.indexOf("["); + result = answerTextFromDB.substring(start+2); + result = result.substring(0, result.length()-2); + result = result.replaceAll("\\\\r\\\\n", "<br />"); + return result; } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java index 072426b1a0cb26294df1664be73f7fa48b8bec45..1550ac5c6a50b643cfb97aa0f90220d77defef3e 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java +++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12StatisticsToolController.java @@ -157,7 +157,7 @@ public class QTI12StatisticsToolController extends BasicController implements St private void doLaunchStatistics(UserRequest ureq, WindowControl wControl) { if(result == null) { - result = new QTIStatisticResourceResult(courseNode, searchParams); + result = new QTIStatisticResourceResult(courseRes, courseNode, searchParams); } GenericTreeModel treeModel = new GenericTreeModel(); diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyItemStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyItemStatisticsController.java deleted file mode 100644 index a088a2ff8eb77980e73319da3aaf1be1331053d1..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyItemStatisticsController.java +++ /dev/null @@ -1,164 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.ims.qti.statistics.ui; - -import static org.olat.ims.qti.statistics.ui.StatisticFormatter.formatTwo; - -import java.util.ArrayList; -import java.util.List; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.chart.BarChartComponent; -import org.olat.core.gui.components.chart.BarSeries; -import org.olat.core.gui.components.chart.Scale; -import org.olat.core.gui.control.Event; -import org.olat.core.gui.control.WindowControl; -import org.olat.ims.qti.editor.beecom.objects.Item; -import org.olat.ims.qti.editor.beecom.objects.Response; -import org.olat.ims.qti.statistics.QTIStatisticResourceResult; -import org.olat.ims.qti.statistics.QTIType; -import org.olat.ims.qti.statistics.model.StatisticsItem; -import org.olat.ims.qti.statistics.model.StatisticChoiceOption; -import org.olat.ims.qti.statistics.model.StatisticKPrimOption; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class QTI12SurveyItemStatisticsController extends AbstractItemStatisticsController { - - - - public QTI12SurveyItemStatisticsController(UserRequest ureq, WindowControl wControl, - Item item, QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl, item, resourceResult, printMode); - } - - @Override - protected void initSingleChoice() { - List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager - .getNumOfAnswersPerSingleChoiceAnswerOption(item, searchParams); - - int i = 0; - BarSeries series = new BarSeries(); - List<ResponseInfos> responseInfos = new ArrayList<>(); - for (StatisticChoiceOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); - double ans_count = statisticResponse.getCount(); - double ans_count_percent = ans_count / numOfParticipants; - - String label = StatisticFormatter.getLabel(i++); - series.add(ans_count_percent, label); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, 0.0f, true, QTIType.survey.equals(type))); - } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.setYScale(Scale.percent); - chart.setYLegend(translate("chart.percent.participants")); - chart.addSeries(series); - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); - } - - @Override - protected void initMultipleChoice(StatisticsItem itemStats) { - List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager - .getNumOfRightAnsweredMultipleChoice(item, searchParams); - - BarSeries series = new BarSeries(); - - int i = 0; - List<ResponseInfos> responseInfos = new ArrayList<>(); - for(StatisticChoiceOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); - - float points = response.getPoints(); - double answersPerAnswerOption = statisticResponse.getCount(); - double percentage = answersPerAnswerOption / numOfParticipants; - - String label = StatisticFormatter.getLabel(i++); - series.add(percentage, label); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, points, true, QTIType.survey.equals(type))); - } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.setYScale(Scale.percent); - chart.setYLegend(translate("chart.percent.participants")); - chart.addSeries(series); - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); - } - - @Override - protected void initKPrim() { - List<StatisticKPrimOption> statisticResponses = qtiStatisticsManager - .getNumbersInKPrim(item, searchParams); - - int i = 0; - BarSeries d1 = new BarSeries("bar_default"); - BarSeries d2 = new BarSeries("bar_default_darker"); - - List<ResponseInfos> responseInfos = new ArrayList<>(); - for (StatisticKPrimOption statisticResponse:statisticResponses) { - Response response = statisticResponse.getResponse(); - double left = ((double)statisticResponse.getNumOfCorrect() / numOfParticipants); - double wrong = ((double)statisticResponse.getNumOfIncorrect() / numOfParticipants); - - String label = StatisticFormatter.getLabel(i++); - d1.add(left, label); - d2.add(wrong, label); - - String text = response.getContent().renderAsHtml(mediaBaseURL); - responseInfos.add(new ResponseInfos(label, text, 0.0f, true, QTIType.survey.equals(type))); - } - - BarChartComponent chart = new BarChartComponent("questionChart"); - chart.setYScale(Scale.percent); - chart.setYLegend(translate("chart.percent.participants")); - chart.addSeries(d1, d2); - mainVC.put("questionChart", chart); - mainVC.contextPut("responseInfos", responseInfos); - } - - @Override - protected StatisticsItem initChoice() { - StatisticsItem itemStats = qtiStatisticsManager - .getItemStatistics(item.getIdent(), 1.0, searchParams); - - mainVC.contextPut("averageDuration", formatTwo(itemStats.getAverageDuration())); - return itemStats; - } - - @Override - protected void doDispose() { - // - } - - @Override - protected void event(UserRequest ureq, Component source, Event event) { - // - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyStatisticsController.java deleted file mode 100644 index eaa5cfb5d391552be92ceee2da2d56f9d2166311..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI12SurveyStatisticsController.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.ims.qti.statistics.ui; - -import static org.olat.ims.qti.statistics.ui.StatisticFormatter.duration; - -import java.util.ArrayList; -import java.util.List; - -import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.chart.BarSeries; -import org.olat.core.gui.components.chart.HorizontalBarChartComponent; -import org.olat.core.gui.control.WindowControl; -import org.olat.ims.qti.editor.beecom.objects.Item; -import org.olat.ims.qti.editor.beecom.objects.QTIDocument; -import org.olat.ims.qti.editor.beecom.objects.Section; -import org.olat.ims.qti.statistics.QTIStatisticResourceResult; -import org.olat.ims.qti.statistics.model.StatisticAssessment; -import org.olat.ims.qti.statistics.model.StatisticSurveyItem; -import org.olat.ims.qti.statistics.model.StatisticSurveyItemResponse; - -/** - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class QTI12SurveyStatisticsController extends AbstractAssessmentStatisticsController { - - public QTI12SurveyStatisticsController(UserRequest ureq, WindowControl wControl, - QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl, resourceResult, printMode, "statistics_survey"); - - StatisticAssessment stats = resourceResult.getQTIStatisticAssessment(); - List<Item> items = new ArrayList<>(); - QTIDocument qtiDocument = resourceResult.getQTIDocument(); - for(Section section:qtiDocument.getAssessment().getSections()) { - for(Item item:section.getItems()) { - items.add(item); - } - } - initCourseNodeInformation(stats); - initItemsOverview(items); - } - - private void initCourseNodeInformation(StatisticAssessment stats) { - mainVC.contextPut("type", resourceResult.getType()); - mainVC.contextPut("numOfParticipants", stats.getNumOfParticipants()); - String duration = duration(stats.getAverageDuration()); - mainVC.contextPut("averageDuration", duration); - } - - private void initItemsOverview(List<Item> items) { - List<StatisticSurveyItem> surveyItems = qtiStatisticsManager - .getStatisticAnswerOptions(resourceResult.getSearchParams(), items); - - BarSeries series = new BarSeries("bar_default"); - for(StatisticSurveyItem surveyItem:surveyItems) { - Item item = surveyItem.getItem(); - String atext = item.getTitle(); - for(StatisticSurveyItemResponse response:surveyItem.getResponses()) { - long value = response.getNumOfResponses(); - String category; - if(response.getResponse() != null && response.getResponse().getContent() != null) { - String text = response.getResponse().getContent().renderAsText(); - category = text; - } else { - category = response.getAnswer(); - } - series.add(value, atext + ":" + category); - } - } - - HorizontalBarChartComponent overviewSurvey = new HorizontalBarChartComponent("overviewSurvey"); - overviewSurvey.addSeries(series); - mainVC.put("overviewSurveyBarChart", overviewSurvey); - } -} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/QTI21OnyxAssessmentStatisticsController.java b/src/main/java/org/olat/ims/qti/statistics/ui/QTI21OnyxAssessmentStatisticsController.java index 993d72b001245ca8361d3e0b70d8729e893025c3..9a6a191d6a0b4764623d48a77472c88e04f5b30b 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/QTI21OnyxAssessmentStatisticsController.java +++ b/src/main/java/org/olat/ims/qti/statistics/ui/QTI21OnyxAssessmentStatisticsController.java @@ -25,8 +25,13 @@ import static org.olat.ims.qti.statistics.ui.StatisticFormatter.format; import java.util.List; import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.chart.HistogramComponent; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.chart.BarSeries; +import org.olat.core.gui.components.chart.StatisticsComponent; +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.ims.qti.statistics.QTIStatisticResourceResult; import org.olat.ims.qti.statistics.model.StatisticAssessment; @@ -35,19 +40,34 @@ import org.olat.ims.qti.statistics.model.StatisticAssessment; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class QTI21OnyxAssessmentStatisticsController extends AbstractAssessmentStatisticsController { +public class QTI21OnyxAssessmentStatisticsController extends BasicController { - + private final VelocityContainer mainVC; + + private final QTIStatisticResourceResult resourceResult; public QTI21OnyxAssessmentStatisticsController(UserRequest ureq, WindowControl wControl, QTIStatisticResourceResult resourceResult, boolean printMode) { - super(ureq, wControl, resourceResult, printMode, "statistics_onyx"); + super(ureq, wControl); + + this.resourceResult = resourceResult; + + mainVC = createVelocityContainer("statistics_onyx"); + mainVC.put("loadd3js", new StatisticsComponent("d3loader")); + mainVC.contextPut("printMode", new Boolean(printMode)); + putInitialPanel(mainVC); StatisticAssessment stats = resourceResult.getQTIStatisticAssessment(); initScoreHistogram(stats); + initDurationHistogram(stats); initCourseNodeInformation(stats); } + @Override + protected void doDispose() { + // + } + private void initCourseNodeInformation(StatisticAssessment stats) { mainVC.contextPut("numOfParticipants", stats.getNumOfParticipants()); @@ -75,9 +95,19 @@ public class QTI21OnyxAssessmentStatisticsController extends AbstractAssessmentS } private void initScoreHistogram(StatisticAssessment stats) { - HistogramComponent scoreHistogram = new HistogramComponent("scoreHistogram"); - scoreHistogram.setDoubleValues(stats.getScores()); - scoreHistogram.setYLegend(translate("chart.percent.participants")); - mainVC.put("scoreHistogram", scoreHistogram); + VelocityContainer scoreHistogramVC = createVelocityContainer("histogram_score"); + scoreHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getScores())); + mainVC.put("scoreHistogram", scoreHistogramVC); + } + + private void initDurationHistogram(StatisticAssessment stats) { + VelocityContainer durationHistogramVC = createVelocityContainer("histogram_duration"); + durationHistogramVC.contextPut("datas", BarSeries.datasToString(stats.getDurations())); + mainVC.put("durationHistogram", durationHistogramVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // } } \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/ResponseInfos.java b/src/main/java/org/olat/ims/qti/statistics/ui/ResponseInfos.java new file mode 100644 index 0000000000000000000000000000000000000000..f8a1b5f452c6dfc92af648e9639a61313033df4c --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/ResponseInfos.java @@ -0,0 +1,108 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.statistics.ui; + +import java.util.Collections; +import java.util.List; + +import org.olat.core.util.StringHelper; +import org.olat.course.assessment.AssessmentHelper; + +/** + * + * Initial date: 10.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ResponseInfos { + + private final String label; + private final String text; + private final Float points; + private final boolean correct; + private final boolean survey; + + private final List<String> wrongAnswers; + + public ResponseInfos(String label, String text, Float points, boolean correct, boolean survey) { + this(label, text, Collections.<String>emptyList(), points, survey, correct); + } + + public ResponseInfos(String label, String text, List<String> wrongAnswers, Float points, boolean correct, boolean survey) { + this.label = label; + this.text = text; + this.points = points; + this.survey = survey; + this.correct = correct; + this.wrongAnswers = wrongAnswers; + } + + public String getLabel() { + return label; + } + + public String getText() { + return text; + } + + public Float getPoints() { + return points; + } + + public String getFormattedPoints() { + if(points == null) { + return ""; + } + return AssessmentHelper.getRoundedScore(points); + } + + public boolean isWrongAnswersAvailable() { + return wrongAnswers != null && wrongAnswers.size() > 0; + } + + public List<String> getWrongAnswers() { + return wrongAnswers; + } + + public String getFormattedWrongAnswers() { + if(wrongAnswers != null && wrongAnswers.size() > 0) { + StringBuilder sb = new StringBuilder(); + for(String answer:wrongAnswers) { + if(sb.length() > 0) sb.append(", "); + if(StringHelper.containsNonWhitespace(answer)) { + sb.append(answer); + } else { + sb.append("\"\""); + } + } + + return sb.toString(); + } + return ""; + } + + public boolean isSurvey() { + return survey; + } + + public boolean isCorrect() { + return correct; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/Series.java b/src/main/java/org/olat/ims/qti/statistics/ui/Series.java new file mode 100644 index 0000000000000000000000000000000000000000..d74ce8f2b57d9ac9d8c0123609f359c16437be76 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/Series.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.statistics.ui; + +import java.util.List; + +import org.olat.core.gui.components.chart.BarSeries; +import org.olat.core.gui.components.chart.BarSeries.Stringuified; + +/** + * + * Initial date: 10.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class Series { + + private String itemCss; + private String chartType; + private final List<BarSeries> series; + private final List<ResponseInfos> responseInfos; + + private final int numOfParticipants; + private Stringuified datas; + + public Series(List<BarSeries> series, List<ResponseInfos> responseInfos, int numOfParticipants) { + this.series = series; + this.responseInfos = responseInfos; + this.numOfParticipants = numOfParticipants; + } + + public String getItemCss() { + return itemCss; + } + + public void setItemCss(String itemCss) { + this.itemCss = itemCss; + } + + public String getChartType() { + return chartType; + } + + public void setChartType(String chartType) { + this.chartType = chartType; + } + + public int getNumOfParticipants() { + return numOfParticipants; + } + + public List<BarSeries> getSeries() { + return series; + } + + public List<ResponseInfos> getResponseInfos() { + return responseInfos; + } + + public boolean isColorsCustom() { + Stringuified d = getDatas(); + return d.getColors().length() > 3; + } + + public Stringuified getDatas() { + if(datas == null) { + datas = BarSeries.getDatasAndColors(series, "bar_default"); + } + return datas; + } + +} diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/SeriesFactory.java b/src/main/java/org/olat/ims/qti/statistics/ui/SeriesFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..39f6de006fb961a4aa8b9abe06b6474442c78d62 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/SeriesFactory.java @@ -0,0 +1,257 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti.statistics.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.components.chart.BarSeries; +import org.olat.ims.qti.editor.beecom.objects.Item; +import org.olat.ims.qti.editor.beecom.objects.Question; +import org.olat.ims.qti.editor.beecom.objects.Response; +import org.olat.ims.qti.statistics.QTIStatisticResourceResult; +import org.olat.ims.qti.statistics.QTIStatisticsManager; +import org.olat.ims.qti.statistics.QTIType; +import org.olat.ims.qti.statistics.model.StatisticChoiceOption; +import org.olat.ims.qti.statistics.model.StatisticFIBOption; +import org.olat.ims.qti.statistics.model.StatisticKPrimOption; +import org.olat.ims.qti.statistics.model.StatisticsItem; + +/** + * + * Initial date: 10.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class SeriesFactory { + + private static final String BAR_CORRECT_WRONG_NOT = "horizontalBarMultipleChoice"; + private static final String BAR_ANSWERED = "horizontalBarMultipleChoiceSurvey"; + private static final String BAR_CORRECT = "horizontalBarSingleChoice"; + + + private final QTIStatisticsManager qtiStatisticsManager; + private final QTIStatisticResourceResult resourceResult; + + public SeriesFactory(QTIStatisticResourceResult resourceResult) { + this.resourceResult = resourceResult; + qtiStatisticsManager = CoreSpringFactory.getImpl(QTIStatisticsManager.class); + } + + public static String getCssClass(Item item) { + int questionType = item.getQuestion().getType(); + switch (questionType) { + case Question.TYPE_SC: return "o_mi_qtisc"; + case Question.TYPE_MC: return "o_mi_qtimc"; + case Question.TYPE_KPRIM: return "o_mi_qtikprim"; + case Question.TYPE_FIB: return "o_mi_qtifib"; + case Question.TYPE_ESSAY: return "o_mi_qtiessay"; + default: return null; + } + } + + public Series getSeries(Item item, StatisticsItem itemStats) { + int questionType = item.getQuestion().getType(); + switch(questionType) { + case Question.TYPE_SC: return getSingleChoice(item); + case Question.TYPE_MC: return getMultipleChoice(item, itemStats); + case Question.TYPE_KPRIM: return getKPrim(item); + case Question.TYPE_FIB: return getFIB(item); + default: return null; + } + } + + public Series getSingleChoice(Item item) { + List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager + .getNumOfAnswersPerSingleChoiceAnswerOption(item, resourceResult.getSearchParams()); + + String mediaBaseURL = resourceResult.getMediaBaseURL(); + boolean survey = QTIType.survey.equals(resourceResult.getType()); + int numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); + + int i = 0; + BarSeries d1 = new BarSeries(); + List<ResponseInfos> responseInfos = new ArrayList<>(); + for (StatisticChoiceOption statisticResponse:statisticResponses) { + Response response = statisticResponse.getResponse(); + double ans_count = statisticResponse.getCount(); + + Float points; + String cssColor; + if(survey) { + points = null; + cssColor = "bar_default"; + } else { + points = response.getPoints(); + cssColor = response.isCorrect() ? "bar_green" : "bar_red"; + } + + String label = Integer.toString(++i); + d1.add(ans_count, label, cssColor); + + String text = response.getContent().renderAsHtml(mediaBaseURL); + responseInfos.add(new ResponseInfos(label, text, points, true, survey)); + } + + List<BarSeries> serieList = Collections.singletonList(d1); + Series series = new Series(serieList, responseInfos, numOfParticipants); + series.setChartType(BAR_CORRECT); + series.setItemCss(getCssClass(item)); + return series; + } + + public Series getMultipleChoice(Item item, StatisticsItem itemStats) { + List<StatisticChoiceOption> statisticResponses = qtiStatisticsManager + .getNumOfRightAnsweredMultipleChoice(item, resourceResult.getSearchParams()); + + BarSeries d1 = new BarSeries("bar_green"); + BarSeries d2 = new BarSeries("bar_red"); + BarSeries d3 = new BarSeries("bar_grey"); + + String mediaBaseURL = resourceResult.getMediaBaseURL(); + boolean survey = QTIType.survey.equals(resourceResult.getType()); + int numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); + int notAnswered = numOfParticipants - (itemStats == null ? 0 : itemStats.getNumOfResults()); + + int i = 0; + List<ResponseInfos> responseInfos = new ArrayList<>(); + for(StatisticChoiceOption statisticResponse:statisticResponses) { + Response response = statisticResponse.getResponse(); + + float points = response.getPoints(); + double answersPerAnswerOption = statisticResponse.getCount(); + + double rightA; + double wrongA; + if (points >= 0f) { + rightA = answersPerAnswerOption; + wrongA = numOfParticipants - notAnswered - answersPerAnswerOption; + } else { + //minus negative points are not answered right? + rightA = numOfParticipants - notAnswered - answersPerAnswerOption ; + wrongA = answersPerAnswerOption; + } + + String label = Integer.toString(++i); + d1.add(rightA, label); + d2.add(wrongA, label); + d3.add(notAnswered, label); + + String text = response.getContent().renderAsHtml(mediaBaseURL); + Float pointsObj = survey ? null : points; + responseInfos.add(new ResponseInfos(label, text, pointsObj, true, survey)); + } + + List<BarSeries> serieList = new ArrayList<>(3); + serieList.add(d1); + if(!survey) { + serieList.add(d2); + serieList.add(d3); + } + + Series series = new Series(serieList, responseInfos, numOfParticipants); + series.setChartType(survey ? BAR_ANSWERED : BAR_CORRECT_WRONG_NOT); + series.setItemCss(getCssClass(item)); + return series; + } + + public Series getKPrim(Item item) { + List<StatisticKPrimOption> statisticResponses = qtiStatisticsManager + .getNumbersInKPrim(item, resourceResult.getSearchParams()); + + String mediaBaseURL = resourceResult.getMediaBaseURL(); + boolean survey = QTIType.survey.equals(resourceResult.getType()); + int numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); + + int i = 0; + BarSeries d1 = new BarSeries("bar_green"); + BarSeries d2 = new BarSeries("bar_red"); + BarSeries d3 = new BarSeries("bar_grey"); + + List<ResponseInfos> responseInfos = new ArrayList<>(); + for (StatisticKPrimOption statisticResponse:statisticResponses) { + Response response = statisticResponse.getResponse(); + + double right = statisticResponse.getNumOfCorrect(); + double wrong = statisticResponse.getNumOfIncorrect(); + double notanswered = numOfParticipants - right - wrong; + + String label = Integer.toString(++i); + d1.add(right, label); + d2.add(wrong, label); + d3.add(notanswered, label); + + String text = response.getContent().renderAsHtml(mediaBaseURL); + responseInfos.add(new ResponseInfos(label, text, null, true, survey)); + } + + List<BarSeries> serieList = new ArrayList<>(3); + serieList.add(d1); + serieList.add(d2); + serieList.add(d3); + Series series = new Series(serieList, responseInfos, numOfParticipants); + series.setChartType(survey ? BAR_ANSWERED : BAR_CORRECT_WRONG_NOT); + series.setItemCss(getCssClass(item)); + return series; + } + + public Series getFIB(Item item) { + List<StatisticFIBOption> processedAnswers = qtiStatisticsManager + .getStatisticAnswerOptionsFIB(item, resourceResult.getSearchParams()); + Collections.reverse(processedAnswers); + + boolean survey = QTIType.survey.equals(resourceResult.getType()); + boolean singleCorrectScore = item.getQuestion().getSingleCorrectScore() > 0.0f; + int numOfParticipants = resourceResult.getQTIStatisticAssessment().getNumOfParticipants(); + + int i = 0; + String cssColor = survey ? "bar_default" : "bar_green"; + BarSeries d1 = new BarSeries(); + List<ResponseInfos> responseInfos = new ArrayList<>(); + for (StatisticFIBOption entry : processedAnswers) { + + String label = Integer.toString(++i); + String answerString = entry.getCorrectBlank(); + d1.add(entry.getNumOfCorrect(), label, cssColor); + + StringBuilder text = new StringBuilder(); + text.append(answerString); + if(entry.getAlternatives().size() > 1) { + text.append(" ["); + for(int j=1; j<entry.getAlternatives().size(); j++) { + if(j > 2) text.append(", "); + text.append(entry.getAlternatives().get(j)); + } + text.append("]"); + } + + Float score = singleCorrectScore ? null : entry.getPoints(); + responseInfos.add(new ResponseInfos(label, text.toString(), entry.getWrongAnswers(), score, true, survey)); + } + + List<BarSeries> serieList = Collections.singletonList(d1); + Series series = new Series(serieList, responseInfos, numOfParticipants); + series.setChartType(BAR_ANSWERED); + series.setItemCss(getCssClass(item)); + return series; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_average_score_per_item.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_average_score_per_item.html new file mode 100644 index 0000000000000000000000000000000000000000..de3fcc5af865a90ef1951045d85b1d3841ab87eb --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_average_score_per_item.html @@ -0,0 +1,12 @@ +<div id="$r.getId('d3div')"><div id="$r.getId('d3holder')" class='d3chart' style='width:600px;height:155px'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('averageScorePerItem', { + values: [$datas.data], + xBottomLegend: ' $r.translate("chart.answer.averageScoreQuestions.y")', + yLeftLegend: '$r.translate("chart.item")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item.html new file mode 100644 index 0000000000000000000000000000000000000000..c6ac3aa65c0c0d517070bc1e0986060ce8126be8 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item.html @@ -0,0 +1,15 @@ +<div id="$r.getId('d3div')"><div id='$r.getId("d3holder")' class='d3chart' style='width:600px;height:300px'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('$series.chartType', { + values: [$series.datas.data], + colors: #if($series.colorsCustom) [$series.datas.colors] #else null #end, + participants: $series.numOfParticipants, + xTopLegend: '$r.translate("chart.percent.participants.num")', + xBottomLegend: '$r.translate("chart.percent.participants")', + yLeftLegend: '$r.translate("chart.item")', + }) +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item_overview.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item_overview.html new file mode 100644 index 0000000000000000000000000000000000000000..d9cbe33313cd1ca813f8b10fe2d46425ee8422d6 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_item_overview.html @@ -0,0 +1,37 @@ +<div class="o_qti_statistics"> + <h4 class="b_with_small_icon_left $series.itemCss">$title</h4> + <p>$question</p> + + <div id="$r.getId('d3div')"><div id='$r.getId("d3holder")' class='d3chart' style='width:600px;'></div> + <script type='text/javascript'> + /* <![CDATA[ */ + jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('$series.chartType', { + values: [$series.datas.data], + colors: #if($series.colorsCustom) [$series.datas.colors] #else null #end, + participants: $series.numOfParticipants, + barHeight: 25, + xTopLegend: '$r.translate("chart.percent.participants.num")', + xBottomLegend: '$r.translate("chart.percent.participants")', + yLeftLegend: '$r.translate("chart.item")', + }) + }); + /* ]]> */</script> + </div> + + <ul> + #foreach($responseInfo in $series.responseInfos) + #if($responseInfo.survey) + <li class="o_qti_statistics-survey-item qti-survey"> + #elseif($responseInfo.correct) + <li class="o_qti_statistics-correct"> + #else + <li class="o_qti_statistics-ncorrect"> + #end + <strong>$responseInfo.label.</strong> $responseInfo.text + #if($responseInfo.points) $r.translate("answer.points", $responseInfo.formattedPoints) #end + #if($responseInfo.wrongAnswersAvailable) <br/>$r.translate("wrong.answer") $responseInfo.formattedWrongAnswers #end + </li> + #end + </ul> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_right_answer_per_item.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_right_answer_per_item.html new file mode 100644 index 0000000000000000000000000000000000000000..93c7ee9d4c2938a5878daab7aea717467f48b198 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/hbar_right_answer_per_item.html @@ -0,0 +1,13 @@ +<div id="$r.getId('d3div')"><div id="$r.getId('d3holder')" class='d3chart' style='width:600px;height:155px'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('rightAnswerPerItem', { + values: [$datas.data], + xBottomLegend: '$r.translate("chart.percent.participants.num")', + xTopLegend: '$r.translate("chart.percent.participants")', + yLeftLegend: '$r.translate("chart.item")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_duration.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_duration.html new file mode 100644 index 0000000000000000000000000000000000000000..64d061cd854a5fd934d5b36d2a446a63d2b12292 --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_duration.html @@ -0,0 +1,14 @@ +<h4>$r.translate("chart.duration.histogramm")</h4> +<div id="$r.getId('d3div')"><div id='$r.getId("d3holder")' class='d3chart' style='width:600px;height:300px'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('histogramDuration', { + values: [$datas], + xBottomLegend: '$r.translate("chart.duration.histogramm.legend")', + yLeftLegend: '$r.translate("chart.percent.participants")', + yRightLegend:'$r.translate("chart.percent.participants.num")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_score.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_score.html new file mode 100644 index 0000000000000000000000000000000000000000..6ffcfd014cd9a030c06becb22c526341508acbcb --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/histogram_score.html @@ -0,0 +1,15 @@ +<h4>$r.translate("chart.score.histogramm")</h4> +<div id="$r.getId('d3div')"><div id='$r.getId("d3holder")' class='d3chart' style='width:600px;height:300px'></div> +<script type='text/javascript'> +/* <![CDATA[ */ +jQuery(function () { + jQuery('#$r.getId("d3holder")').qtiStatistics('histogramScore', { + values: [$datas], + cut: #if($cutValue) $cutValue #else null #end, + xBottomLegend: '$r.translate("chart.points")', + yLeftLegend: '$r.translate("chart.percent.participants")', + yRightLegend:'$r.translate("chart.percent.participants.num")' + }); +}); +/* ]]> */</script> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_assessment.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_assessment.html index 9037ca9ea9e9dd077464ce040fce15244ca165d6..beadbbfc40f769d767d2fc44b678b30c3713a9d3 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_assessment.html +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_assessment.html @@ -11,68 +11,97 @@ } } </script> - <div id='$r.getId("print")'> - <a class="b_small_icon o_print_icon" href='javascript:$r.getId("print")()' title='$r.translateInAttribute("print")'><span>Print</span></a> - </div> + <a class="b_with_small_icon_left o_print_icon" href='javascript:$r.getId("print")()' title='$r.translateInAttribute("print")'> + <span>$r.translate("print")</span> + </a> + $r.render("download") </div> #end + <div class="b_clearfix"> <div class="b_c33l"> -<h4>$r.translate("fig.title")</h4> -$r.translate("fig.participants"): $numOfParticipants<br/> -#if($numOfPassed) -$r.translate("fig.passed"): $numOfPassed<br/> -#end -#if($numOfFailed) -$r.translate("fig.failed"): $numOfFailed<br/> -#end - -#if($maxScore) - $r.translate("chart.maxscore"): $maxScore<br/> -#end -#if($cutScore) - $r.translate("chart.cutscore"): $cutScore<br/> -#end - -$r.translate("fig.avg"): $average<br/> -$r.translate("fig.span"): $range<br/> -$r.translate("fig.stddev"): $standardDeviation<br/> -$r.translate("fig.mode"): $mode<br/> -$r.translate("fig.median"): $median<br/> -$r.translate("fig.averagedur"): $averageDuration<br/> + <h4>$r.translate("fig.title")</h4> + <table><tbody> + <tr><td>$r.translate("fig.participants")</td> + <td>$numOfParticipants</td></tr> + #if($numOfPassed) + <tr><td>$r.translate("fig.passed")</td> + <td>$numOfPassed</td></tr> + #end + #if($numOfFailed) + <tr><td>$r.translate("fig.failed")</td> + <td>$numOfFailed</td></tr> + #end + #if($maxScore) + <tr><td>$r.translate("chart.maxscore")</td> + <td>$maxScore</td></tr> + #end + #if($cutScore) + <tr><td>$r.translate("chart.cutscore")</td> + <td>$cutScore</td></tr> + #end + #if($average) + <tr><td>$r.translate("fig.avg")</td> + <td>$average</td></tr> + #end + #if($range) + <tr><td>$r.translate("fig.span")</td> + <td>$range</td></tr> + #end + #if($standardDeviation) + <tr><td>$r.translate("fig.stddev")</td> + <td>$standardDeviation</td></tr> + #end + #if($mode) + <tr><td>$r.translate("fig.mode")</td> + <td>$mode</td></tr> + #end + #if($median) + <tr><td>$r.translate("fig.median")</td> + <td>$median</td></tr> + #end + #if($averageDuration) + <tr><td>$r.translate("fig.averagedur")</td> + <td>$averageDuration</td></tr> + #end + </tbody></table> </div> <div class="b_c66r"> - - <h4>$r.translate("chart.score.histogramm")</h4> - #if($r.available("scoreHistogram")) - $r.render("scoreHistogram") - #end + #if($r.available("scoreHistogram")) + $r.render("scoreHistogram") + #end </div> </div> -<h4>$r.translate("chart.duration.histogramm")</h4> $r.render("durationHistogram") #if($r.available("averageScorePerItemChart")) <h4>$r.translate("chart.averagescore.peritem")</h4> $r.render("averageScorePerItemChart") + + <ul> + #foreach($itemInfo in $itemInfoList) + <li><strong>$itemInfo.label.</strong> $itemInfo.text</li> + #end + </ul> #end -<ul> -#foreach($itemInfo in $itemInfoList) - <li><strong>$itemInfo.label.</strong> $itemInfo.text</li> -#end -</ul> - - #if($r.available("percentRightAnswersPerItemChart")) <h4>$r.translate("chart.rightanswers.peritem")</h4> $r.render("percentRightAnswersPerItemChart") + <ul> + #foreach($itemInfo in $itemInfoList) + <li><strong>$itemInfo.label.</strong> $itemInfo.text</li> + #end + </ul> #end -<ul> -#foreach($itemInfo in $itemInfoList) - <li><strong>$itemInfo.label.</strong> $itemInfo.text</li> +#if($overviewList) + #foreach($overview in $overviewList) + $r.render($overview) + + #end #end -</ul> + + </div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item.html new file mode 100644 index 0000000000000000000000000000000000000000..380268731304f40df1a84ce84a037cb9da31e65b --- /dev/null +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item.html @@ -0,0 +1,56 @@ +<div class="o_qti_statistics"> + <h4 class="b_with_small_icon_left $series.itemCss">$title</h4> + <p>$question</p> + <table><tbody> + #if($numOfParticipants) + <tr><td>$r.translate("fig.participants")</td> + <td>$numOfParticipants</td></tr> + #end + #if($maxScore) + <tr><td>$r.translate("chart.maxscore")</td> + <td>$maxScore</td></tr> + #end + #if($rightAnswers) + <tr><td>$r.translate("fig.correctanswers")</td> + <td>$rightAnswers</td></tr> + #end + #if($wrongAnswers) + <tr><td>$r.translate("fig.wronganswers")</td> + <td>$wrongAnswers</td></tr> + #end + #if($notAnswered) + <tr><td>$r.translate("fig.notanswered")</td> + <td>$notAnswered</td></tr> + #end + #if($itemDifficulty) + <tr><td>$r.translate("fig.itemdiff")</td> + <td>$itemDifficulty</td></tr> + #end + #if($averageScore) + <tr><td>$r.translate("fig.averagescore")</td> + <td>$averageScore</td></tr> + #end + #if($averageDuration) + <tr><td>$r.translate("fig.averagedur")</td> + <td>$averageDuration</td></tr> + #end + </tbody></table> + + <h4>$r.translate("chart.responses")</h4> + $r.render("questionChart") + <ul> + #foreach($responseInfo in $series.responseInfos) + #if($responseInfo.survey) + <li class="o_qti_statistics-survey-item qti-survey"> + #elseif($responseInfo.correct) + <li class="o_qti_statistics-correct"> + #else + <li class="o_qti_statistics-ncorrect"> + #end + <strong>$responseInfo.label.</strong> $responseInfo.text + #if($responseInfo.points) $r.translate("answer.points", $responseInfo.formattedPoints) #end + #if($responseInfo.wrongAnswersAvailable) <br/>$r.translate("wrong.answer") $responseInfo.formattedWrongAnswers #end + </li> + #end + </ul> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_essai.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_essai.html similarity index 54% rename from src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_essai.html rename to src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_essai.html index f029324fa37bb7e68b0cf2739f0686eea0a15f0c..096d3917c6fd3009f603016eddb5032529779349 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_essai.html +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_essai.html @@ -1,8 +1,8 @@ <div class="o_qti_statistics"> -<h4>$title</h4> +<h4 class="b_with_small_icon_left $series.itemCss">$title</h4> <p>$question</p> #foreach($studentAnswer in $studentAnswers) -<p>$studentAnswer</p> + <p>$studentAnswer</p> #end </div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_survey.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_survey.html deleted file mode 100644 index bf19c81a2f5c9809d2a4bb8fd794661cc4e6e7d3..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_survey.html +++ /dev/null @@ -1,13 +0,0 @@ -<div class="o_qti_statistics"> -<h4>$title</h4> -<p>$question</p> -$r.translate("fig.averagedur"): $averageDuration<br/> - -<h4>$r.translate("chart.responses")</h4> -$r.render("questionChart") -<ul> -#foreach($responseInfo in $responseInfos) - <li class="qti-survey"><strong>$responseInfo.label</strong> $responseInfo.text</li> -#end -</ul> -</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_test.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_test.html deleted file mode 100644 index 9ebf699501a4c5e02bf251f829c8fba54bc0b739..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_item_test.html +++ /dev/null @@ -1,41 +0,0 @@ -<div class="o_qti_statistics"> -<h4>$title</h4> -<p>$question</p> - -#if($maxScore) - $r.translate("chart.maxscore"): $maxScore<br/> -#end -#if($rightAnswers) - $r.translate("fig.correctanswers"): $rightAnswers<br/> -#end -#if($wrongAnswers) - $r.translate("fig.wronganswers"): $wrongAnswers<br/> -#end -#if($notAnswered) - $r.translate("fig.notanswered"): $notAnswered<br/> -#end -#if($itemDifficulty) - $r.translate("fig.itemdiff"): $itemDifficulty<br/> -#end -#if($averageScore) - $r.translate("fig.averagescore"): $averageScore<br/> -#end -#if($averageDuration) - $r.translate("fig.averagedur"): $averageDuration<br/> -#end - -<h4>$r.translate("chart.responses")</h4> -$r.render("questionChart") -<ul> -#foreach($responseInfo in $responseInfos) - #if($responseInfo.survey) - <li class="o_qti_statistics-survey-item"> - #elseif($responseInfo.correct) - <li class="o_qti_statistics-correct"> - #else - <li class="o_qti_statistics-ncorrect"> - #end - <strong>$responseInfo.label.</strong> $responseInfo.text ($responseInfo.points)</li> -#end -</ul> -</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_onyx.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_onyx.html index 5270da16cb6917e6634de69875fae3467e8bcd22..f85fffe12a010320bcc488ead3030cbcb5c80a25 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_onyx.html +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_onyx.html @@ -1,4 +1,5 @@ <div class="o_qti_statistics"> + #if(!$printMode) <div class="o_buttons_box_right"> <script type="text/javascript"> @@ -18,24 +19,32 @@ #end <div class="b_clearfix"> <div class="b_c33l"> -<h4>$r.translate("fig.title")</h4> -$r.translate("fig.participants"): $numOfParticipants<br/> -$r.translate("fig.avg"): $average<br/> -$r.translate("fig.span"): $range<br/> -$r.translate("fig.stddev"): $standardDeviation<br/> -$r.translate("fig.mode"): $mode<br/> -$r.translate("fig.median"): $median<br/> -$r.translate("fig.averagedur"): $averageDuration<br/> + <h4>$r.translate("fig.title")</h4> + <table><tbody> + <tr><td>$r.translate("fig.participants")</td> + <td>$numOfParticipants</td></tr> + <tr><td>$r.translate("fig.avg")</td> + <td>$average</td></tr> + <tr><td>$r.translate("fig.span")</td> + <td>$range</td></tr> + <tr><td>$r.translate("fig.stddev")</td> + <td>$standardDeviation</td></tr> + <tr><td>$r.translate("fig.mode")</td> + <td>$mode</td></tr> + <tr><td>$r.translate("fig.median")</td> + <td>$median</td></tr> + <tr><td>$r.translate("fig.averagedur")</td> + <td>$averageDuration</td></tr> + </tbody></table> </div> <div class="b_c66r"> - - <h4>$r.translate("chart.score.histogramm")</h4> - #if($r.available("scoreHistogram")) - $r.render("scoreHistogram") - #end + #if($r.available("scoreHistogram")) + $r.render("scoreHistogram") + #end </div> </div> +<div class="b_clearfix"> + $r.render("durationHistogram") +</div> -<h4>$r.translate("chart.duration.histogramm")</h4> -$r.render("durationHistogram") </div> diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_survey.html b/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_survey.html deleted file mode 100644 index e2a88bb059922814ea3de1085bdecc6420ac9261..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_content/statistics_survey.html +++ /dev/null @@ -1,35 +0,0 @@ -<div class="o_qti_statistics"> -#if(!$printMode) - <div class="o_buttons_box_right"> - <script type="text/javascript"> - function $r.getId("print")() { - try { - var ww = window.open('$r.commandURI("print")', '$winid', 'height=400, width=800, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no'); - ww.focus(); - } catch(e) { - if(jQuery(document).ooLog().isDebugEnabled()) jQuery(document).ooLog('debug','Error when trying to print qti statistics' , ""); - } - } - </script> - <div id='$r.getId("print")'> - <a class="b_small_icon o_print_icon" href='javascript:$r.getId("print")()' title='$r.translateInAttribute("print")'><span>Print</span></a> - </div> - </div> -#end - -<div class="b_clearfix"> - <div class="b_c33l"> - <h4>$r.translate("fig.title")</h4> - $r.translate("fig.participants"): $numOfParticipants<br/> - $r.translate("fig.averagedur"): $averageDuration<br/> - </div> - <div class="b_c66r"> - <h4>$r.translate("chart.duration.histogramm")</h4> - $r.render("durationHistogram") - </div> -</div> - -<h4>$r.translate("chart.survey.overview")</h4> -$r.render("overviewSurveyBarChart") -</div> - diff --git a/src/main/java/org/olat/ims/qti/statistics/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti/statistics/ui/_i18n/LocalStrings_de.properties index acd4b53ba0181fa94cc4beb851678c94ecdd1fa2..b122af998cd5d4517a54242851c07112ae15df75 100644 --- a/src/main/java/org/olat/ims/qti/statistics/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti/statistics/ui/_i18n/LocalStrings_de.properties @@ -7,14 +7,16 @@ splash.notenoughresults=F chart.answer.averageScoreQuestions.y=Average score chart.percent.participants=% Teilnehmer - +chart.percent.participants.num=# Teilnehmer chart.score.histogramm=Score histogram -chart.duration.histogramm=Duration histogramm +chart.duration.histogramm=Bearbeitungsdauer +chart.duration.histogramm.legend=Bearbeitungsdauer (Min.) chart.averagescore.peritem=Average score per item chart.rightanswers.peritem=Percent of right answers per item chart.responses=Antworten chart.survey.overview=Ubersicht +chart.item=Item chart.points=Punkte chart.tests=Anzahl Tests @@ -26,7 +28,7 @@ chart.questions=Fragen chart.duration=Bearbeitungs-Dauer in Sekunden chart.percent=Prozent chart.maxscore=Maximale Punktzahl -chart.cutscore=Cut-Score +chart.cutscore=Notwendige Punktzahl für "bestanden" chart.title.ovpassedfailed=Bestanden / Nicht bestanden @@ -53,7 +55,9 @@ answer.false=falsch answer.noanswer=keine Antwort answer.yes=Ja answer.no=Nein - +wrong.answer=Liste of falsche Antworten: +answer.points=({0} Punkte) +download.raw.data=Raw Daten herunterladen fig.title=Kennzahlen fig.participants=Teilnehmer fig.passed=Bestanden @@ -70,3 +74,4 @@ fig.stddev=Standardabweichung fig.avg=Mittelwert fig.span=Spannweite fig.median=Median +print=Drücken diff --git a/src/main/java/org/olat/instantMessaging/ui/ChatController.java b/src/main/java/org/olat/instantMessaging/ui/ChatController.java index 5c86bba4d65bc6ce6ba4b0cf192478c674d99e7b..45c2dbee7586143947d3571f5163ae4a830cd7e4 100644 --- a/src/main/java/org/olat/instantMessaging/ui/ChatController.java +++ b/src/main/java/org/olat/instantMessaging/ui/ChatController.java @@ -85,6 +85,7 @@ public class ChatController extends BasicController implements GenericEventListe private JSAndCSSComponent jsc; private FloatingResizableDialogController chatPanelCtr; + private Date today; private List<String> allChats; private final Formatter formatter; @@ -109,6 +110,7 @@ public class ChatController extends BasicController implements GenericEventListe this.ores = ores; this.privateReceiverKey = privateReceiverKey; this.vip = vip; + setToday(); avatarBaseURL = registerCacheableMapper(ureq, "avatars-members", new AvatarMapper()); @@ -193,6 +195,15 @@ public class ChatController extends BasicController implements GenericEventListe } } + private void setToday() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + today = cal.getTime(); + } + private Date getYesterday() { Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); @@ -254,6 +265,7 @@ public class ChatController extends BasicController implements GenericEventListe } private void loadModel(Date from, int maxResults) { + setToday(); messageHistory.clear(); List<InstantMessage> lastMessages = imService.getMessages(getIdentity(), getOlatResourceable(), from, 0, maxResults, true); for(int i=lastMessages.size(); i-->0; ) { @@ -322,7 +334,15 @@ public class ChatController extends BasicController implements GenericEventListe String m = message.getBody().replaceAll("<br/>\n", "\r\n"); m = prepareMsgBody(m.replaceAll("<", "<").replaceAll(">", ">")).replaceAll("\r\n", "<br/>\n"); - String creationDate = formatter.formatTime(message.getCreationDate()); + + Date msgDate = message.getCreationDate(); + String creationDate; + if(today.compareTo(msgDate) < 0) { + creationDate = formatter.formatTime(message.getCreationDate()); + } else { + creationDate = formatter.formatDateAndTime(message.getCreationDate()); + } + String from = message.getFromNickName(); synchronized (messageHistory) { 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 8150fa69f325d36b089a77f9d6c0e6c2a5059a97..1db98dc378454b73807749b0eb0d68889a222b3e 100644 --- a/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java +++ b/src/main/java/org/olat/modules/scorm/assessment/ScormResultDetailsController.java @@ -170,12 +170,12 @@ public class ScormResultDetailsController extends BasicController { String username = userCourseEnvironment.getIdentityEnvironment().getIdentity().getName(); CourseEnvironment courseEnv = userCourseEnvironment.getCourseEnvironment(); ScormAssessmentManager.getInstance().deleteResults(username, courseEnv, node); - fireEvent(ureq, Event.CHANGED_EVENT); + fireEvent(ureq, Event.DONE_EVENT); } } } - public class CmiTableDataModel extends BaseTableDataModelWithoutFilter<CmiData> { + public static class CmiTableDataModel extends BaseTableDataModelWithoutFilter<CmiData> { private final List<CmiData> datas; private final Translator translator; private final Pattern pattern = Pattern.compile("[0-9]"); @@ -242,7 +242,7 @@ public class ScormResultDetailsController extends BasicController { * Initial Date: 07.01.2010 <br> * @author thomasw */ - public class SummaryTableDataModelMultiResults implements TableDataModel<List<CmiData>> { + public static class SummaryTableDataModelMultiResults implements TableDataModel<List<CmiData>> { private final Map<Date, List<CmiData>> objects; diff --git a/src/main/java/org/olat/portfolio/ui/EPArtefactPoolRunController.java b/src/main/java/org/olat/portfolio/ui/EPArtefactPoolRunController.java index 9aae9a47071145a90a757d20ebc34c3e021bd08c..02793e274f037a43f782efe4af37f4a4dcbee996 100755 --- a/src/main/java/org/olat/portfolio/ui/EPArtefactPoolRunController.java +++ b/src/main/java/org/olat/portfolio/ui/EPArtefactPoolRunController.java @@ -262,6 +262,7 @@ public class EPArtefactPoolRunController extends BasicController implements Acti // some artefacts were added, refresh view if (event.equals(Event.DONE_EVENT)) { initTPAllView(ureq); + fireEvent(ureq, event); } } else if (event instanceof EPArtefactChoosenEvent) { // an artefact was choosen, pass through the event until top diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewController.java index 8f83a36c183b0808596a3a0a3550261341862890..211c0c576e7d2a861540d25ba4e7665ded1405d2 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewController.java +++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/EPArtefactViewController.java @@ -26,7 +26,6 @@ package org.olat.portfolio.ui.artefacts.view; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -65,6 +64,7 @@ import org.olat.portfolio.model.structel.PortfolioStructure; import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm00; import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm03; import org.olat.portfolio.ui.artefacts.collect.EPReflexionChangeEvent; +import org.olat.resource.OLATResource; import org.olat.util.logging.activity.LoggingResourceable; /** @@ -203,20 +203,15 @@ public class EPArtefactViewController extends FormBasicController { // get maps wherein this artefact is linked and create links to them List<PortfolioStructure> linkedMaps = ePFMgr.getReferencedMapsForArtefact(artefact); if (linkedMaps != null && linkedMaps.size() != 0) { - StringBuilder buf = new StringBuilder(); - for (Iterator<PortfolioStructure> iterator = linkedMaps.iterator(); iterator.hasNext();) { - PortfolioStructure ePMap = iterator.next(); - if (viewOnlyMode || artefactChooseMode){ - StringHelper.escapeHtml(ePMap.getTitle()); - buf.append(", "); - } else { - buf.append("<a href=\"").append(createLinkToMap(ePMap)).append("\">"); - StringHelper.escapeHtml(ePMap.getTitle()); - buf.append("</a>, "); - } + List<FormLink> selectMapNames = new ArrayList<FormLink>(linkedMaps.size()); + for (PortfolioStructure ePMap : linkedMaps) { + String title = StringHelper.escapeHtml(ePMap.getTitle()); + FormLink selectMap = uifactory.addFormLink("map", "map", title, null, formLayout, Link.NONTRANSLATED); + selectMap.setUserObject(ePMap.getOlatResource()); + selectMap.setEnabled(!viewOnlyMode && !artefactChooseMode); + selectMapNames.add(selectMap); } - String mapLinks = buf.toString(); - flc.contextPut("maps", mapLinks.substring(0, mapLinks.length() - 2)); + flc.contextPut("maps", selectMapNames); } // build link to original source @@ -276,13 +271,10 @@ public class EPArtefactViewController extends FormBasicController { flc.contextPut("artAttribConfig", attribConfig); } - private String createLinkToMap(PortfolioStructure ePMap) { - BusinessControlFactory bCF = BusinessControlFactory.getInstance(); - ContextEntry mapCE = bCF.createContextEntry(ePMap.getOlatResource()); - ArrayList<ContextEntry> cEList = new ArrayList<ContextEntry>(); - cEList.add(mapCE); - String busLink = bCF.getAsURIString(cEList, true); - return busLink; + private void doOpenLinkToMap(UserRequest ureq, OLATResource mapResource) { + String businessPath = "[" + mapResource.getResourceableTypeName() + ":" + + mapResource.getResourceableId() + "]"; + NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } private String createLinkToArtefactSource(UserRequest ureq, String businessPath){ @@ -317,6 +309,12 @@ public class EPArtefactViewController extends FormBasicController { } else if(source == tblE){ List<String> actualTags = tblE.getValueList(); ePFMgr.setArtefactTags(getIdentity(), artefact, actualTags); + } else if(source instanceof FormLink) { + FormLink link = (FormLink)source; + if("map".equals(link.getCmd())) { + OLATResource map = (OLATResource)link.getUserObject(); + doOpenLinkToMap(ureq, map); + } } } diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/singleArtefact.html b/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/singleArtefact.html index 3529f592a300866fb35181be6d593a185142b79a..83b5dfa21db57321c7c1a7bd60efdbc21f9a5fa5 100644 --- a/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/singleArtefact.html +++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/_content/singleArtefact.html @@ -59,7 +59,11 @@ #if ($artAttribConfig.get("artefact.used.in.maps")) <div class="b_form_element_wrapper b_clearfix"> <div class="b_form_element_label">$r.translate("artefact.used.in.maps")</div> - <div class="b_form_element">$!maps</div> + <div class="b_form_element"> + #foreach($map in $maps) + $r.render($map.component.componentName) + #end + </div> </div> #end #if ($artAttribConfig.get("artefact.reflexion")) diff --git a/src/main/java/org/olat/portfolio/ui/structel/EPAddElementsController.java b/src/main/java/org/olat/portfolio/ui/structel/EPAddElementsController.java index 3a8a3f1a381caa479406f8f5f75f4e74da861e61..57429a2c48e6e7c8108f1248fb354bac0b30e976 100644 --- a/src/main/java/org/olat/portfolio/ui/structel/EPAddElementsController.java +++ b/src/main/java/org/olat/portfolio/ui/structel/EPAddElementsController.java @@ -133,28 +133,32 @@ public class EPAddElementsController extends BasicController { @Override protected void event(UserRequest ureq, Controller source, Event event) { super.event(ureq, source, event); - if (source == artefactPoolCtrl && event instanceof EPArtefactChoosenEvent) { - // finally an artefact was choosen - EPArtefactChoosenEvent artCEv = (EPArtefactChoosenEvent) event; - artefactBox.deactivate(); - AbstractArtefact choosenArtefact = artCEv.getArtefact(); - // check for a yet existing link to this artefact - if (ePFMgr.isArtefactInStructure(choosenArtefact, portfolioStructure)) { - showWarning("artefact.already.in.structure"); - } else { - boolean successfullLink = ePFMgr.addArtefactToStructure(getIdentity(), choosenArtefact, portfolioStructure); - if (successfullLink) { - getWindowControl().setInfo( - getTranslator().translate("artefact.choosen", new String[] { choosenArtefact.getTitle(), portfolioStructure.getTitle() })); - ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapPortfolioOres(choosenArtefact)); - ThreadLocalUserActivityLogger.log(EPLoggingAction.EPORTFOLIO_ARTEFACT_SELECTED, getClass()); + if (source == artefactPoolCtrl) { + if(event instanceof EPArtefactChoosenEvent) { + // finally an artefact was choosen + EPArtefactChoosenEvent artCEv = (EPArtefactChoosenEvent) event; + artefactBox.deactivate(); + AbstractArtefact choosenArtefact = artCEv.getArtefact(); + // check for a yet existing link to this artefact + if (ePFMgr.isArtefactInStructure(choosenArtefact, portfolioStructure)) { + showWarning("artefact.already.in.structure"); } else { - showError("restrictions.not.conform"); + boolean successfullLink = ePFMgr.addArtefactToStructure(getIdentity(), choosenArtefact, portfolioStructure); + if (successfullLink) { + getWindowControl().setInfo( + getTranslator().translate("artefact.choosen", new String[] { choosenArtefact.getTitle(), portfolioStructure.getTitle() })); + ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapPortfolioOres(choosenArtefact)); + ThreadLocalUserActivityLogger.log(EPLoggingAction.EPORTFOLIO_ARTEFACT_SELECTED, getClass()); + } else { + showError("restrictions.not.conform"); + } + fireEvent(ureq, new EPStructureChangeEvent(EPStructureChangeEvent.ADDED, portfolioStructure)); } + } else if(event == Event.DONE_EVENT) { + artefactBox.deactivate(); fireEvent(ureq, new EPStructureChangeEvent(EPStructureChangeEvent.ADDED, portfolioStructure)); } - - } + } } public void setShowLink(String... types) { diff --git a/src/main/java/org/olat/repository/controllers/RepositoryEntryMarkCellRenderer.java b/src/main/java/org/olat/repository/controllers/RepositoryEntryMarkCellRenderer.java deleted file mode 100644 index a251ea42ab6d22da7e7398cd1feea3cda36a2f64..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/repository/controllers/RepositoryEntryMarkCellRenderer.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * <a href="http://www.openolat.org"> - * OpenOLAT - Online Learning and Training</a><br> - * <p> - * Licensed under the Apache License, Version 2.0 (the "License"); <br> - * you may not use this file except in compliance with the License.<br> - * You may obtain a copy of the License at the - * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> - * <p> - * Unless required by applicable law or agreed to in writing,<br> - * software distributed under the License is distributed on an "AS IS" BASIS, <br> - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> - * See the License for the specific language governing permissions and <br> - * limitations under the License. - * <p> - * Initial code contributed and copyrighted by<br> - * frentix GmbH, http://www.frentix.com - * <p> - */ -package org.olat.repository.controllers; - -import java.util.Locale; -import java.util.UUID; - -import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.components.link.LinkFactory; -import org.olat.core.gui.components.table.CustomCellRenderer; -import org.olat.core.gui.components.velocity.VelocityContainer; -import org.olat.core.gui.control.Controller; -import org.olat.core.gui.render.RenderResult; -import org.olat.core.gui.render.Renderer; -import org.olat.core.gui.render.StringOutput; -import org.olat.core.gui.render.URLBuilder; -import org.olat.core.gui.translator.Translator; -import org.olat.repository.RepositoryEntry; - -/** - * - * Render to mark a group - * - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - */ -public class RepositoryEntryMarkCellRenderer implements CustomCellRenderer { - - private final Translator translator; - private final VelocityContainer container; - private final Controller listeningController; - - public RepositoryEntryMarkCellRenderer(Controller listeningController, VelocityContainer container, Translator translator) { - this.listeningController = listeningController; - this.container = container; - this.translator = translator; - } - - @Override - public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) { - if(val instanceof RepositoryEntry && renderer != null) { - RepositoryEntry item = (RepositoryEntry)val; - sb.append("<div class='b_mark'>"); - - Link link = LinkFactory.createLink("marked_" + UUID.randomUUID().toString(), container, listeningController); - link.setCustomDisplayText("   "); - if(false /*item.isMarked()*/) { - link.setCustomEnabledLinkCSS("b_mark_set"); - } else { - link.setCustomEnabledLinkCSS("b_mark_not_set"); - } - link.setUserObject(item); - URLBuilder ubu = renderer.getUrlBuilder().createCopyFor(link); - RenderResult renderResult = new RenderResult(); - link.getHTMLRendererSingleton().render(renderer, sb, link, ubu, translator, renderResult, null); - sb.append("</div>"); - } - } -} diff --git a/src/main/java/org/olat/repository/controllers/WizardCloseCourseController.java b/src/main/java/org/olat/repository/controllers/WizardCloseCourseController.java index d0ef095fba1d423263e59c66e454a923031925ad..3324306212e74df2b21c19d22eae03cfce152166 100644 --- a/src/main/java/org/olat/repository/controllers/WizardCloseCourseController.java +++ b/src/main/java/org/olat/repository/controllers/WizardCloseCourseController.java @@ -155,7 +155,7 @@ public class WizardCloseCourseController extends WizardController implements Wiz + ureq.getIdentity().getUser().getProperty(UserConstants.LASTNAME, null) })); if(mailNotificationCtr != null) mailNotificationCtr.dispose(); - mailNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false); + mailNotificationCtr = new MailNotificationEditController(getWindowControl(), ureq, mailTempl, false, false, true); mailNotificationCtr.addControllerListener(this); sendNotificationVC = createVelocityContainer("sendnotification"); sendNotificationVC.put("notificationForm", mailNotificationCtr.getInitialComponent()); diff --git a/src/main/java/org/olat/repository/delete/SelectionController.java b/src/main/java/org/olat/repository/delete/SelectionController.java index c9466cbe6525cdaaee67abd41dbf8ca87f2525c0..acfe28491a2ed140f7421881eae28bafb0bd430c 100644 --- a/src/main/java/org/olat/repository/delete/SelectionController.java +++ b/src/main/java/org/olat/repository/delete/SelectionController.java @@ -90,7 +90,6 @@ public class SelectionController extends BasicController { private final RepositoryService repositoryService; - /** * @param ureq * @param wControl @@ -205,7 +204,7 @@ public class SelectionController extends BasicController { deleteMailTemplate.addToContext("durationdeleteemail", Integer.toString(RepositoryDeletionManager.getInstance().getDeleteEmailDuration() )); removeAsListenerAndDispose(deleteRepositoryMailCtr); - deleteRepositoryMailCtr = new MailNotificationEditController(getWindowControl(), ureq, deleteMailTemplate, true, false); + deleteRepositoryMailCtr = new MailNotificationEditController(getWindowControl(), ureq, deleteMailTemplate, true, false, true); listenTo(deleteRepositoryMailCtr); removeAsListenerAndDispose(cmc); diff --git a/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.js b/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.js new file mode 100644 index 0000000000000000000000000000000000000000..01daea1e31f8e76848e0769cd7cd64365ca35857 --- /dev/null +++ b/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.js @@ -0,0 +1,658 @@ +(function ( $ ) { + $.fn.qtiStatistics = function(type, options) { + var settings = $.extend({ + values: [], + colors: [], + cut: null, + participants: -1, + barHeight: 40, + xTopLegend: 'x Top', + xBottomLegend: 'x Bottom', + yLeftLegend: 'y Left', + yRightLegend: 'y Right' + }, options ); + + try { + if(type == 'histogramScore') { + histogramScore(this, settings); + } else if(type == 'histogramDuration') { + histogramDuration(this, settings); + } else if(type == 'horizontalBarSingleChoice') { + horizontalBarSingleChoice(this, settings); + } else if(type == 'rightAnswerPerItem') { + rightAnswerPerItem(this, settings); + } else if(type == 'averageScorePerItem') { + averageScorePerItem(this, settings); + } else if(type == 'horizontalBarMultipleChoice') { + horizontalBarMultipleChoice(this, settings); + } else if(type == 'horizontalBarMultipleChoiceSurvey') { + horizontalBarMultipleChoiceSurvey(this, settings); + } + } catch(e) { + if(console) console.log(e); + } + return this; + }; + + averageScorePerItem = function($obj, settings) { + var placeholderheight = $obj.height(); + var placeholderwidth = $obj.width(); + var data = settings.values; + + var margin = {top: 10, right: 60, bottom: 40, left: 60}, + width = placeholderwidth - margin.left - margin.right, + height = placeholderheight - margin.top - margin.bottom; + + var x = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d[1]; })]) + .range([0, width]); + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom') + .ticks(10); + + var y = d3.scale.ordinal() + .domain(data.map(function(d) { return d[0]; })) + .rangeRoundBands([height, 0]); + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis) .append('text') + .attr('y', (margin.bottom / 1.7)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.selectAll('.bar0') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar0 bar_default') + .attr('fill', 'bar_default') + .attr('x', 0) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[1]); }) + .attr('height', y.rangeBand() - 4); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - (margin.right / 1.7)) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + } + + rightAnswerPerItem = function($obj, settings) { + var placeholderheight = $obj.height(); + var placeholderwidth = $obj.width(); + + var data = settings.values; + + var margin = {top: 40, right: 60, bottom: 40, left: 60}, + width = placeholderwidth - margin.left - margin.right, + height = placeholderheight - margin.top - margin.bottom; + + var sum = d3.sum(data, function(d) { return d[1]; }); + + var x = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d[1]; })]) + .range([0, width]); + var xAxis = d3.svg.axis() + .scale(x) + .orient('top') + .ticks(10); + + var x2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d[1] / sum; })]) + .range([0, width]); + var x2Axis = d3.svg.axis() + .scale(x2) + .orient('bottom') + .ticks(10, '%'); + + var y = d3.scale.ordinal() + .domain(data.map(function(d) { return d[0]; })) + .rangeRoundBands([height, 0]); + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,0)') + .call(xAxis) .append('text') + .attr('y', 0 - (margin.top / 1.1)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(x2Axis) .append('text') + .attr('y', (margin.bottom / 1.7)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xTopLegend); + + svg.selectAll('.bar0') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar0 bar_green') + .attr('fill', 'bar_green') + .attr('x', 0) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[1]); }) + .attr('height', y.rangeBand() - 4); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - (margin.right / 1.7)) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + }; + + horizontalBarMultipleChoiceSurvey = function($obj, settings) { + var placeholderwidth = $obj.width(); + + var data = settings.values; + var colors = settings.colors; + + var margin = {top: 40, right: 10, bottom: 40, left: 40}; + var height = data.length * settings.barHeight; + $obj.height(height + margin.top + margin.bottom + 'px'); + var width = placeholderwidth - margin.left - margin.right; + + var sum = settings.participants; + var max = d3.max(data, function(d) { return d[1]; }); + + var x = d3.scale.linear() + .domain([0, max]) + .range([0, width]); + var xAxis = d3.svg.axis() + .scale(x) + .orient('top') + .ticks(max); + + var x2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return (d[1]) / sum; })]) + .range([0, width]); + var x2Axis = d3.svg.axis() + .scale(x2) + .orient('bottom') + .ticks(10, '%'); + + var y = d3.scale.ordinal() + .domain(data.map(function(d) { return d[0]; })) + .rangeRoundBands([height, 0]); + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,0)') + .call(xAxis) .append('text') + .attr('y', 0 - (margin.top / 1.1)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xTopLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(x2Axis) .append('text') + .attr('y', (margin.bottom / 1.7)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - margin.left) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + + svg.selectAll('.bar0') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar_default') + .attr('fill', 'bar_default') + .attr('x', 0) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[1]); }) + .attr('height', y.rangeBand() - 4); + }; + + horizontalBarMultipleChoice = function($obj, settings) { + var placeholderwidth = $obj.width(); + + var data = settings.values; + var colors = settings.colors; + + var margin = {top: 40, right: 10, bottom: 40, left: 40}; + var height = data.length * settings.barHeight; + $obj.height(height + margin.top + margin.bottom + 'px'); + var width = placeholderwidth - margin.left - margin.right; + + var sum = settings.participants; + var max = d3.max(data, function(d) { return d[1] + d[2] + d[3]; }); + + var x = d3.scale.linear() + .domain([0, max]) + .range([0, width]); + var xAxis = d3.svg.axis() + .scale(x) + .orient('top') + .ticks(max); + + var x2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return (d[1] + d[2] + d[3]) / sum; })]) + .range([0, width]); + var x2Axis = d3.svg.axis() + .scale(x2) + .orient('bottom') + .ticks(10, '%'); + + var y = d3.scale.ordinal() + .domain(data.map(function(d) { return d[0]; })) + .rangeRoundBands([height, 0]); + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,0)') + .call(xAxis) .append('text') + .attr('y', 0 - (margin.top / 1.1)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xTopLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(x2Axis) .append('text') + .attr('y', (margin.bottom / 1.7)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomlegend); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - margin.left) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + + svg.selectAll('.bar0') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar_green') + .attr('fill', 'bar_green') + .attr('x', 0) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[1]); }) + .attr('height', y.rangeBand() - 4); + + svg.selectAll('.bar1') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar_red') + .attr('fill', 'bar_red') + .attr('x', function(d) { return x(d[1]); }) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[2]); }) + .attr('height', y.rangeBand() - 4); + + svg.selectAll('.bar2') + .data(data) + .enter().append('rect') + .attr('class', 'bar bar_grey') + .attr('fill', 'bar_grey') + .attr('x', function(d) { return x(d[1] + d[2]); }) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[3]); }) + .attr('height', y.rangeBand() - 4); + }; + + horizontalBarSingleChoice = function($obj, settings) { + var placeholderwidth = $obj.width(); + + var data = settings.values; + var colors = settings.colors; + + var margin = {top: 40, right: 10, bottom: 40, left: 40}; + var height = data.length * settings.barHeight; + $obj.height(height + margin.top + margin.bottom + 'px'); + var width = placeholderwidth - margin.left - margin.right; + + var sum = d3.sum(data, function(d) { return d[1]; }); + var max = d3.max(data, function(d) { return d[1]; }); + + var x = d3.scale.linear() + .domain([0, max]) + .range([0, width]); + var xAxis = d3.svg.axis() + .scale(x) + .orient('top') + .ticks(max); + + var x2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d[1] / sum; })]) + .range([0, width]); + var x2Axis = d3.svg.axis() + .scale(x2) + .orient('bottom') + .ticks(10, '%'); + + var y = d3.scale.ordinal() + .domain(data.map(function(d) { return d[0]; })) + .rangeRoundBands([height, 0]); + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,0)') + .call(xAxis) + .append('text') + .attr('y', 0 - (margin.top / 1.1)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xTopLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(x2Axis) .append('text') + .attr('y', (margin.bottom / 1.7)) + .attr('x', (width / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.selectAll('.bar0') + .data(data) + .enter().append('rect') + .attr('class', function(d, i){ + if(colors == null) return 'bar bar0 bar_default'; + else if(colors.length > i) return colors[i]; + else return 'bar bar0 bar_default'; + }) + .attr('fill', 'bar_green') + .attr('x', 0) + .attr('y', function(d) { return y(d[0]) + 2; }) + .attr('width', function(d) { return x(d[1]); }) + .attr('height', y.rangeBand() - 4); + + svg.append('g') + .attr('class', 'y axis') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - margin.left) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + }; + + histogramDuration = function($obj, settings) { + var placeholderheight = $obj.height(); + var placeholderwidth = $obj.width(); + + var values = settings.values; + var formatCount = d3.format(',.f'), + formatTime = d3.time.format('%H:%M'), + formatMinutes = function(d) { return formatTime(new Date(2012, 0, 1, 0, d)); }; + + var margin = {top: 10, right: 60, bottom: 40, left: 60}, + width = placeholderwidth - margin.left - margin.right, + height = placeholderheight - margin.top - margin.bottom; + + var x = d3.scale.linear() + .domain([0, 4.0]) + .range([0, width]); + + var data = d3.layout.histogram() + .bins(x.ticks(20)) + (values); + + var sum = d3.sum(data, function(d) { return d.y; }); + var y = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d.y; })]) + .range([height, 0]); + + var y2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d.y / sum; })]) + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom') + .tickFormat(formatMinutes); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('right') + .ticks(y.domain()[1]).tickSubdivide(0); + + var y2Axis = d3.svg.axis() + .scale(y2) + .orient('left') + .ticks(10, '%'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var bar = svg.selectAll('.bar') + .data(data) + .enter().append('g') + .attr('class', 'bar bar_default') + .attr('transform', function(d) { return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; }) + .append('rect') + .attr('x', 2) + .attr('width', x(data[0].dx) - 4) + .attr('height', function(d) { return height - y(d.y); }); + + svg.append('g') + .attr('class', 'y axis') + .call(y2Axis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - margin.left) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis) + .append('text') + .attr('y', (margin.bottom / 1.1)) + .attr('x', (width / 2)) + .attr('dx', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.append('g') + .attr('class', 'y axis') + .attr('transform', 'translate(' + width + ',0)') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(90)') + .attr('y', 0 - (margin.right)) + .attr('x', (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + }; + + histogramScore = function($obj, settings) { + var placeholderheight = $obj.height(); + var placeholderwidth = $obj.width(); + var values = settings.values; + + var margin = {top: 10, right: 60, bottom: 40, left: 60}, + width = placeholderwidth - margin.left - margin.right, + height = placeholderheight - margin.top - margin.bottom; + + var cut = settings.cut; + + var x = d3.scale.linear() + .domain([0, 3.0]) + .range([0, width]); + + var data = d3.layout.histogram() + .bins(x.ticks(20)) + (values); + + var sum = d3.sum(data, function(d) { return d.y; }); + + var y = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d.y; })]) + .range([height, 0]); + + var y2 = d3.scale.linear() + .domain([0, d3.max(data, function(d) { return d.y / sum; })]) + .range([height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom') + .tickFormat(d3.format('.01f')); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('right') + .ticks(y.domain()[1]).tickSubdivide(0); + + var y2Axis = d3.svg.axis() + .scale(y2) + .orient('left') + .ticks(10, '%'); + + var svg = d3.select('#' + $obj.attr('id')).append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var bar = svg.selectAll('.bar') + .data(data) + .enter().append('g') + .attr('class', function(d, i) { + if(cut == null) return 'bar bar_default'; + else if(data[i].x < cut) return 'bar bar_red'; + else return 'bar bar_green'; + }) + .attr('transform', function(d) { return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; }) + .append('rect') + .attr('x', 2) + .attr('width', x(data[0].dx) - 4) + .attr('height', function(d) { return height - y(d.y); }); + + svg.append('g') + .attr('class', 'y axis') + .call(y2Axis) + .append('text') + .attr('transform', 'rotate(-90)') + .attr('y', 0 - margin.left) + .attr('x', 0 - (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yLeftLegend); + + svg.append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0,' + height + ')') + .call(xAxis) + .append('text') + .attr('y', (margin.bottom / 1.1)) + .attr('x', (width / 2)) + .attr('dx', '1em') + .style('text-anchor', 'middle') + .text(settings.xBottomLegend); + + svg.append('g') + .attr('class', 'y axis') + .attr('transform', 'translate(' + width + ',0)') + .call(yAxis) + .append('text') + .attr('transform', 'rotate(90)') + .attr('y', 0 - (margin.right)) + .attr('x', (height / 2)) + .attr('dy', '1em') + .style('text-anchor', 'middle') + .text(settings.yRightLegend); + } + + +}( jQuery )); \ No newline at end of file diff --git a/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.min.js b/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.min.js new file mode 100644 index 0000000000000000000000000000000000000000..2f443e233ecb393c2474ba00c5c0d90275f2e49f --- /dev/null +++ b/src/main/webapp/static/js/jquery/openolat/jquery.statistics.chart.min.js @@ -0,0 +1 @@ +(function(a){a.fn.qtiStatistics=function(d,b){var c=a.extend({values:[],colors:[],cut:null,participants:-1,barHeight:40,xTopLegend:"x Top",xBottomLegend:"x Bottom",yLeftLegend:"y Left",yRightLegend:"y Right"},b);try{if(d=="histogramScore"){histogramScore(this,c)}else{if(d=="histogramDuration"){histogramDuration(this,c)}else{if(d=="horizontalBarSingleChoice"){horizontalBarSingleChoice(this,c)}else{if(d=="rightAnswerPerItem"){rightAnswerPerItem(this,c)}else{if(d=="averageScorePerItem"){averageScorePerItem(this,c)}else{if(d=="horizontalBarMultipleChoice"){horizontalBarMultipleChoice(this,c)}else{if(d=="horizontalBarMultipleChoiceSurvey"){horizontalBarMultipleChoiceSurvey(this,c)}}}}}}}}catch(f){if(console){console.log(f)}}return this};averageScorePerItem=function(b,f){var m=b.height();var j=b.width();var h=f.values;var g={top:10,right:60,bottom:40,left:60},d=j-g.left-g.right,n=m-g.top-g.bottom;var l=d3.scale.linear().domain([0,d3.max(h,function(o){return o[1]})]).range([0,d]);var e=d3.svg.axis().scale(l).orient("bottom").ticks(10);var k=d3.scale.ordinal().domain(h.map(function(o){return o[0]})).rangeRoundBands([n,0]);var c=d3.svg.axis().scale(k).orient("left");var i=d3.select("#"+b.attr("id")).append("svg").attr("width",d+g.left+g.right).attr("height",n+g.top+g.bottom).append("g").attr("transform","translate("+g.left+","+g.top+")");i.append("g").attr("class","x axis").attr("transform","translate(0,"+n+")").call(e).append("text").attr("y",(g.bottom/1.7)).attr("x",(d/2)).attr("dy","1em").style("text-anchor","middle").text(f.xBottomLegend);i.selectAll(".bar0").data(h).enter().append("rect").attr("class","bar bar0 bar_default").attr("fill","bar_default").attr("x",0).attr("y",function(o){return k(o[0])+2}).attr("width",function(o){return l(o[1])}).attr("height",k.rangeBand()-4);i.append("g").attr("class","y axis").call(c).append("text").attr("transform","rotate(-90)").attr("y",0-(g.right/1.7)).attr("x",0-(n/2)).attr("dy","1em").style("text-anchor","middle").text(f.yLeftLegend)};rightAnswerPerItem=function(c,h){var p=c.height();var l=c.width();var j=h.values;var i={top:40,right:60,bottom:40,left:60},f=l-i.left-i.right,q=p-i.top-i.bottom;var m=d3.sum(j,function(r){return r[1]});var o=d3.scale.linear().domain([0,d3.max(j,function(r){return r[1]})]).range([0,f]);var g=d3.svg.axis().scale(o).orient("top").ticks(10);var e=d3.scale.linear().domain([0,d3.max(j,function(r){return r[1]/m})]).range([0,f]);var b=d3.svg.axis().scale(e).orient("bottom").ticks(10,"%");var n=d3.scale.ordinal().domain(j.map(function(r){return r[0]})).rangeRoundBands([q,0]);var d=d3.svg.axis().scale(n).orient("left");var k=d3.select("#"+c.attr("id")).append("svg").attr("width",f+i.left+i.right).attr("height",q+i.top+i.bottom).append("g").attr("transform","translate("+i.left+","+i.top+")");k.append("g").attr("class","x axis").attr("transform","translate(0,0)").call(g).append("text").attr("y",0-(i.top/1.1)).attr("x",(f/2)).attr("dy","1em").style("text-anchor","middle").text(h.xBottomLegend);k.append("g").attr("class","x axis").attr("transform","translate(0,"+q+")").call(b).append("text").attr("y",(i.bottom/1.7)).attr("x",(f/2)).attr("dy","1em").style("text-anchor","middle").text(h.xTopLegend);k.selectAll(".bar0").data(j).enter().append("rect").attr("class","bar bar0 bar_green").attr("fill","bar_green").attr("x",0).attr("y",function(r){return n(r[0])+2}).attr("width",function(r){return o(r[1])}).attr("height",n.rangeBand()-4);k.append("g").attr("class","y axis").call(d).append("text").attr("transform","rotate(-90)").attr("y",0-(i.right/1.7)).attr("x",0-(q/2)).attr("dy","1em").style("text-anchor","middle").text(h.yLeftLegend)};horizontalBarMultipleChoiceSurvey=function(c,i){var m=c.width();var k=i.values;var d=i.colors;var j={top:40,right:10,bottom:40,left:40};var r=k.length*i.barHeight;c.height(r+j.top+j.bottom+"px");var g=m-j.left-j.right;var n=i.participants;var p=d3.max(k,function(s){return s[1]});var q=d3.scale.linear().domain([0,p]).range([0,g]);var h=d3.svg.axis().scale(q).orient("top").ticks(p);var f=d3.scale.linear().domain([0,d3.max(k,function(s){return(s[1])/n})]).range([0,g]);var b=d3.svg.axis().scale(f).orient("bottom").ticks(10,"%");var o=d3.scale.ordinal().domain(k.map(function(s){return s[0]})).rangeRoundBands([r,0]);var e=d3.svg.axis().scale(o).orient("left");var l=d3.select("#"+c.attr("id")).append("svg").attr("width",g+j.left+j.right).attr("height",r+j.top+j.bottom).append("g").attr("transform","translate("+j.left+","+j.top+")");l.append("g").attr("class","x axis").attr("transform","translate(0,0)").call(h).append("text").attr("y",0-(j.top/1.1)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xTopLegend);l.append("g").attr("class","x axis").attr("transform","translate(0,"+r+")").call(b).append("text").attr("y",(j.bottom/1.7)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xBottomLegend);l.append("g").attr("class","y axis").call(e).append("text").attr("transform","rotate(-90)").attr("y",0-j.left).attr("x",0-(r/2)).attr("dy","1em").style("text-anchor","middle").text(i.yLeftLegend);l.selectAll(".bar0").data(k).enter().append("rect").attr("class","bar bar_default").attr("fill","bar_default").attr("x",0).attr("y",function(s){return o(s[0])+2}).attr("width",function(s){return q(s[1])}).attr("height",o.rangeBand()-4)};horizontalBarMultipleChoice=function(c,i){var m=c.width();var k=i.values;var d=i.colors;var j={top:40,right:10,bottom:40,left:40};var r=k.length*i.barHeight;c.height(r+j.top+j.bottom+"px");var g=m-j.left-j.right;var n=i.participants;var p=d3.max(k,function(s){return s[1]+s[2]+s[3]});var q=d3.scale.linear().domain([0,p]).range([0,g]);var h=d3.svg.axis().scale(q).orient("top").ticks(p);var f=d3.scale.linear().domain([0,d3.max(k,function(s){return(s[1]+s[2]+s[3])/n})]).range([0,g]);var b=d3.svg.axis().scale(f).orient("bottom").ticks(10,"%");var o=d3.scale.ordinal().domain(k.map(function(s){return s[0]})).rangeRoundBands([r,0]);var e=d3.svg.axis().scale(o).orient("left");var l=d3.select("#"+c.attr("id")).append("svg").attr("width",g+j.left+j.right).attr("height",r+j.top+j.bottom).append("g").attr("transform","translate("+j.left+","+j.top+")");l.append("g").attr("class","x axis").attr("transform","translate(0,0)").call(h).append("text").attr("y",0-(j.top/1.1)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xTopLegend);l.append("g").attr("class","x axis").attr("transform","translate(0,"+r+")").call(b).append("text").attr("y",(j.bottom/1.7)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xBottomlegend);l.append("g").attr("class","y axis").call(e).append("text").attr("transform","rotate(-90)").attr("y",0-j.left).attr("x",0-(r/2)).attr("dy","1em").style("text-anchor","middle").text(i.yLeftLegend);l.selectAll(".bar0").data(k).enter().append("rect").attr("class","bar bar_green").attr("fill","bar_green").attr("x",0).attr("y",function(s){return o(s[0])+2}).attr("width",function(s){return q(s[1])}).attr("height",o.rangeBand()-4);l.selectAll(".bar1").data(k).enter().append("rect").attr("class","bar bar_red").attr("fill","bar_red").attr("x",function(s){return q(s[1])}).attr("y",function(s){return o(s[0])+2}).attr("width",function(s){return q(s[2])}).attr("height",o.rangeBand()-4);l.selectAll(".bar2").data(k).enter().append("rect").attr("class","bar bar_grey").attr("fill","bar_grey").attr("x",function(s){return q(s[1]+s[2])}).attr("y",function(s){return o(s[0])+2}).attr("width",function(s){return q(s[3])}).attr("height",o.rangeBand()-4)};horizontalBarSingleChoice=function(c,i){var m=c.width();var k=i.values;var d=i.colors;var j={top:40,right:10,bottom:40,left:40};var r=k.length*i.barHeight;c.height(r+j.top+j.bottom+"px");var g=m-j.left-j.right;var n=d3.sum(k,function(s){return s[1]});var p=d3.max(k,function(s){return s[1]});var q=d3.scale.linear().domain([0,p]).range([0,g]);var h=d3.svg.axis().scale(q).orient("top").ticks(p);var f=d3.scale.linear().domain([0,d3.max(k,function(s){return s[1]/n})]).range([0,g]);var b=d3.svg.axis().scale(f).orient("bottom").ticks(10,"%");var o=d3.scale.ordinal().domain(k.map(function(s){return s[0]})).rangeRoundBands([r,0]);var e=d3.svg.axis().scale(o).orient("left");var l=d3.select("#"+c.attr("id")).append("svg").attr("width",g+j.left+j.right).attr("height",r+j.top+j.bottom).append("g").attr("transform","translate("+j.left+","+j.top+")");l.append("g").attr("class","x axis").attr("transform","translate(0,0)").call(h).append("text").attr("y",0-(j.top/1.1)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xTopLegend);l.append("g").attr("class","x axis").attr("transform","translate(0,"+r+")").call(b).append("text").attr("y",(j.bottom/1.7)).attr("x",(g/2)).attr("dy","1em").style("text-anchor","middle").text(i.xBottomLegend);l.selectAll(".bar0").data(k).enter().append("rect").attr("class",function(t,s){if(d==null){return"bar bar0 bar_default"}else{if(d.length>s){return d[s]}else{return"bar bar0 bar_default"}}}).attr("fill","bar_green").attr("x",0).attr("y",function(s){return o(s[0])+2}).attr("width",function(s){return q(s[1])}).attr("height",o.rangeBand()-4);l.append("g").attr("class","y axis").call(e).append("text").attr("transform","rotate(-90)").attr("y",0-j.left).attr("x",0-(r/2)).attr("dy","1em").style("text-anchor","middle").text(i.yLeftLegend)};histogramDuration=function(s,r){var c=s.height();var o=s.width();var e=r.values;var t=d3.format(",.f"),u=d3.time.format("%H:%M"),j=function(w){return u(new Date(2012,0,1,0,w))};var l={top:10,right:60,bottom:40,left:60},p=o-l.left-l.right,n=c-l.top-l.bottom;var k=d3.scale.linear().domain([0,4]).range([0,p]);var v=d3.layout.histogram().bins(k.ticks(20))(e);var g=d3.sum(v,function(w){return w.y});var i=d3.scale.linear().domain([0,d3.max(v,function(w){return w.y})]).range([n,0]);var f=d3.scale.linear().domain([0,d3.max(v,function(w){return w.y/g})]).range([n,0]);var h=d3.svg.axis().scale(k).orient("bottom").tickFormat(j);var b=d3.svg.axis().scale(i).orient("right").ticks(i.domain()[1]).tickSubdivide(0);var d=d3.svg.axis().scale(f).orient("left").ticks(10,"%");var m=d3.select("#"+s.attr("id")).append("svg").attr("width",p+l.left+l.right).attr("height",n+l.top+l.bottom).append("g").attr("transform","translate("+l.left+","+l.top+")");var q=m.selectAll(".bar").data(v).enter().append("g").attr("class","bar bar_default").attr("transform",function(w){return"translate("+k(w.x)+","+i(w.y)+")"}).append("rect").attr("x",2).attr("width",k(v[0].dx)-4).attr("height",function(w){return n-i(w.y)});m.append("g").attr("class","y axis").call(d).append("text").attr("transform","rotate(-90)").attr("y",0-l.left).attr("x",0-(n/2)).attr("dy","1em").style("text-anchor","middle").text(r.yLeftLegend);m.append("g").attr("class","x axis").attr("transform","translate(0,"+n+")").call(h).append("text").attr("y",(l.bottom/1.1)).attr("x",(p/2)).attr("dx","1em").style("text-anchor","middle").text(r.xBottomLegend);m.append("g").attr("class","y axis").attr("transform","translate("+p+",0)").call(b).append("text").attr("transform","rotate(90)").attr("y",0-(l.right)).attr("x",(n/2)).attr("dy","1em").style("text-anchor","middle").text(r.yLeftLegend)};histogramScore=function(s,r){var c=s.height();var o=s.width();var d=r.values;var l={top:10,right:60,bottom:40,left:60},p=o-l.left-l.right,n=c-l.top-l.bottom;var f=r.cut;var k=d3.scale.linear().domain([0,3]).range([0,p]);var t=d3.layout.histogram().bins(k.ticks(20))(d);var h=d3.sum(t,function(u){return u.y});var j=d3.scale.linear().domain([0,d3.max(t,function(u){return u.y})]).range([n,0]);var g=d3.scale.linear().domain([0,d3.max(t,function(u){return u.y/h})]).range([n,0]);var i=d3.svg.axis().scale(k).orient("bottom").tickFormat(d3.format(".01f"));var b=d3.svg.axis().scale(j).orient("right").ticks(j.domain()[1]).tickSubdivide(0);var e=d3.svg.axis().scale(g).orient("left").ticks(10,"%");var m=d3.select("#"+s.attr("id")).append("svg").attr("width",p+l.left+l.right).attr("height",n+l.top+l.bottom).append("g").attr("transform","translate("+l.left+","+l.top+")");var q=m.selectAll(".bar").data(t).enter().append("g").attr("class",function(v,u){if(f==null){return"bar bar_default"}else{if(t[u].x<f){return"bar bar_red"}else{return"bar bar_green"}}}).attr("transform",function(u){return"translate("+k(u.x)+","+j(u.y)+")"}).append("rect").attr("x",2).attr("width",k(t[0].dx)-4).attr("height",function(u){return n-j(u.y)});m.append("g").attr("class","y axis").call(e).append("text").attr("transform","rotate(-90)").attr("y",0-l.left).attr("x",0-(n/2)).attr("dy","1em").style("text-anchor","middle").text(r.yLeftLegend);m.append("g").attr("class","x axis").attr("transform","translate(0,"+n+")").call(i).append("text").attr("y",(l.bottom/1.1)).attr("x",(p/2)).attr("dx","1em").style("text-anchor","middle").text(r.xBottomLegend);m.append("g").attr("class","y axis").attr("transform","translate("+p+",0)").call(b).append("text").attr("transform","rotate(90)").attr("y",0-(l.right)).attr("x",(n/2)).attr("dy","1em").style("text-anchor","middle").text(r.yRightLegend)}}(jQuery)); \ No newline at end of file diff --git a/src/main/webapp/static/js/js.plugins.min.js b/src/main/webapp/static/js/js.plugins.min.js index ab84cd7635a71ea4c5b62e75e4618df7f1802cfc..7eaec0c1833d2bfcffb47861af3e65a0293eb0a5 100644 --- a/src/main/webapp/static/js/js.plugins.min.js +++ b/src/main/webapp/static/js/js.plugins.min.js @@ -5,4 +5,4 @@ OPOL={};var o2c=0;var o3c=new Array();o_info.guibusy=false;o_info.linkbusy=false * Dual licensed under the MIT or GPL Version 2 licenses. * */ -jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};!function(t){function e(){function e(t){"remove"===t&&this.each(function(t,e){var n=r(e);n&&n.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(t,e){var n=tinymce.get(e.id.replace(/_parent$/,""));n&&n.remove()})}function i(t){var n,i=this;if(null!=t)e.call(i),i.each(function(e,n){var i;(i=tinymce.get(n.id))&&i.setContent(t)});else if(i.length>0&&(n=tinymce.get(i[0].id)))return n.getContent()}function r(t){var e=null;return t&&t.id&&a.tinymce&&(e=tinymce.get(t.id)),e}function c(t){return!!(t&&t.length&&a.tinymce&&t.is(":tinymce"))}var u={};t.each(["text","html","val"],function(e,a){var o=u[a]=t.fn[a],s="text"===a;t.fn[a]=function(e){var a=this;if(!c(a))return o.apply(a,arguments);if(e!==n)return i.call(a.filter(":tinymce"),e),o.apply(a.not(":tinymce"),arguments),a;var u="",l=arguments;return(s?a:a.eq(0)).each(function(e,n){var i=r(n);u+=i?s?i.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):i.getContent({save:!0}):o.apply(t(n),l)}),u}}),t.each(["append","prepend"],function(e,i){var a=u[i]=t.fn[i],o="prepend"===i;t.fn[i]=function(t){var e=this;return c(e)?t!==n?(e.filter(":tinymce").each(function(e,n){var i=r(n);i&&i.setContent(o?t+i.getContent():i.getContent()+t)}),a.apply(e.not(":tinymce"),arguments),e):void 0:a.apply(e,arguments)}}),t.each(["remove","replaceWith","replaceAll","empty"],function(n,i){var r=u[i]=t.fn[i];t.fn[i]=function(){return e.call(this,i),r.apply(this,arguments)}}),u.attr=t.fn.attr,t.fn.attr=function(e,a){var o=this,s=arguments;if(!e||"value"!==e||!c(o))return a!==n?u.attr.apply(o,s):u.attr.apply(o,s);if(a!==n)return i.call(o.filter(":tinymce"),a),u.attr.apply(o.not(":tinymce"),s),o;var l=o[0],p=r(l);return p?p.getContent({save:!0}):u.attr.apply(t(l),s)}}var n,i,r=[],a=window;t.fn.tinymce=function(n){function c(){var i=[],r=0;e&&(e(),e=null),l.each(function(t,e){var a,c=e.id,u=n.oninit;c||(e.id=c=tinymce.DOM.uniqueId()),tinymce.get(c)||(a=new tinymce.Editor(c,n,tinymce.EditorManager),i.push(a),a.on("init",function(){var t,e=u;l.css("visibility",""),u&&++r==i.length&&("string"==typeof e&&(t=-1===e.indexOf(".")?null:tinymce.resolve(e.replace(/\.\w+$/,"")),e=tinymce.resolve(e)),e.apply(t||tinymce,i))}))}),t.each(i,function(t,e){e.render()})}var u,o,s,l=this,p="";if(!l.length)return l;if(!n)return tinymce.get(l[0].id);if(l.css("visibility","hidden"),a.tinymce||i||!(u=n.script_url))1===i?r.push(c):c();else{i=1,o=u.substring(0,u.lastIndexOf("/")),-1!=u.indexOf(".min")&&(p=".min"),a.tinymce=a.tinyMCEPreInit||{base:o,suffix:p},-1!=u.indexOf("gzip")&&(s=n.language||"en",u=u+(/\?/.test(u)?"&":"?")+"js=true&core=true&suffix="+escape(p)+"&themes="+escape(n.theme||"")+"&plugins="+escape(n.plugins||"")+"&languages="+(s||""),a.tinyMCE_GZ||(a.tinyMCE_GZ={start:function(){function e(t){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(t))}e("langs/"+s+".js"),e("themes/"+n.theme+"/theme"+p+".js"),e("themes/"+n.theme+"/langs/"+s+".js"),t.each(n.plugins.split(","),function(t,n){n&&(e("plugins/"+n+"/plugin"+p+".js"),e("plugins/"+n+"/langs/"+s+".js"))})},end:function(){}}));var f=document.createElement("script");f.type="text/javascript",f.onload=f.onreadystatechange=function(e){e=e||event,("load"==e.type||/complete|loaded/.test(f.readyState))&&(tinymce.dom.Event.domLoaded=1,i=2,n.script_loaded&&n.script_loaded(),c(),t.each(r,function(t,e){e()}))},f.src=u,document.body.appendChild(f)}return l},t.extend(t.expr[":"],{tinymce:function(t){return!!(t.id&&"tinymce"in window&&tinymce.get(t.id))}})}(jQuery); \ No newline at end of file +jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};!function(e){function t(){function t(e){"remove"===e&&this.each(function(e,t){var n=r(t);n&&n.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(e,t){var n=tinymce.get(t.id.replace(/_parent$/,""));n&&n.remove()})}function i(e){var n,i=this;if(null!=e)t.call(i),i.each(function(t,n){var i;(i=tinymce.get(n.id))&&i.setContent(e)});else if(i.length>0&&(n=tinymce.get(i[0].id)))return n.getContent()}function r(e){var t=null;return e&&e.id&&a.tinymce&&(t=tinymce.get(e.id)),t}function c(e){return!!(e&&e.length&&a.tinymce&&e.is(":tinymce"))}var u={};e.each(["text","html","val"],function(t,a){var o=u[a]=e.fn[a],s="text"===a;e.fn[a]=function(t){var a=this;if(!c(a))return o.apply(a,arguments);if(t!==n)return i.call(a.filter(":tinymce"),t),o.apply(a.not(":tinymce"),arguments),a;var u="",l=arguments;return(s?a:a.eq(0)).each(function(t,n){var i=r(n);u+=i?s?i.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):i.getContent({save:!0}):o.apply(e(n),l)}),u}}),e.each(["append","prepend"],function(t,i){var a=u[i]=e.fn[i],o="prepend"===i;e.fn[i]=function(e){var t=this;return c(t)?e!==n?(t.filter(":tinymce").each(function(t,n){var i=r(n);i&&i.setContent(o?e+i.getContent():i.getContent()+e)}),a.apply(t.not(":tinymce"),arguments),t):void 0:a.apply(t,arguments)}}),e.each(["remove","replaceWith","replaceAll","empty"],function(n,i){var r=u[i]=e.fn[i];e.fn[i]=function(){return t.call(this,i),r.apply(this,arguments)}}),u.attr=e.fn.attr,e.fn.attr=function(t,a){var o=this,s=arguments;if(!t||"value"!==t||!c(o))return a!==n?u.attr.apply(o,s):u.attr.apply(o,s);if(a!==n)return i.call(o.filter(":tinymce"),a),u.attr.apply(o.not(":tinymce"),s),o;var l=o[0],m=r(l);return m?m.getContent({save:!0}):u.attr.apply(e(l),s)}}var n,i,r=[],a=window;e.fn.tinymce=function(n){function c(){var i=[],r=0;t&&(t(),t=null),l.each(function(e,t){var a,c=t.id,u=n.oninit;c||(t.id=c=tinymce.DOM.uniqueId()),tinymce.get(c)||(a=new tinymce.Editor(c,n,tinymce.EditorManager),i.push(a),a.on("init",function(){var e,t=u;l.css("visibility",""),u&&++r==i.length&&("string"==typeof t&&(e=-1===t.indexOf(".")?null:tinymce.resolve(t.replace(/\.\w+$/,"")),t=tinymce.resolve(t)),t.apply(e||tinymce,i))}))}),e.each(i,function(e,t){t.render()})}var u,o,s,l=this,m="";if(!l.length)return l;if(!n)return tinymce.get(l[0].id);if(l.css("visibility","hidden"),a.tinymce||i||!(u=n.script_url))1===i?r.push(c):c();else{i=1,o=u.substring(0,u.lastIndexOf("/")),-1!=u.indexOf(".min")&&(m=".min"),a.tinymce=a.tinyMCEPreInit||{base:o,suffix:m},-1!=u.indexOf("gzip")&&(s=n.language||"en",u=u+(/\?/.test(u)?"&":"?")+"js=true&core=true&suffix="+escape(m)+"&themes="+escape(n.theme||"modern")+"&plugins="+escape(n.plugins||"")+"&languages="+(s||""),a.tinyMCE_GZ||(a.tinyMCE_GZ={start:function(){function t(e){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(e))}t("langs/"+s+".js"),t("themes/"+n.theme+"/theme"+m+".js"),t("themes/"+n.theme+"/langs/"+s+".js"),e.each(n.plugins.split(","),function(e,n){n&&(t("plugins/"+n+"/plugin"+m+".js"),t("plugins/"+n+"/langs/"+s+".js"))})},end:function(){}}));var p=document.createElement("script");p.type="text/javascript",p.onload=p.onreadystatechange=function(t){t=t||event,2===i||"load"!=t.type&&!/complete|loaded/.test(p.readyState)||(tinymce.dom.Event.domLoaded=1,i=2,n.script_loaded&&n.script_loaded(),c(),e.each(r,function(e,t){t()}))},p.src=u,document.body.appendChild(p)}return l},e.extend(e.expr[":"],{tinymce:function(e){return!!(e.id&&"tinymce"in window&&tinymce.get(e.id))}})}(jQuery); \ No newline at end of file diff --git a/src/main/webapp/static/themes/openolat/all/modules/_course.scss b/src/main/webapp/static/themes/openolat/all/modules/_course.scss index 29cf4d6c8dabd68c4f1ec9cd3224444dbe8eab6f..56d3554f33510ed0af7072ddd41c5552104788f6 100644 --- a/src/main/webapp/static/themes/openolat/all/modules/_course.scss +++ b/src/main/webapp/static/themes/openolat/all/modules/_course.scss @@ -170,7 +170,9 @@ div.o_scorm div.o_scorm_content iframe { } .o_scorm_not_attempted { top: 6px; left: 6px; background-image: none; } /* Checklists */ -span.o_cl_duedate { color: red; } +span.o_cl_duedate { color: green; } +p.o_cl_duedate_passed span.o_cl_duedate { color: red; } + \ No newline at end of file diff --git a/src/main/webapp/static/themes/openolat/all/modules/_misc.scss b/src/main/webapp/static/themes/openolat/all/modules/_misc.scss index 9839827d966fbf7b266540375c99db7f3e3c6b39..83f0c5169f9fc87865db628b16c96706215b142a 100644 --- a/src/main/webapp/static/themes/openolat/all/modules/_misc.scss +++ b/src/main/webapp/static/themes/openolat/all/modules/_misc.scss @@ -1,19 +1,14 @@ /* misc */ -/* charts */ +/* charts (crispEdges) */ .d3chart { .bar { shape-rendering: crispEdges; } .bar_default_light { fill: $basecolor_ultra_light; } - .bar_default_light:hover { fill: $basecolor_light; } - .bar_default { fill: $basecolor_light; } - .bar_default:hover { fill: $basecolor; } - .bar_default_dark { fill: $basecolor; } - .bar_default_dark:hover { fill: black; } .axis { font: 12px sans-serif; } @@ -22,8 +17,6 @@ stroke: #000; shape-rendering: crispEdges; } - - .x.axis path { display: none; } } /* BUSINESS CARD & Member site, members search */ @@ -355,6 +348,7 @@ div.o_notifications_news_subscription { margin: 1.5em 0 2em 0; h4 { font-size: 110%; } h4.o_returnbox_icon { background-image: url(../openolat/images/box_return.png) !important; } + h4.o_dropbox_icon { background-image: url(../openolat/images/box_drop.png) !important; } } div.o_notifications_news_context { color: #7D7D7D; font-size: 90%; } div.o_notifications_news_content { diff --git a/src/main/webapp/static/themes/openolat/all/modules/_qti.scss b/src/main/webapp/static/themes/openolat/all/modules/_qti.scss index 0c97ed0d341fd73d90f93297c3bd806b68b364e4..e9b2bf4fa2102a7d6cfb3fac80ea091182944b55 100644 --- a/src/main/webapp/static/themes/openolat/all/modules/_qti.scss +++ b/src/main/webapp/static/themes/openolat/all/modules/_qti.scss @@ -4,16 +4,11 @@ a.o_print_icon span { display:none; } .d3chart { .bar_green { fill: #9dd53a; } - .bar_green:hover { fill: #7cbc0a; } .bar_red { fill: #f85032; } - .bar_red:hover { fill: #e73827; } .bar_grey { fill: lightgrey; } - .bar_grey:hover { fill: grey; } } div.o_qti_statistics { - padding-top: 20px; padding-left: 40px; padding-right: 5%; - ul { list-style-type: none; padding:0; margin:0; } li { padding-left: 22px; } @@ -21,22 +16,27 @@ div.o_qti_statistics { li.o_qti_statistics-ncorrect { background: url(../img/decorator_error.png) no-repeat 5px 4px; } li.o_qti_statistics-correct { background: url(../img/decorator_ok.png) no-repeat 5px 2px; } - h4 { - padding-left:20px; - &.qti-qtype1 {background: url(../img/scItem.png) no-repeat;} + &.qti-qtype1 {padding-left:20px; background: url(../img/scItem.png) no-repeat;} - &.qti-qtype2 {background: url(../img/mcItem.png) no-repeat;} + &.qti-qtype2 {padding-left:20px; background: url(../img/mcItem.png) no-repeat;} - &.qti-qtype3 {background: url(../img/fibItem.png) no-repeat;} + &.qti-qtype3 {padding-left:20px; background: url(../img/fibItem.png) no-repeat;} - &.qti-qtype4 {background: url(../img/essayItem.png) no-repeat;} + &.qti-qtype4 {padding-left:20px; background: url(../img/essayItem.png) no-repeat;} + + &.qti-qtype5 {padding-left:20px; background: url(../img/kprimItem.png) no-repeat;} + } - &.qti-qtype5 { background: url(../img/kprimItem.png) no-repeat;} + a.b_content_download { + display: inline; + } + + a.o_print_icon span { + display: inline; } } - /* QTI legacy styles */ #o_qti_run {} #o_qti_run div.b_button_group { text-align: left;} diff --git a/src/test/java/org/olat/ims/qti/QTIResultManagerTest.java b/src/test/java/org/olat/ims/qti/QTIResultManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c83cb2c1d380b3d94353e0c3da6e7b3c2e4677c --- /dev/null +++ b/src/test/java/org/olat/ims/qti/QTIResultManagerTest.java @@ -0,0 +1,381 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti; + + +import static org.olat.modules.iq.IQTestHelper.createRepository; +import static org.olat.modules.iq.IQTestHelper.createResult; +import static org.olat.modules.iq.IQTestHelper.createSet; +import static org.olat.modules.iq.IQTestHelper.modDate; + +import java.util.Collections; +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.Group; +import org.olat.basesecurity.GroupRoles; +import org.olat.core.commons.persistence.DB; +import org.olat.core.id.Identity; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 18.03.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class QTIResultManagerTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private QTIResultManager qtiResultManager; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + @Autowired + private BaseSecurity securityManager; + + + @Test + public void hasResultSets() { + RepositoryEntry re = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-1"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-2"); + dbInstance.commit(); + + long assessmentId = 838l; + String resSubPath = "qtiResult34"; + + //3 try for id1 and id2 + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id1, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResultSet set1_2 = createSet(3.0f, assessmentId, id1, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResultSet set2_1 = createSet(5.0f, assessmentId, id2, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + dbInstance.commit(); + Assert.assertNotNull(set1_1); + Assert.assertNotNull(set1_2); + Assert.assertNotNull(set2_1); + + boolean hasSet = qtiResultManager.hasResultSets(re.getOlatResource().getResourceableId(), resSubPath, re.getKey()); + Assert.assertTrue(hasSet); + } + + @Test + public void hasResultSets_negativeTest() { + RepositoryEntry re = createRepository(); + String resSubPath = "qtiResult35"; + + boolean hasSet = qtiResultManager.hasResultSets(re.getOlatResource().getResourceableId(), resSubPath, re.getKey()); + Assert.assertFalse(hasSet); + } + + @Test + public void getResultSets_withIdentity() { + RepositoryEntry re = createRepository(); + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-3"); + dbInstance.commit(); + + long assessmentId = 839l; + String resSubPath = "qtiResult36"; + + //3 try for id1 + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResultSet set1_3 = createSet(3.0f, assessmentId, id, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResultSet set1_2 = createSet(5.0f, assessmentId, id, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + dbInstance.commit(); + + List<QTIResultSet> set = qtiResultManager.getResultSets(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), id); + Assert.assertNotNull(set); + Assert.assertEquals(3, set.size()); + Assert.assertTrue(set.contains(set1_1)); + Assert.assertTrue(set.contains(set1_2)); + Assert.assertTrue(set.contains(set1_3)); + } + + @Test + public void getResultSets_withoutIdentity() { + RepositoryEntry re = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-4"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-5"); + dbInstance.commit(); + + long assessmentId = 840l; + String resSubPath = "qtiResult36"; + + //3 try for id1 + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id1, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResultSet set1_2 = createSet(3.0f, assessmentId, id1, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResultSet set2_1 = createSet(5.0f, assessmentId, id2, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + dbInstance.commit(); + + List<QTIResultSet> set = qtiResultManager.getResultSets(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), null); + Assert.assertNotNull(set); + Assert.assertEquals(3, set.size()); + Assert.assertTrue(set.contains(set1_1)); + Assert.assertTrue(set.contains(set1_2)); + Assert.assertTrue(set.contains(set2_1)); + } + + @Test + public void selectResults() { + RepositoryEntry re = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-6"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-7"); + Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-8"); + dbInstance.commit(); + + long assessmentId = 841l; + String resSubPath = "qtiResult37"; + String itemIdent = "NES:PS4:849235789"; + + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id1, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResult result1_1 = createResult(itemIdent, "Hello world", set1_1); + QTIResultSet set2_1 = createSet(3.0f, assessmentId, id2, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResult result2_1 = createResult(itemIdent, "Bonjour madame", set2_1); + QTIResultSet set3_1 = createSet(5.0f, assessmentId, id3, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + QTIResult result3_1 = createResult(itemIdent, "Tschuss", set3_1); + dbInstance.commit(); + + //order by last name + List<QTIResult> resultsType1 = qtiResultManager.selectResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), null, 1); + Assert.assertNotNull(resultsType1); + Assert.assertEquals(3, resultsType1.size()); + Assert.assertTrue(resultsType1.contains(result1_1)); + Assert.assertTrue(resultsType1.contains(result2_1)); + Assert.assertTrue(resultsType1.contains(result3_1)); + + //order by last name + List<QTIResult> resultsType2 = qtiResultManager.selectResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), null, 2); + Assert.assertNotNull(resultsType2); + Assert.assertEquals(3, resultsType2.size()); + Assert.assertTrue(resultsType2.contains(result1_1)); + Assert.assertTrue(resultsType2.contains(result2_1)); + Assert.assertTrue(resultsType2.contains(result3_1)); + + //order by creation date + List<QTIResult> resultsType3 = qtiResultManager.selectResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), null, 3); + Assert.assertNotNull(resultsType3); + Assert.assertEquals(3, resultsType3.size()); + Assert.assertTrue(resultsType3.contains(result1_1)); + Assert.assertTrue(resultsType3.contains(result2_1)); + Assert.assertTrue(resultsType3.contains(result3_1)); + } + + @Test + public void selectResults_limitToSecurityGroup() { + RepositoryEntry re = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-16"); + repositoryEntryRelationDao.addRole(id1, re, GroupRoles.participant.name()); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-17"); + dbInstance.commit(); + + long assessmentId = 841l; + String resSubPath = "qtiResult37"; + String itemIdent1 = "NES:PS4:849235797"; + String itemIdent2 = "NES:PS4:849235798"; + + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id1, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResult result1_1 = createResult(itemIdent1, "Hello world", set1_1); + QTIResultSet set2_1 = createSet(3.0f, assessmentId, id2, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResult result2_1 = createResult(itemIdent1, "Bonjour madame", set2_1); + QTIResultSet set1_1b = createSet(5.0f, assessmentId, id1, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + QTIResult result1_1b = createResult(itemIdent1, "Tschuss", set1_1b); + QTIResult result1_1c = createResult(itemIdent2, "Tschuss", set1_1b); + dbInstance.commitAndCloseSession(); + + List<Group> secGroups = Collections.singletonList(repositoryEntryRelationDao.getDefaultGroup(re)); + List<QTIResult> resultsType1 = qtiResultManager.selectResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey(), secGroups, 1); + Assert.assertNotNull(resultsType1); + Assert.assertEquals(3, resultsType1.size()); + Assert.assertTrue(resultsType1.contains(result1_1)); + Assert.assertTrue(resultsType1.contains(result1_1b)); + Assert.assertTrue(resultsType1.contains(result1_1c)); + //not a participant in the security group + Assert.assertFalse(resultsType1.contains(result2_1)); + } + + @Test + public void hasResults() { + RepositoryEntry re = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-9"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-10"); + Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-11"); + dbInstance.commit(); + + long assessmentId = 842l; + String resSubPath = "qtiResult38"; + String itemIdent = "NES:PS4:849235790"; + + QTIResultSet set1_1 = createSet(1.0f, assessmentId, id1, re, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResult result1_1 = createResult(itemIdent, "Hello world", set1_1); + QTIResultSet set2_1 = createSet(3.0f, assessmentId, id2, re, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResult result2_1 = createResult(itemIdent, "Bonjour madame", set2_1); + QTIResultSet set3_1 = createSet(5.0f, assessmentId, id3, re, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + QTIResult result3_1 = createResult(itemIdent, "Tschuss", set3_1); + dbInstance.commit(); + Assert.assertNotNull(result1_1); + Assert.assertNotNull(result2_1); + Assert.assertNotNull(result3_1); + + int numOfResults = qtiResultManager.countResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey()); + Assert.assertEquals(3, numOfResults); + } + + @Test + public void hasResults_negativeTest() { + RepositoryEntry re = createRepository(); + dbInstance.commit(); + + String resSubPath = "qtiResult39"; + + int numOfResults = qtiResultManager.countResults(re.getOlatResource().getResourceableId(), resSubPath, re.getKey()); + Assert.assertEquals(0, numOfResults); + } + + @Test + public void findQtiResultSets() { + RepositoryEntry re1 = createRepository(); + RepositoryEntry re2 = createRepository(); + RepositoryEntry re3 = createRepository(); + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-12"); + dbInstance.commit(); + + QTIResultSet set1_1 = createSet(1.0f, 842l, id, re1, "qtiResult38", modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResultSet set1_2 = createSet(3.0f, 843l, id, re2, "qtiResult39", modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResultSet set1_3 = createSet(5.0f, 844l, id, re3, "qtiResult40", modDate(3, 10, 35), modDate(3, 10, 55)); + dbInstance.commit(); + + List<QTIResultSet> sets = qtiResultManager.findQtiResultSets(id); + Assert.assertNotNull(sets); + Assert.assertEquals(3, sets.size()); + Assert.assertTrue(sets.contains(set1_1)); + Assert.assertTrue(sets.contains(set1_2)); + Assert.assertTrue(sets.contains(set1_3)); + } + + @Test + public void deleteResultSet() { + RepositoryEntry re1 = createRepository(); + RepositoryEntry re2 = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-17"); + + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-18"); + dbInstance.commit(); + + String itemIdent1 = "NES:PS4:849235795"; + String itemIdent2 = "NES:PS4:849235796"; + String resSubPath = "qtiResult43"; + + //the set to delete + QTIResultSet set1_1 = createSet(1.0f, 842l, id1, re1, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResult result1_1a = createResult(itemIdent1, "Hello world", set1_1); + QTIResult result1_1b = createResult(itemIdent2, "Hello world", set1_1); + //two sets which stay on the database and use to check that the queries didn't delete too much rows + QTIResultSet set1_2 = createSet(3.0f, 843l, id1, re2, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResult result1_2 = createResult(itemIdent1, "Hello world", set1_2); + QTIResultSet set2_1 = createSet(5.0f, 844l, id2, re1, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + QTIResult result2_1 = createResult(itemIdent1, "Hello world", set2_1); + dbInstance.commitAndCloseSession(); + + qtiResultManager.deleteResultSet(set1_1); + dbInstance.commit(); + + //check sets on database + List<QTIResultSet> sets = qtiResultManager.getResultSets(re1.getOlatResource().getResourceableId(), resSubPath, re1.getKey(), null); + Assert.assertNotNull(sets); + Assert.assertEquals(1, sets.size()); + Assert.assertTrue(sets.contains(set2_1)); + + List<QTIResultSet> setRe2s = qtiResultManager.getResultSets(re2.getOlatResource().getResourceableId(), resSubPath, re2.getKey(), null); + Assert.assertNotNull(setRe2s); + Assert.assertEquals(1, setRe2s.size()); + Assert.assertTrue(setRe2s.contains(set1_2)); + + //check results + List<QTIResult> results = qtiResultManager.selectResults(re1.getOlatResource().getResourceableId(), resSubPath, re1.getKey(), null, 3); + Assert.assertNotNull(results); + Assert.assertEquals(1, results.size()); + Assert.assertTrue(results.contains(result2_1)); + Assert.assertFalse(results.contains(result1_1a)); + Assert.assertFalse(results.contains(result1_1b)); + + List<QTIResult> resultsRe2 = qtiResultManager.selectResults(re2.getOlatResource().getResourceableId(), resSubPath, re2.getKey(), null, 3); + Assert.assertNotNull(resultsRe2); + Assert.assertEquals(1, resultsRe2.size()); + Assert.assertTrue(resultsRe2.contains(result1_2)); + } + + @Test + public void deleteAllResults() { + RepositoryEntry re1 = createRepository(); + RepositoryEntry re2 = createRepository(); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-15"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("qti-result-mgr-16"); + dbInstance.commit(); + + String itemIdent1 = "NES:PS4:849235793"; + String itemIdent2 = "NES:PS4:849235794"; + String resSubPath = "qtiResult42"; + + //the set to delete + QTIResultSet set1_1 = createSet(1.0f, 842l, id1, re1, resSubPath, modDate(3, 8, 5), modDate(3, 8, 20)); + QTIResult result1_1a = createResult(itemIdent1, "Hello world", set1_1); + QTIResult result1_1b = createResult(itemIdent2, "Hello world", set1_1); + //two sets which stay on the database and use to check that the queries didn't delete too much rows + QTIResultSet set1_2 = createSet(3.0f, 843l, id1, re2, resSubPath, modDate(3, 14, 8), modDate(3, 14, 32)); + QTIResult result1_2 = createResult(itemIdent1, "Hello world", set1_2); + QTIResultSet set2_1 = createSet(5.0f, 844l, id2, re1, resSubPath, modDate(3, 10, 35), modDate(3, 10, 55)); + QTIResult result2_1 = createResult(itemIdent1, "Hello world", set2_1); + dbInstance.commitAndCloseSession(); + + //delete all results of re1 + qtiResultManager.deleteAllResults(re1.getOlatResource().getResourceableId(), resSubPath, re1.getKey()); + dbInstance.commit(); + + //check sets on database + List<QTIResultSet> sets = qtiResultManager.getResultSets(re1.getOlatResource().getResourceableId(), resSubPath, re1.getKey(), null); + Assert.assertNotNull(sets); + Assert.assertEquals(0, sets.size()); + Assert.assertFalse(sets.contains(result1_1a)); + Assert.assertFalse(sets.contains(result1_1b)); + Assert.assertFalse(sets.contains(result2_1)); + + List<QTIResultSet> setRe2s = qtiResultManager.getResultSets(re2.getOlatResource().getResourceableId(), resSubPath, re2.getKey(), null); + Assert.assertNotNull(setRe2s); + Assert.assertEquals(1, setRe2s.size()); + Assert.assertTrue(setRe2s.contains(set1_2)); + + //check results + List<QTIResult> results = qtiResultManager.selectResults(re1.getOlatResource().getResourceableId(), resSubPath, re1.getKey(), null, 3); + Assert.assertNotNull(results); + Assert.assertEquals(0, results.size()); + + List<QTIResult> resultsRe2 = qtiResultManager.selectResults(re2.getOlatResource().getResourceableId(), resSubPath, re2.getKey(), null, 3); + Assert.assertNotNull(resultsRe2); + Assert.assertEquals(1, resultsRe2.size()); + Assert.assertTrue(resultsRe2.contains(result1_2)); + } +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index e4e2c6afddd41a37e3cea3d9db8b4b953ba01fe7..f4467b3f39909f0817624c3046c93658094e42ef 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -134,6 +134,7 @@ import org.junit.runners.Suite; org.olat.core.commons.persistence.DBTest.class, org.olat.modules.ims.cp.CPManagerTest.class, org.olat.modules.ims.qti.fileresource.FileResourceValidatorTest.class, + org.olat.ims.qti.QTIResultManagerTest.class, org.olat.ims.qti.qpool.QTIImportProcessorTest.class, org.olat.ims.qti.qpool.QTIExportProcessorTest.class, org.olat.ims.qti.statistics.manager.QTIStatisticsManagerLargeTest.class,