diff --git a/src/main/java/org/olat/admin/user/UserSearchFlexiController.java b/src/main/java/org/olat/admin/user/UserSearchFlexiController.java index 50f7345d340aaf873ca74120a900e06122e09323..072fbabeacc4b510c22b75f3374e9b06065615c8 100644 --- a/src/main/java/org/olat/admin/user/UserSearchFlexiController.java +++ b/src/main/java/org/olat/admin/user/UserSearchFlexiController.java @@ -219,7 +219,7 @@ public class UserSearchFlexiController extends FlexiAutoCompleterController { Translator myTrans = userManager.getPropertyHandlerTranslator(getTranslator()); userTableModel = new UserSearchFlexiTableModel(Collections.<Identity>emptyList(), resultingPropertyHandlers, isAdministrativeUser, getLocale(), tableColumnModel); - tableEl = uifactory.addTableElement(getWindowControl(), "users", userTableModel, myTrans, formLayout); + tableEl = uifactory.addTableElement(getWindowControl(), "users", userTableModel, 250, false, myTrans, formLayout); tableEl.setCustomizeColumns(false); tableEl.setMultiSelect(true); tableEl.setSelectAllEnable(true); @@ -422,7 +422,8 @@ public class UserSearchFlexiController extends FlexiAutoCompleterController { } tableEl.reset(); - List<Identity> users = searchUsers(login, userPropertiesSearch, true); + + List<Identity> users = searchUsers(login, userPropertiesSearch, true); if (!users.isEmpty()) { userTableModel.setObjects(users); flc.contextPut("showButton","true"); diff --git a/src/main/java/org/olat/basesecurity/BaseSecurity.java b/src/main/java/org/olat/basesecurity/BaseSecurity.java index 62c526e1b617b3c4f7629e41680969f1bcf0cc4a..c2445becd78ba24a3d1fc8aa92b68b84719cd7ac 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurity.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurity.java @@ -173,7 +173,13 @@ public interface BaseSecurity { */ public Identity findIdentityByNumber(String identityNumber); - + /** + * The list of visible identities with a institutional number like in the + * specified list. Deleted ones are not included. + * + * @param identityNumbers + * @return A list of identities + */ public List<Identity> findIdentitiesByNumber(Collection<String> identityNumbers); /** diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java index 2b23870a2936cd0ad3b8c8666e1afe81178e9d69..d71ffa545081bac5350051de15ee371f098bb8b6 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java @@ -991,13 +991,15 @@ public class BaseSecurityManager implements BaseSecurity { if(identityNumbers == null || identityNumbers.isEmpty()) return Collections.emptyList(); StringBuilder sb = new StringBuilder(); - sb.append("select identity from ").append(IdentityImpl.class.getName()).append(" identity ") - .append(" inner join fetch identity.user user ") - .append(" where user.").append(UserConstants.INSTITUTIONALUSERIDENTIFIER).append(" in (:idNumbers) "); + sb.append("select ident from ").append(IdentityImpl.class.getName()).append(" ident ") + .append(" inner join fetch ident.user user ") + .append(" where user.").append(UserConstants.INSTITUTIONALUSERIDENTIFIER).append(" in (:idNumbers) ") + .append(" and ident.status<:status"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Identity.class) .setParameter("idNumbers", identityNumbers) + .setParameter("status", Identity.STATUS_VISIBLE_LIMIT) .getResultList(); } diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/elements/FlexiTableSortOptions.java b/src/main/java/org/olat/core/gui/components/form/flexible/elements/FlexiTableSortOptions.java index 711062ebbcd8046f172d8dbe73c0b1e19937b991..531d731dac254b48d6b404b39f7b4b81b16068fa 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/elements/FlexiTableSortOptions.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/elements/FlexiTableSortOptions.java @@ -59,6 +59,13 @@ public class FlexiTableSortOptions { public boolean isFromColumnModel() { return fromColumnModel; } + + /** + * @return true if a default order is set + */ + public boolean hasDefaultOrderBy() { + return defaultOrderBy != null; + } public SortKey getDefaultOrderBy() { return defaultOrderBy; diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/SortableFlexiTableModelDelegate.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/SortableFlexiTableModelDelegate.java index dc7c57381507107468da38f5019891e4c4cbe8c1..b2ee11bf8014481a777d430311cf76f6520b4529 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/SortableFlexiTableModelDelegate.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/SortableFlexiTableModelDelegate.java @@ -167,6 +167,10 @@ public class SortableFlexiTableModelDelegate<T> { } return a.compareTo(b); } + + protected final int compareDoubles(double a, double b) { + return Double.compare(a, b); + } protected final int compareNullObjects(final Object a, final Object b) { boolean ba = (a == null); diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 5b581a887dbc1e882fd4d98f983f17deddbbaa89..0fc67c15e805620987e8bd9eb925bb8a7ea9eebb 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -28,6 +28,8 @@ package org.olat.course; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -711,10 +713,13 @@ public class CourseFactory { // archive course results overview List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment()); List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment()); - - String result = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, locale); - String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "xls"); - ExportUtil.writeContentToFile(fileName, result, exportDirectory, charset); + + String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "xlsx"); + try(OutputStream out = new FileOutputStream(new File(exportDirectory, fileName))) { + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, out); + } catch(IOException e) { + log.error("", e); + } // archive all nodes content Visitor archiveV = new NodeArchiveVisitor(locale, course, exportDirectory, charset); diff --git a/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java b/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java index 89dbb46493a7f02b5c26f9b9d4a9ff922ef1860d..018b3f9bc90a0a9717b65e5a387eda79de96865e 100644 --- a/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java +++ b/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java @@ -26,6 +26,9 @@ package org.olat.course.archiver; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.List; import org.olat.core.gui.UserRequest; @@ -46,7 +49,6 @@ import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; -import org.olat.user.UserManager; /** * Description: Course-Results-Archiver using ScoreAccountingHelper.class @@ -103,18 +105,16 @@ public class ScoreAccountingArchiveController extends BasicController { List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment()); List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment()); - String result = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, getLocale()); - String courseTitle = course.getCourseTitle(); - - String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xls"); + String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); // location for data export File exportDirectory = CourseFactory.getOrCreateDataExportDirectory(getIdentity(), courseTitle); - // the user's charset - UserManager um = UserManager.getInstance(); - String charset = um.getUserCharset(getIdentity()); - - File downloadFile = ExportUtil.writeContentToFile(fileName, result, exportDirectory, charset); + File downloadFile = new File(exportDirectory, fileName); + try(OutputStream out=new FileOutputStream(downloadFile)) { + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, getLocale(), out); + } catch(IOException e) { + logError("", e); + } vcFeedback = createVelocityContainer("feedback"); vcFeedback.contextPut("body", translate("course.res.feedback", new String[] { downloadFile.getName() })); diff --git a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java index 2a79bade63499db9eb7c0c21cc9f18e19120f3ab..997c3e8092e32bf923fe1c04c120d70ec1bbcaae 100644 --- a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java +++ b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java @@ -40,13 +40,11 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.olat.basesecurity.GroupRoles; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; -import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.openxml.OpenXMLWorkbook; @@ -79,314 +77,18 @@ import org.olat.user.propertyhandlers.UserPropertyHandler; * Comment: Provides functionality to get a course results overview. */ public class ScoreAccountingHelper { - + /** * The results from assessable nodes are written to one row per user into an excel-sheet. An * assessable node will only appear if it is producing at least one of the * following variables: score, passed, attempts, comments. - * - * @param identities - * @param myNodes - * @param course - * @param locale - * @return String + * + * @param identities The list of identities which results need to be archived. + * @param myNodes The assessable nodes to archive. + * @param course The course. + * @param locale The locale. + * @param bos The output stream (which will be closed at the end, if you use a zip stream don't forget to shield it). */ - public static String createCourseResultsOverviewTable(List<Identity> identities, List<AssessableCourseNode> myNodes, ICourse course, Locale locale) { - Translator t = Util.createPackageTranslator(ScoreAccountingArchiveController.class, locale); - StringBuilder tableHeader1 = new StringBuilder(); - StringBuilder tableHeader2 = new StringBuilder(); - StringBuilder tableContent = new StringBuilder(); - StringBuilder table = new StringBuilder(); - - String sequentialNumber = t.translate("column.header.seqnum"); - String login = t.translate("column.header.businesspath"); - // user properties are dynamic - String sc = t.translate("column.header.score"); - String pa = t.translate("column.header.passed"); - String co = t.translate("column.header.comment"); - String cco = t.translate("column.header.coachcomment"); - String at = t.translate("column.header.attempts"); - String il = t.translate("column.header.initialLaunchDate"); - String slm = t.translate("column.header.scoreLastModified"); - String na = t.translate("column.field.notavailable"); - String mi = t.translate("column.field.missing"); - String yes = t.translate("column.field.yes"); - String no = t.translate("column.field.no"); - - - tableHeader1.append(sequentialNumber); - tableHeader1.append("\t"); - tableHeader2.append("\t"); - - tableHeader1.append(login); - tableHeader1.append("\t"); - tableHeader2.append("\t"); - // get user property handlers for this export, translate using the fallback - // translator configured in the property handler - - //Initial launch date - tableHeader1.append(il).append("\t"); - tableHeader2.append("\t"); - - List<UserPropertyHandler> userPropertyHandlers = UserManager.getInstance().getUserPropertyHandlersFor( - ScoreAccountingHelper.class.getCanonicalName(), true); - t = UserManager.getInstance().getPropertyHandlerTranslator(t); - for (UserPropertyHandler propertyHandler : userPropertyHandlers) { - tableHeader1.append(t.translate(propertyHandler.i18nColumnDescriptorLabelKey())); - tableHeader1.append("\t"); - tableHeader2.append("\t"); - } - - // preload user properties cache - CourseEnvironment courseEnvironment = course.getCourseEnvironment(); - - boolean firstIteration = true; - int rowNumber = 1; - - UserCourseInformationsManager mgr = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); - OLATResource courseResource = courseEnvironment.getCourseGroupManager().getCourseResource(); - Map<Long,Date> firstTimes = mgr.getInitialLaunchDates(courseResource, identities); - Formatter formatter = Formatter.getInstance(locale); - - int count = 0; - for (Identity identity:identities) { - ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(identity); - String uname = BusinessControlFactory.getInstance().getAsURIString(Collections.singletonList(ce), false); - - tableContent.append(rowNumber); - tableContent.append("\t"); - tableContent.append(uname); - tableContent.append("\t"); - - String initialLaunchDate = ""; - if(firstTimes.containsKey(identity.getKey())) { - initialLaunchDate = formatter.formatDateAndTime(firstTimes.get(identity.getKey())); - } - tableContent.append(initialLaunchDate).append("\t"); - - // add dynamic user properties - for (UserPropertyHandler propertyHandler : userPropertyHandlers) { - String value = propertyHandler.getUserProperty(identity.getUser(), t.getLocale()); - tableContent.append((StringHelper.containsNonWhitespace(value) ? value : na)); - tableContent.append("\t"); - } - - // create a identenv with no roles, no attributes, no locale - IdentityEnvironment ienv = new IdentityEnvironment(); - ienv.setIdentity(identity); - UserCourseEnvironment uce = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); - ScoreAccounting scoreAccount = uce.getScoreAccounting(); - scoreAccount.evaluateAll(); - AssessmentManager am = course.getCourseEnvironment().getAssessmentManager(); - - for (AssessableCourseNode acnode:myNodes) { - boolean scoreOk = acnode.hasScoreConfigured(); - boolean passedOk = acnode.hasPassedConfigured(); - boolean attemptsOk = acnode.hasAttemptsConfigured(); - boolean commentOk = acnode.hasCommentConfigured(); - - if (scoreOk || passedOk || commentOk || attemptsOk) { - ScoreEvaluation se = scoreAccount.evalCourseNode(acnode); - boolean nodeColumnOk = false; - StringBuilder tabs = new StringBuilder(); - - if (scoreOk) { - Float score = se.getScore(); - nodeColumnOk = true; - tabs.append("\t"); // tabulators for header1 after node title - - if (firstIteration) { - tableHeader2.append(sc); - tableHeader2.append("\t"); - } - - if (score != null) { - tableContent.append(AssessmentHelper.getRoundedScore(score)); - tableContent.append("\t"); - } else { // score == null - tableContent.append(mi); - tableContent.append("\t"); - } - } - - if (passedOk) { - Boolean passed = se.getPassed(); - nodeColumnOk = true; - tabs.append("\t"); // tabulators for header1 after node title - - if (firstIteration) { - tableHeader2.append(pa); - tableHeader2.append("\t"); - } - - if (passed != null) { - String yesno; - if (passed.booleanValue()) { - yesno = yes; - } else { - yesno = no; - } - tableContent.append(yesno); - tableContent.append("\t"); - } else { // passed == null - tableContent.append(mi); - tableContent.append("\t"); - } - } - - if (attemptsOk) { - Integer attempts = am.getNodeAttempts(acnode, identity); - int a = attempts == null ? 0 : attempts.intValue(); - nodeColumnOk = true; - tabs.append("\t"); // tabulators for header1 after node title - - if (firstIteration) { - tableHeader2.append(at); - tableHeader2.append("\t"); - } - - tableContent.append(a); - tableContent.append("\t"); - } - - if (firstIteration) { - //last Modified - tableHeader2.append(slm); - tableHeader2.append("\t"); - } - - String scoreLastModified = ""; - Date lastModified = am.getScoreLastModifiedDate(acnode, identity); - if(lastModified != null) { - scoreLastModified = formatter.formatDateAndTime(lastModified); - } - tableContent.append(scoreLastModified); - tableContent.append("\t"); - - if (commentOk) { - // Comments for user - String comment = am.getNodeComment(acnode, identity); - nodeColumnOk = true; - tabs.append("\t"); // tabulators for header1 after node title - - if (firstIteration) { - tableHeader2.append(co); - tableHeader2.append("\t"); - } - - if (comment != null) { - // put comment between double quote in order to prevent that - // '\t','\r' or '\n' destroy the excel table - // A (double) quote must be represented by two (double) quotes. - tableContent.append("\""); - tableContent.append(comment.replace("\"", "\"\"")); - tableContent.append("\"\t"); - } else { - tableContent.append(mi); - tableContent.append("\t"); - } - - // Comments for tutors - String coachComment = am.getNodeCoachComment(acnode, identity); - tabs.append("\t"); // tabulators for header1 after node title - - if (firstIteration) { - tableHeader2.append(cco); - tableHeader2.append("\t"); - } - - if (coachComment != null) { - // put coachComment between double quote in order to prevent that - // '\t','\r' or '\n' destroy the excel table - // A (double) quote must be represented by two (double) quotes. - tableContent.append("\""); - tableContent.append(coachComment.replace("\"", "\"\"")); - tableContent.append("\"\t"); - } else { - tableContent.append(mi); - tableContent.append("\t"); - } - - - } - - if (firstIteration && nodeColumnOk) { - String shortTitle = acnode.getShortTitle(); - - tableHeader1.append(shortTitle); - tableHeader1.append(tabs.toString()); - } - - } - } - if (firstIteration) { - tableHeader1.append("\t\n"); - tableHeader2.append("\t\n"); - } - tableContent.append("\t\n"); - firstIteration = false; - rowNumber++; - - if(count++ % 20 == 0) { - DBFactory.getInstance().commitAndCloseSession(); - } - } - //fxdiff VCRP-4: assessment overview with max score - StringBuilder tableFooter = new StringBuilder(); - tableFooter.append("\t\n").append("\t\n").append(t.translate("legend")).append("\t\n").append("\t\n"); - for (AssessableCourseNode acnode:myNodes) { - if (!acnode.hasScoreConfigured()) { - // only show min/max/cut legend when score configured - continue; - } - String minVal; - String maxVal; - String cutVal; - if(acnode instanceof STCourseNode || !acnode.hasScoreConfigured()) { - minVal = maxVal = cutVal = "-"; - } else { - minVal = acnode.getMinScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMinScoreConfiguration()); - maxVal = acnode.getMaxScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMaxScoreConfiguration()); - if (acnode.hasPassedConfigured()) { - cutVal = acnode.getCutValueConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getCutValueConfiguration()); - } else { - cutVal = "-"; - } - } - - tableFooter.append('"'); - tableFooter.append(acnode.getShortTitle()); - tableFooter.append('"'); - tableFooter.append('\n'); - - tableFooter.append("\t\t"); - tableFooter.append("minValue"); - tableFooter.append('\t'); - tableFooter.append(minVal); - tableFooter.append('\n'); - - tableFooter.append("\t\t"); - tableFooter.append("maxValue"); - tableFooter.append('\t'); - tableFooter.append(maxVal); - tableFooter.append('\n'); - - tableFooter.append("\t\t"); - tableFooter.append("cutValue"); - tableFooter.append('\t'); - tableFooter.append(cutVal); - tableFooter.append('\n'); - } - - table.append(tableHeader1); - table.append(tableHeader2); - table.append(tableContent); - table.append(tableFooter); - String tab = table.toString(); - - return tab; - } - public static void createCourseResultsOverviewXMLTable(List<Identity> identities, List<AssessableCourseNode> myNodes, ICourse course, Locale locale, OutputStream bos) { OpenXMLWorkbook workbook = new OpenXMLWorkbook(bos, 1); OpenXMLWorksheet sheet = workbook.nextWorksheet(); @@ -409,11 +111,7 @@ public class ScoreAccountingHelper { String no = t.translate("column.field.no"); String submitted = t.translate("column.field.submitted"); - AssessableCourseNode firstAcnode = myNodes.get(0); - boolean scoreOk = firstAcnode.hasScoreConfigured(); - boolean passedOk = firstAcnode.hasPassedConfigured(); - boolean attemptsOk = firstAcnode.hasAttemptsConfigured(); - boolean commentOk = firstAcnode.hasCommentConfigured(); + Row headerRow1 = sheet.newRow(); headerRow1.addCell(headerColCnt++, sequentialNumber); @@ -428,32 +126,63 @@ public class ScoreAccountingHelper { for (UserPropertyHandler propertyHandler : userPropertyHandlers) { headerRow1.addCell(headerColCnt++, t.translate(propertyHandler.i18nColumnDescriptorLabelKey())); } - if (scoreOk || passedOk || commentOk || attemptsOk) - headerRow1.addCell(headerColCnt, firstAcnode.getShortTitle()); - - Row headerRow2 = sheet.newRow(); - if (firstAcnode.getType().equals("ita")) { - headerRow2.addCell(headerColCnt++, submitted); + + int header1ColCnt = headerColCnt; + for(AssessableCourseNode acNode:myNodes) { + headerRow1.addCell(header1ColCnt++, acNode.getShortTitle()); + header1ColCnt += acNode.getType().equals("ita") ? 1 : 0; + + boolean scoreOk = acNode.hasScoreConfigured(); + boolean passedOk = acNode.hasPassedConfigured(); + boolean attemptsOk = acNode.hasAttemptsConfigured(); + boolean commentOk = acNode.hasCommentConfigured(); + if (scoreOk || passedOk || commentOk || attemptsOk) { + header1ColCnt += scoreOk ? 1 : 0; + header1ColCnt += passedOk ? 1 : 0; + header1ColCnt += attemptsOk ? 1 : 0; + header1ColCnt++;//last modified + header1ColCnt += commentOk ? 1 : 0; + header1ColCnt++;//coach comment + } + header1ColCnt--;//column title } - if (scoreOk) - headerRow2.addCell(headerColCnt++, sc); - if (passedOk) - headerRow2.addCell(headerColCnt++, pa); - if (attemptsOk) - headerRow2.addCell(headerColCnt++, at); - headerRow2.addCell(headerColCnt++, slm); - if (commentOk) { - headerRow2.addCell(headerColCnt++, co); - headerRow2.addCell(headerColCnt++, cco); + int header2ColCnt = headerColCnt; + Row headerRow2 = sheet.newRow(); + for(AssessableCourseNode acNode:myNodes) { + if (acNode.getType().equals("ita")) { + headerRow2.addCell(header2ColCnt++, submitted); + } + + boolean scoreOk = acNode.hasScoreConfigured(); + boolean passedOk = acNode.hasPassedConfigured(); + boolean attemptsOk = acNode.hasAttemptsConfigured(); + boolean commentOk = acNode.hasCommentConfigured(); + if (scoreOk || passedOk || commentOk || attemptsOk) { + if(scoreOk) { + headerRow2.addCell(header2ColCnt++, sc); + } + if(passedOk) { + headerRow2.addCell(header2ColCnt++, pa); + } + if(attemptsOk) { + headerRow2.addCell(header2ColCnt++, at); + } + headerRow2.addCell(header2ColCnt++, slm);//last modified + if (commentOk) { + headerRow2.addCell(header2ColCnt++, co); + } + headerRow2.addCell(header2ColCnt++, cco);//coach comment + } } + sheet.setHeaderRows(2); // preload user properties cache CourseEnvironment courseEnvironment = course.getCourseEnvironment(); int rowNumber = 0; - + DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm"); UserCourseInformationsManager mgr = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); OLATResource courseResource = courseEnvironment.getCourseGroupManager().getCourseResource(); Map<Long,Date> firstTimes = mgr.getInitialLaunchDates(courseResource, identities); @@ -488,10 +217,10 @@ public class ScoreAccountingHelper { AssessmentManager am = course.getCourseEnvironment().getAssessmentManager(); for (AssessableCourseNode acnode:myNodes) { - scoreOk = acnode.hasScoreConfigured(); - passedOk = acnode.hasPassedConfigured(); - attemptsOk = acnode.hasAttemptsConfigured(); - commentOk = acnode.hasCommentConfigured(); + boolean scoreOk = acnode.hasScoreConfigured(); + boolean passedOk = acnode.hasPassedConfigured(); + boolean attemptsOk = acnode.hasAttemptsConfigured(); + boolean commentOk = acnode.hasCommentConfigured(); if (acnode.getType().equals("ita")) { String log = acnode.getUserLog(uce); @@ -502,9 +231,9 @@ public class ScoreAccountingHelper { log = log.substring(0, log.lastIndexOf("submit")); log = log.substring(log.lastIndexOf("date:")); date = log.split("\n")[0].substring(6); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm"); lastUploaded = df.parse(date); } catch (Exception e) { + // } if (lastUploaded != null) { dataRow.addCell(dataColCnt++, lastUploaded, workbook.getStyles().getDateStyle()); @@ -561,18 +290,62 @@ public class ScoreAccountingHelper { } else { dataRow.addCell(dataColCnt++, mi); } + } - // Comments for tutors - String coachComment = am.getNodeCoachComment(acnode, identity); - if (coachComment != null) { - dataRow.addCell(dataColCnt++, coachComment); - } else { - dataRow.addCell(dataColCnt++, mi); - } + // Always export comments for tutors + String coachComment = am.getNodeCoachComment(acnode, identity); + if (coachComment != null) { + dataRow.addCell(dataColCnt++, coachComment); + } else { + dataRow.addCell(dataColCnt++, mi); } } } } + + //min. max. informations + boolean first = true; + for (AssessableCourseNode acnode:myNodes) { + if (!acnode.hasScoreConfigured()) { + // only show min/max/cut legend when score configured + continue; + } + + if(first) { + sheet.newRow().addCell(0, ""); + sheet.newRow().addCell(0, ""); + sheet.newRow().addCell(0, t.translate("legend")); + sheet.newRow().addCell(0, ""); + first = false; + } + + String minVal; + String maxVal; + String cutVal; + if(acnode instanceof STCourseNode || !acnode.hasScoreConfigured()) { + minVal = maxVal = cutVal = "-"; + } else { + minVal = acnode.getMinScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMinScoreConfiguration()); + maxVal = acnode.getMaxScoreConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getMaxScoreConfiguration()); + if (acnode.hasPassedConfigured()) { + cutVal = acnode.getCutValueConfiguration() == null ? "-" : AssessmentHelper.getRoundedScore(acnode.getCutValueConfiguration()); + } else { + cutVal = "-"; + } + } + + sheet.newRow().addCell(0, acnode.getShortTitle()); + + Row minRow = sheet.newRow(); + minRow.addCell(2, "minValue"); + minRow.addCell(3, minVal); + Row maxRow = sheet.newRow(); + maxRow.addCell(2, "maxValue"); + maxRow.addCell(3, maxVal); + Row cutRow = sheet.newRow(); + cutRow.addCell(2, "cutValue"); + cutRow.addCell(3, cutVal); + } IOUtils.closeQuietly(workbook); } diff --git a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java index 05b943d9d0897652eda6ce82f7365c4a0067434e..6a7d93d4b4950608531e22e8a9907144fd460da3 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java @@ -31,12 +31,14 @@ import java.util.concurrent.ConcurrentMap; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.BaseSecurityModule; import org.olat.basesecurity.Group; +import org.olat.core.commons.persistence.SortKey; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableSortOptions; import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; @@ -194,8 +196,10 @@ public class IdentityListCourseNodeController extends FormBasicController implem ? "select" : null; //add the table + FlexiTableSortOptions options = new FlexiTableSortOptions(); FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); if(isAdministrativeUser) { + options.setDefaultOrderBy(new SortKey(IdentityCourseElementCols.username.sortKey(), true)); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(IdentityCourseElementCols.username, select)); } @@ -204,6 +208,9 @@ public class IdentityListCourseNodeController extends FormBasicController implem UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i); boolean visible = UserManager.getInstance().isMandatoryUserProperty(AssessmentToolConstants.usageIdentifyer , userPropertyHandler); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex, select, true, "userProp-" + colIndex)); + if(!options.hasDefaultOrderBy()) { + options.setDefaultOrderBy(new SortKey("userProp-" + colIndex, true)); + } colIndex++; } AssessableCourseNode assessableNode = null; @@ -245,7 +252,8 @@ public class IdentityListCourseNodeController extends FormBasicController implem tableEl.setExportEnabled(true); tableEl.setSearchEnabled(new AssessedIdentityListProvider(getIdentity(), courseEntry, referenceEntry, courseNode.getIdent(), assessmentCallback), ureq.getUserSession()); tableEl.setMultiSelect(!coachCourseEnv.isCourseReadOnly()); - + tableEl.setSortSettings(options); + List<FlexiTableFilter> filters = new ArrayList<>(); filters.add(new FlexiTableFilter(translate("filter.showAll"), "showAll", true)); filters.add(FlexiTableFilter.SPACER); diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java index d65c4cf6345e854192668fbb266ca6087b81504a..d4b86ac953b21b644882ad8a8829257372c11064 100644 --- a/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java +++ b/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java @@ -19,7 +19,6 @@ */ package org.olat.course.member.wizard; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -58,16 +57,11 @@ public class ImportMemberBySearchController extends StepFormBasicController { protected void event(UserRequest ureq, Controller source, Event event) { if(event instanceof SingleIdentityChosenEvent) { SingleIdentityChosenEvent e = (SingleIdentityChosenEvent)event; - String key = e.getChosenIdentity().getKey().toString(); - addToRunContext("keys", Collections.singletonList(key)); + addToRunContext("keyIdentities", Collections.singletonList(e.getChosenIdentity())); fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); } else if(event instanceof MultiIdentityChosenEvent) { MultiIdentityChosenEvent e = (MultiIdentityChosenEvent)event; - List<String> keys = new ArrayList<>(); - for(Identity identity: e.getChosenIdentities()) { - keys.add(identity.getKey().toString()); - } - addToRunContext("keys", keys); + addToRunContext("keyIdentities", e.getChosenIdentities()); fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); } else { super.event(ureq, source, event); @@ -80,11 +74,7 @@ public class ImportMemberBySearchController extends StepFormBasicController { if(identities.isEmpty()) { searchController.doSearch(); } else { - List<String> keys = new ArrayList<>(identities.size()); - for(Identity identity: identities) { - keys.add(identity.getKey().toString()); - } - addToRunContext("keys", keys); + addToRunContext("keyIdentities", identities); fireEvent(ureq, StepsEvent.ACTIVATE_NEXT); } } diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java index 2249ce0eff809757ef4b47323a81facff24d1351..90d8973787bb8612db1ef4ec537350c09f8b8331 100644 --- a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java +++ b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java @@ -20,7 +20,9 @@ package org.olat.course.member.wizard; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.olat.admin.user.UserTableDataModel; import org.olat.basesecurity.BaseSecurity; @@ -28,7 +30,6 @@ import org.olat.basesecurity.BaseSecurityModule; import org.olat.basesecurity.Constants; import org.olat.basesecurity.SecurityGroup; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; @@ -47,6 +48,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.UserConstants; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -60,9 +62,12 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr private List<String> notfounds; private boolean isAdministrativeUser; - private final UserManager userManager; - private final BaseSecurity securityManager; - private final BaseSecurityModule securityModule; + @Autowired + private UserManager userManager; + @Autowired + private BaseSecurity securityManager; + @Autowired + private BaseSecurityModule securityModule; public ImportMemberOverviewIdentitiesController(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext) { super(ureq, wControl, rootForm, runContext, LAYOUT_VERTICAL, null); @@ -78,6 +83,10 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr @SuppressWarnings("unchecked") List<String> keys = (List<String>)runContext.get("keys"); loadModel(keys); + } else if(containsRunContextKey("keyIdentities")) { + @SuppressWarnings("unchecked") + List<Identity> keys = (List<Identity>)runContext.get("keyIdentities"); + loadModelByIdentities(keys); } isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); @@ -129,28 +138,50 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr } private void loadModel(List<String> keys) { - oks = new ArrayList<Identity>(); - List<String> isanonymous = new ArrayList<String>(); - notfounds = new ArrayList<String>(); - + notfounds = new ArrayList<>(); + + Set<Identity> okSet = new HashSet<>(); SecurityGroup anonymousSecGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS); + List<Identity> anonymousUsers = securityManager.getIdentitiesOfSecurityGroup(anonymousSecGroup); + for (String identityKey : keys) { Identity ident = securityManager.loadIdentityByKey(Long.parseLong(identityKey)); if (ident == null) { // not found, add to not-found-list notfounds.add(identityKey); - } else if (securityManager.isIdentityInSecurityGroup(ident, anonymousSecGroup)) { - isanonymous.add(identityKey); - } else if (!PersistenceHelper.containsPersistable(oks, ident)) { - oks.add(ident); + } else if (anonymousUsers.contains(ident)) { + //ignore + } else if (!okSet.contains(ident)) { + okSet.add(ident); } } + oks = new ArrayList<>(okSet); + } + + private void loadModelByIdentities(List<Identity> keys) { + notfounds = new ArrayList<>(); + + Set<Identity> okSet = new HashSet<>(); + SecurityGroup anonymousSecGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS); + List<Identity> anonymousUsers = securityManager.getIdentitiesOfSecurityGroup(anonymousSecGroup); + + for (Identity ident : keys) { + if (ident == null || anonymousUsers.contains(ident)) { + //ignore + } else if (!okSet.contains(ident)) { + okSet.add(ident); + } + } + oks = new ArrayList<>(okSet); } private void loadModel(String inp) { - oks = new ArrayList<Identity>(); - notfounds = new ArrayList<String>(); + oks = new ArrayList<>(); + notfounds = new ArrayList<>(); + + Set<Identity> okSet = new HashSet<>(); - SecurityGroup anonymousSecGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS); + SecurityGroup anonymousGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS); + Set<Identity> anonymousUsers = new HashSet<>(securityManager.getIdentitiesOfSecurityGroup(anonymousGroup)); List<String> identList = new ArrayList<String>(); String[] lines = inp.split("\r?\n"); @@ -168,13 +199,12 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr if(userIdent != null) { identList.remove(userIdent); } - if (!PersistenceHelper.containsPersistable(oks, identity) - && !securityManager.isIdentityInSecurityGroup(identity, anonymousSecGroup)) { - oks.add(identity); + if (!okSet.contains(identity) && !anonymousUsers.contains(identity)) { + okSet.add(identity); } } // make a lowercase copy of identList for processing username and email - List<String> identListLowercase = new ArrayList<String>(identList.size()); + List<String> identListLowercase = new ArrayList<>(identList.size()); for (String ident:identList) { identListLowercase.add(ident.toLowerCase()); } @@ -182,14 +212,20 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr List<Identity> identities = securityManager.findIdentitiesByNameCaseInsensitive(identListLowercase); for(Identity identity:identities) { identListLowercase.remove(identity.getName().toLowerCase()); - if (!PersistenceHelper.containsPersistable(oks, identity) - && !securityManager.isIdentityInSecurityGroup(identity, anonymousSecGroup)) { - oks.add(identity); + if (!okSet.contains(identity) && !anonymousUsers.contains(identity)) { + okSet.add(identity); } } //search by email, case insensitive - List<Identity> mailIdentities = userManager.findIdentitiesByEmail(identListLowercase); + List<String> emailListLowercase = new ArrayList<>(identList.size()); + for (String ident:identList) { + if(ident.indexOf('@') > 0) { + emailListLowercase.add(ident.toLowerCase()); + } + } + + List<Identity> mailIdentities = userManager.findIdentitiesByEmail(emailListLowercase); for(Identity identity:mailIdentities) { String email = identity.getUser().getProperty(UserConstants.EMAIL, null); if(email != null) { @@ -199,13 +235,13 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr if(institutEmail != null) { identListLowercase.remove(institutEmail.toLowerCase()); } - if (!PersistenceHelper.containsPersistable(oks, identity) - && !securityManager.isIdentityInSecurityGroup(identity, anonymousSecGroup)) { - oks.add(identity); + if (!okSet.contains(identity) && !anonymousUsers.contains(identity)) { + okSet.add(identity); } } notfounds.addAll(identListLowercase); + oks = new ArrayList<>(okSet); } public boolean validate() { diff --git a/src/main/java/org/olat/course/nodes/TACourseNode.java b/src/main/java/org/olat/course/nodes/TACourseNode.java index 01c7728dae986c57b763838ba786663a8f4cc3be..1c3555b4ce569d06f0dfce5d23e51332a293d300 100644 --- a/src/main/java/org/olat/course/nodes/TACourseNode.java +++ b/src/main/java/org/olat/course/nodes/TACourseNode.java @@ -37,7 +37,6 @@ import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.commons.io.IOUtils; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.vfs.OlatNamedContainerImpl; @@ -63,6 +62,7 @@ import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.ZipUtil; +import org.olat.core.util.io.ShieldOutputStream; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSManager; import org.olat.course.ICourse; @@ -861,13 +861,13 @@ public class TACourseNode extends GenericCourseNode implements PersistentAssessa } String courseTitle = course.getCourseTitle(); - String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xls"); + String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(this); - String s = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, locale); // write course results overview table to filesystem try { exportStream.putNextEntry(new ZipEntry(dirName + "/" + fileName)); - IOUtils.write(s, exportStream); + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, + new ShieldOutputStream(exportStream)); exportStream.closeEntry(); } catch (IOException e) { log.error("", e); 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 df3461698648dc67e1e98794118c0906e8c75852..2284d4566e6fe6e6ae435c5e1b8772af5ef88c6f 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 @@ -202,7 +202,8 @@ public class CheckListAssessmentController extends FormBasicController implement FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); if(isAdministrativeUser) { - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.username.i18nKey(), Cols.username.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.username.i18nKey(), Cols.username.ordinal(), + true, Cols.username.name())); } int i=0; @@ -247,7 +248,7 @@ public class CheckListAssessmentController extends FormBasicController implement } List<CheckListAssessmentRow> datas = loadDatas(); - model = new CheckListAssessmentDataModel(checkboxList, datas, columnsModel); + model = new CheckListAssessmentDataModel(checkboxList, datas, columnsModel, getLocale()); table = uifactory.addTableElement(getWindowControl(), "checkbox-list", model, getTranslator(), formLayout); if(coachCourseEnv instanceof UserCourseEnvironmentImpl) { UserCourseEnvironmentImpl env = (UserCourseEnvironmentImpl)coachCourseEnv; diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModel.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModel.java index 6d0d718aaeda8197049e88215d39ae4dc1518326..5d04477f146e9ee7d5aaff40503db39b06875fe8 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModel.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModel.java @@ -24,6 +24,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import org.apache.commons.lang.StringEscapeUtils; import org.olat.core.commons.persistence.SortKey; @@ -36,7 +37,6 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColum import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; -import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.render.EmptyURLBuilder; import org.olat.core.gui.render.StringOutput; @@ -70,13 +70,15 @@ public class CheckListAssessmentDataModel extends DefaultFlexiTableDataModel<Che public static final int USER_PROPS_OFFSET = 500; public static final int CHECKBOX_OFFSET = 5000; + private final Locale locale; private final CheckboxList checkboxList; private List<CheckListAssessmentRow> backupRows; public CheckListAssessmentDataModel(CheckboxList checkboxList, List<CheckListAssessmentRow> datas, - FlexiTableColumnModel columnModel) { + FlexiTableColumnModel columnModel, Locale locale) { super(datas, columnModel); backupRows = datas; + this.locale = locale; this.checkboxList = checkboxList; } @@ -89,13 +91,12 @@ public class CheckListAssessmentDataModel extends DefaultFlexiTableDataModel<Che @Override public DefaultFlexiTableDataModel<CheckListAssessmentRow> createCopyWithEmptyList() { - return new CheckListAssessmentDataModel(checkboxList, new ArrayList<CheckListAssessmentRow>(), getTableColumnModel()); + return new CheckListAssessmentDataModel(checkboxList, new ArrayList<CheckListAssessmentRow>(), getTableColumnModel(), locale); } @Override public void sort(SortKey orderBy) { - SortableFlexiTableModelDelegate<CheckListAssessmentRow> sorter - = new SortableFlexiTableModelDelegate<>(orderBy, this, null); + CheckListAssessmentDataModelSorter sorter = new CheckListAssessmentDataModelSorter(orderBy, this, locale); List<CheckListAssessmentRow> views = sorter.sort(); super.setObjects(views); } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModelSorter.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModelSorter.java new file mode 100644 index 0000000000000000000000000000000000000000..d1916d141f552ec72955d538ae90f246b34eca99 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentDataModelSorter.java @@ -0,0 +1,92 @@ +/** + * <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.course.nodes.cl.ui; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; + +/** + * + * Initial date: 5 févr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CheckListAssessmentDataModelSorter extends SortableFlexiTableModelDelegate<CheckListAssessmentRow> { + + public CheckListAssessmentDataModelSorter(SortKey orderBy, CheckListAssessmentDataModel model, Locale locale) { + super(orderBy, model, locale); + } + + @Override + protected void sort(List<CheckListAssessmentRow> rows) { + int columnIndex = getColumnIndex(); + if(columnIndex >= CheckListAssessmentDataModel.CHECKBOX_OFFSET) { + int checkBoxIndex = columnIndex - CheckListAssessmentDataModel.CHECKBOX_OFFSET; + Collections.sort(rows, new CheckBoxComparator(checkBoxIndex)); + + } else { + super.sort(rows); + } + } + + private class CheckBoxComparator implements Comparator<CheckListAssessmentRow> { + + private final int checkBoxIndex; + + public CheckBoxComparator(int checkBoxIndex) { + this.checkBoxIndex = checkBoxIndex; + } + + @Override + public int compare(CheckListAssessmentRow o1, CheckListAssessmentRow o2) { + if(o1 == null || o2 == null) { + return compareNullObjects(o1, o2); + } + + boolean c1 = isCheck(o1); + boolean c2 = isCheck(o2); + int c = compareBooleans(c1, c2); + return c; + } + + private boolean isCheck(CheckListAssessmentRow row) { + if(row.getCheckedEl() != null) { + //edit mode + MultipleSelectionElement[] checked = row.getCheckedEl(); + if(checked != null && checkBoxIndex >= 0 && checkBoxIndex < checked.length) { + return checked[checkBoxIndex].isAtLeastSelected(1); + } + } + + Boolean[] checked = row.getChecked(); + if(checked != null && checkBoxIndex >= 0 && checkBoxIndex < checked.length + && checked[checkBoxIndex] != null) { + return checked[checkBoxIndex].booleanValue(); + } + return false; + } + } +} diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentController.java index 208d65a756a51f298d42340326bf846696635973..940cf1297817f80ea662d19804f70c4cc9f1d55a 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentController.java @@ -130,10 +130,10 @@ public class CheckboxAssessmentController extends FormBasicController { protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormDescription("assessment.checkbox.description"); - FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); if(isAdministrativeUser) { - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.username.i18nKey(), Cols.username.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.username.i18nKey(), Cols.username.ordinal(), + true, Cols.username.name())); } int i=0; @@ -157,9 +157,11 @@ public class CheckboxAssessmentController extends FormBasicController { } } - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.check.i18nKey(), Cols.check.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.check.i18nKey(), Cols.check.ordinal(), + true, Cols.check.name())); if(withScore) { - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.points.i18nKey(), Cols.points.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.points.i18nKey(), Cols.points.ordinal(), + true, Cols.points.name())); } int numOfCheckbox = checkboxList.getList().size(); String[] keys = new String[numOfCheckbox]; @@ -217,7 +219,7 @@ public class CheckboxAssessmentController extends FormBasicController { boxRows.add(row); } - model = new CheckboxAssessmentDataModel(boxRows, columnsModel); + model = new CheckboxAssessmentDataModel(boxRows, columnsModel, getLocale()); table = uifactory.addTableElement(getWindowControl(), "checkbox-list", model, getTranslator(), formLayout); table.setCustomizeColumns(true); table.setEditMode(true); diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModel.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModel.java index 0bfd163eee68cb8382fd20f1b5c153388a168ba1..94ad376fdb7b51743f86d274e070e9ee3a12a444 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModel.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModel.java @@ -21,12 +21,12 @@ package org.olat.course.nodes.cl.ui; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.olat.core.commons.persistence.SortKey; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; -import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; /** * @@ -36,20 +36,22 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFl */ public class CheckboxAssessmentDataModel extends DefaultFlexiTableDataModel<CheckboxAssessmentRow> implements SortableFlexiTableDataModel<CheckboxAssessmentRow> { + + private final Locale locale; - public CheckboxAssessmentDataModel(List<CheckboxAssessmentRow> datas, FlexiTableColumnModel columnModel) { + public CheckboxAssessmentDataModel(List<CheckboxAssessmentRow> datas, FlexiTableColumnModel columnModel, Locale locale) { super(datas, columnModel); + this.locale = locale; } @Override public DefaultFlexiTableDataModel<CheckboxAssessmentRow> createCopyWithEmptyList() { - return new CheckboxAssessmentDataModel(new ArrayList<CheckboxAssessmentRow>(), getTableColumnModel()); + return new CheckboxAssessmentDataModel(new ArrayList<CheckboxAssessmentRow>(), getTableColumnModel(), locale); } @Override public void sort(SortKey orderBy) { - SortableFlexiTableModelDelegate<CheckboxAssessmentRow> sorter - = new SortableFlexiTableModelDelegate<>(orderBy, this, null); + CheckboxAssessmentDataModelSorter sorter = new CheckboxAssessmentDataModelSorter(orderBy, this, locale); List<CheckboxAssessmentRow> views = sorter.sort(); super.setObjects(views); } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java new file mode 100644 index 0000000000000000000000000000000000000000..657ca3acb19b966b7994211ad5aaa9956056ed70 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java @@ -0,0 +1,88 @@ +package org.olat.course.nodes.cl.ui; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.cl.ui.CheckboxAssessmentDataModel.Cols; + +/** + * + * Initial date: 5 févr. 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CheckboxAssessmentDataModelSorter extends SortableFlexiTableModelDelegate<CheckboxAssessmentRow> { + + public CheckboxAssessmentDataModelSorter(SortKey orderBy, CheckboxAssessmentDataModel model, Locale locale) { + super(orderBy, model, locale); + } + + @Override + protected void sort(List<CheckboxAssessmentRow> rows) { + int columnIndex = getColumnIndex(); + if(columnIndex == Cols.check.ordinal()) { + Collections.sort(rows, new CheckBoxComparator()); + + } else if(columnIndex == Cols.points.ordinal()) { + Collections.sort(rows, new PointsComparator()); + } else { + super.sort(rows); + } + } + + private class PointsComparator implements Comparator<CheckboxAssessmentRow> { + @Override + public int compare(CheckboxAssessmentRow o1, CheckboxAssessmentRow o2) { + if(o1 == null || o2 == null) { + return compareNullObjects(o1, o2); + } + + TextElement e1 = o1.getPointEl(); + TextElement e2 = o2.getPointEl(); + if(e1 == null || e2 == null) { + return compareNullObjects(e1, e2); + } + + double t1 = parseDouble(e1); + double t2 = parseDouble(e2); + return compareDoubles(t1, t2); + } + + private double parseDouble(TextElement el) { + if(el != null && StringHelper.containsNonWhitespace(el.getValue())) { + try { + return Double.parseDouble(el.getValue()); + } catch (NumberFormatException e) { + //ignore parsing error, the validation take this + } + } + return 0.0d; + } + } + + private class CheckBoxComparator implements Comparator<CheckboxAssessmentRow> { + @Override + public int compare(CheckboxAssessmentRow o1, CheckboxAssessmentRow o2) { + if(o1 == null || o2 == null) { + return compareNullObjects(o1, o2); + } + + MultipleSelectionElement e1 = o1.getCheckedEl(); + MultipleSelectionElement e2 = o2.getCheckedEl(); + if(e1 == null || e2 == null) { + return compareNullObjects(e1, e2); + } + + boolean b1 = e1.isAtLeastSelected(1); + boolean b2 = e2.isAtLeastSelected(1); + return compareBooleans(b1, b2); + } + } +} diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java index 24884835631432e12e45a441484ec51012c6659f..fe75173fcd7ed7ee2a386575b98a2ac23c782870 100644 --- a/src/main/java/org/olat/repository/RepositoryManager.java +++ b/src/main/java/org/olat/repository/RepositoryManager.java @@ -1811,9 +1811,13 @@ public class RepositoryManager { boolean allOk = repositoryEntryRelationDao.removeMembers(re, members); if (allOk) { + int count = 0; List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for(Identity identity:members) { deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + if(++count % 100 == 0) { + dbInstance.commitAndCloseSession(); + } } dbInstance.commit(); sendDeferredEvents(deferredEvents, re); @@ -2268,12 +2272,16 @@ public class RepositoryManager { public void updateRepositoryEntryMemberships(Identity ureqIdentity, Roles ureqRoles, RepositoryEntry re, List<RepositoryEntryPermissionChangeEvent> changes, MailPackage mailing) { + int count = 0; List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for(RepositoryEntryPermissionChangeEvent e:changes) { updateRepositoryEntryMembership(ureqIdentity, ureqRoles, re, e, mailing, deferredEvents); + if(++count % 100 == 0) { + dbInstance.commitAndCloseSession(); + } } - dbInstance.commit(); + dbInstance.commitAndCloseSession(); sendDeferredEvents(deferredEvents, re); } diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java index 459a6519ba81662e0c57fa59f0c0b6d57ab7c793..6d7bebaca35260ca20361fcd776c6c9fddf58973 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java +++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java @@ -181,6 +181,14 @@ public class RepositoryEntryRelationDAO { return groupDao.removeMemberships(group, role); } + /** + * Retrieve the default group of the repository entry (the one + * marked with the flag defaultGroup=true). The query is cached + * by hibernate 2nd level cache. + * + * @param re The repository entry + * @return The group + */ public Group getDefaultGroup(RepositoryEntryRef re) { StringBuilder sb = new StringBuilder(); sb.append("select baseGroup from ").append(RepositoryEntry.class.getName()).append(" as v ") @@ -190,6 +198,7 @@ public class RepositoryEntryRelationDAO { return dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Group.class) .setParameter("repoKey", re.getKey()) + .setHint("org.hibernate.cacheable", Boolean.TRUE) .getSingleResult(); } diff --git a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java index 91e6fd1d8264bf4edde6bdc8e76bdf8df7661bfc..e68fb702172729c90ef3bd5c88d4fedd3c71acc7 100644 --- a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java +++ b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java @@ -247,6 +247,26 @@ public class BaseSecurityManagerTest extends OlatTestCase { Assert.assertTrue(foundIds.contains(id2)); } + @Test + public void findIdentitiesByNumber() { + //create a user it + String username = "fINd-ME-6-" + UUID.randomUUID(); + String institutionalNumber = UUID.randomUUID().toString(); + Identity id = JunitTestHelper.createAndPersistIdentityAsUser(username); + id.getUser().setProperty(UserConstants.INSTITUTIONALUSERIDENTIFIER, institutionalNumber); + userManager.updateUserFromIdentity(id); + dbInstance.commitAndCloseSession(); + + List<String> numbers = new ArrayList<String>(2); + numbers.add(institutionalNumber); + + //find it + List<Identity> foundIds = securityManager.findIdentitiesByNumber(numbers); + Assert.assertNotNull(foundIds); + Assert.assertEquals(1, foundIds.size()); + Assert.assertTrue(foundIds.contains(id)); + } + @Test public void loadIdentityShortByKey() { //create a user it