From 614589d72aa3560ac5f251774dcf13ae9370543d Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 16 Jun 2017 17:07:59 +0200 Subject: [PATCH] OO-2636: add new item in coaching tool to search the lectures --- .../modules/coach/ui/CoachMainController.java | 24 ++ .../coach/ui/_i18n/LocalStrings_de.properties | 3 +- .../coach/ui/_i18n/LocalStrings_en.properties | 3 +- .../olat/modules/lecture/LectureService.java | 11 + .../manager/LectureBlockRollCallDAO.java | 278 ++++++++++++++++- .../lecture/manager/LectureServiceImpl.java | 15 + .../model/LectureBlockIdentityStatistics.java | 28 ++ .../LectureStatisticsSearchParameters.java | 72 +++++ .../lecture/ui/LecturesListController.java | 111 +++++++ .../lecture/ui/LecturesListDataModel.java | 115 ++++++++ .../lecture/ui/LecturesSearchController.java | 106 +++++++ .../ui/LecturesSearchFormController.java | 279 ++++++++++++++++++ .../lecture/ui/_content/cycle_dates.html | 6 + .../ui/_content/lectures_coaching.html | 1 + .../ui/_i18n/LocalStrings_de.properties | 12 + .../ui/_i18n/LocalStrings_en.properties | 12 + .../_spring/userPropertiesContext.xml | 23 ++ 17 files changed, 1095 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/olat/modules/lecture/model/LectureBlockIdentityStatistics.java create mode 100644 src/main/java/org/olat/modules/lecture/model/LectureStatisticsSearchParameters.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/LecturesListController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/LecturesListDataModel.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/LecturesSearchController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/LecturesSearchFormController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/cycle_dates.html create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/lectures_coaching.html diff --git a/src/main/java/org/olat/modules/coach/ui/CoachMainController.java b/src/main/java/org/olat/modules/coach/ui/CoachMainController.java index bf95fa49a00..6a0b4c87025 100644 --- a/src/main/java/org/olat/modules/coach/ui/CoachMainController.java +++ b/src/main/java/org/olat/modules/coach/ui/CoachMainController.java @@ -43,7 +43,10 @@ import org.olat.core.id.context.StateEntry; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.tree.TreeHelper; +import org.olat.modules.lecture.LectureModule; +import org.olat.modules.lecture.ui.LecturesSearchController; import org.olat.util.logging.activity.LoggingResourceable; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -64,6 +67,10 @@ public class CoachMainController extends MainLayoutBasicController implements Ac private CourseListController courseListCtrl; private StudentListController studentListCtrl; private LayoutMain3ColsController columnLayoutCtr; + private LecturesSearchController lecturesSearchCtrl; + + @Autowired + private LectureModule lectureModule; public CoachMainController(UserRequest ureq, WindowControl control) { super(ureq, control); @@ -145,6 +152,15 @@ public class CoachMainController extends MainLayoutBasicController implements Ac listenTo(courseListCtrl); } selectedCtrl = courseListCtrl; + } else if("lectures".equalsIgnoreCase(cmd)) { + if(lecturesSearchCtrl == null) { + OLATResourceable ores = OresHelper.createOLATResourceableInstance("Lectures", 0l); + ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores)); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ores, null, getWindowControl()); + lecturesSearchCtrl = new LecturesSearchController(ureq, bwControl, content); + listenTo(lecturesSearchCtrl); + } + selectedCtrl = lecturesSearchCtrl; } else if("search".equalsIgnoreCase(cmd)) { if(userSearchCtrl == null) { OLATResourceable ores = OresHelper.createOLATResourceableInstance("Search", 0l); @@ -191,6 +207,14 @@ public class CoachMainController extends MainLayoutBasicController implements Ac courses.setAltText(translate("courses.menu.title.alt")); root.addChild(courses); + if(lectureModule.isEnabled()) { + GenericTreeNode lectures = new GenericTreeNode(); + lectures.setUserObject("Lectures"); + lectures.setTitle(translate("lectures.menu.title")); + lectures.setAltText(translate("courses.menu.title.alt")); + root.addChild(lectures); + } + Roles roles = ureq.getUserSession().getRoles(); if(roles.isUserManager() || roles.isOLATAdmin()) { GenericTreeNode search = new GenericTreeNode(); diff --git a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties index d590614e3fd..7bbe6356bec 100644 --- a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_de.properties @@ -15,7 +15,8 @@ error.search.form.too.many=Die Suche lieferte zu viele Treffer. Bitte schr\u00e4 group.name=Name groups.menu.title.alt=Gruppen groups.menu.title=Gruppen - +lectures.menu.title=Lektionen +lectures.menu.title.alt=Lektionen und Absenzmanagement home.link=Visitenkarte main.menu.title.alt=\$:site.title.alt main.menu.title=\$:site.title diff --git a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_en.properties index 5ad2932ee51..992686f67f5 100644 --- a/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/coach/ui/_i18n/LocalStrings_en.properties @@ -15,7 +15,8 @@ error.search.form.too.many=Too many search results. Please narrow down your sear group.name=Name groups.menu.title.alt=Groups groups.menu.title=Groups - +lectures.menu.title=Lectures +lectures.menu.title.alt=Lectures and absences management home.link=visiting card main.menu.title.alt=$\:site.title.alt main.menu.title=$\:site.title diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java index e5a80313b2a..d5a19343771 100644 --- a/src/main/java/org/olat/modules/lecture/LectureService.java +++ b/src/main/java/org/olat/modules/lecture/LectureService.java @@ -25,10 +25,13 @@ import org.olat.basesecurity.Group; import org.olat.basesecurity.IdentityRef; import org.olat.core.id.Identity; import org.olat.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; import org.olat.modules.lecture.model.LectureBlockStatistics; import org.olat.modules.lecture.model.LectureBlockWithTeachers; +import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; +import org.olat.user.propertyhandlers.UserPropertyHandler; /** * @@ -351,6 +354,14 @@ public interface LectureService { */ public List<LectureBlockStatistics> getParticipantsLecturesStatistics(RepositoryEntry entry); + /** + * + * @param params + * @return + */ + public List<LectureBlockIdentityStatistics> getLecturesStatistics(LectureStatisticsSearchParameters params, + List<UserPropertyHandler> userPropertyHandlers, Identity identity, boolean admin); + /** * The list of roll calls within the specified course for the specified user * after it's first admission. diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java index ff7b34a5a3a..37a602b7dd6 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java @@ -25,11 +25,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.persistence.TemporalType; +import javax.persistence.TypedQuery; + import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.modules.lecture.LectureBlock; import org.olat.modules.lecture.LectureBlockRef; import org.olat.modules.lecture.LectureBlockRollCall; @@ -37,11 +41,14 @@ import org.olat.modules.lecture.LectureBlockStatus; import org.olat.modules.lecture.LectureRollCallStatus; import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; import org.olat.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; import org.olat.modules.lecture.model.LectureBlockRollCallImpl; import org.olat.modules.lecture.model.LectureBlockStatistics; +import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -338,6 +345,207 @@ public class LectureBlockRollCallDAO { calculateAttendanceRate(statisticsList, countAuthorizedAbsenceAsAttendant); return statisticsList; } + + public List<LectureBlockIdentityStatistics> getStatistics(LectureStatisticsSearchParameters params, + List<UserPropertyHandler> userPropertyHandlers, Identity identity, boolean admin, + boolean absenceDefaultAuthorized, boolean countAuthorizedAbsenceAsAttendant, + boolean calculateAttendanceRate, double requiredAttendanceRateDefault) { + + StringBuilder sb = new StringBuilder(2048); + sb.append("select ident.key as participantKey, ") + .append(" call.lecturesAttendedNumber as attendedLectures,") + .append(" call.lecturesAbsentNumber as absentLectures,") + .append(" call.absenceAuthorized as absenceAuthorized,") + .append(" block.key as blockKey,") + .append(" block.compulsory as compulsory,") + .append(" block.plannedLecturesNumber as blockPlanned,") + .append(" block.effectiveLecturesNumber as blockEffective,") + .append(" block.statusString as status,") + .append(" block.rollCallStatusString as rollCallStatus,") + .append(" block.endDate as rollCallEndDate,") + .append(" re.key as repoKey,") + .append(" re.displayname as repoDisplayName,") + .append(" config.overrideModuleDefault as overrideDef,") + .append(" config.calculateAttendanceRate as calculateRate,") + .append(" config.requiredAttendanceRate as repoConfigRate,")//rate enabled + .append(" summary.firstAdmissionDate as firstAdmissionDate,") + .append(" summary.requiredAttendanceRate as summaryRate"); + for(UserPropertyHandler handler:userPropertyHandlers) { + sb.append(", user.").append(handler.getName()).append(" as ").append("p_").append(handler.getName()); + } + sb.append(" from lectureblock block") + .append(" inner join block.entry re") + .append(" inner join block.groups blockToGroup") + .append(" inner join blockToGroup.group bGroup") + .append(" inner join bGroup.members membership") + .append(" inner join membership.identity ident") + .append(" inner join ident.user user") + .append(" left join lectureentryconfig as config on (re.key=config.entry.key)") + .append(" left join lectureblockrollcall as call on (call.identity.key=membership.identity.key and call.lectureBlock.key=block.key)") + .append(" left join lectureparticipantsummary as summary on (summary.identity.key=membership.identity.key and summary.entry.key=block.entry.key)") + .append(" where membership.role='").append(GroupRoles.participant.name()).append("'"); + if(!admin) { + sb.append(" and (exists (select rel from repoentrytogroup as rel, bgroupmember as membership ") + .append(" where re.key=rel.entry.key and membership.group.key=rel.group.key and rel.defaultGroup=true and membership.identity.key=:identityKey") + .append(" and membership.role='").append(GroupRoles.owner.name()).append("'") + .append(" and re.access >= ").append(RepositoryEntry.ACC_OWNERS) + .append(" ) or exists (select membership.key from bgroupmember as membership ") + .append(" where block.teacherGroup.key=membership.group.key and membership.identity.key=:identityKey") + .append(" and (re.access >= ").append(RepositoryEntry.ACC_USERS) + .append(" or (re.access = ").append(RepositoryEntry.ACC_OWNERS).append(" and re.membersOnly=true))") + .append(" ))"); + } else { + sb.append(" and re.access >= ").append(RepositoryEntry.ACC_OWNERS); + } + + if(params.getLifecycle() != null) { + sb.append(" and re.lifecycle.key=:lifecycleKey"); + } + if(params.getStartDate() != null) { + sb.append(" and block.startDate>=:startDate"); + } + if(params.getEndDate() != null) { + sb.append(" and block.endDate<=:endDate"); + } + if(params.getBulkIdentifiers() != null && params.getBulkIdentifiers().size() > 0) { + sb.append(" and (") + .append(" lower(ident.name) in (:bulkIdentifiers)") + .append(" or lower(ident.externalId) in (:bulkIdentifiers)") + .append(" or lower(user.email) in (:bulkIdentifiers)") + .append(" or lower(user.institutionalEmail) in (:bulkIdentifiers)") + .append(")"); + } + + Map<String,Object> queryParams = new HashMap<>(); + appendUsersStatisticsSearchParams(params, queryParams, sb); + + TypedQuery<Object[]> rawQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class); + if(StringHelper.containsNonWhitespace(params.getLogin())) { + rawQuery.setParameter("login", params.getLogin()); + } + if(params.getLifecycle() != null) { + rawQuery.setParameter("lifecycleKey", params.getLifecycle().getKey()); + } + if(params.getStartDate() != null) { + rawQuery.setParameter("startDate", params.getStartDate(), TemporalType.TIMESTAMP); + } + if(params.getEndDate() != null) { + rawQuery.setParameter("endDate", params.getEndDate(), TemporalType.TIMESTAMP); + } + if(params.getBulkIdentifiers() != null && params.getBulkIdentifiers().size() > 0) { + rawQuery.setParameter("bulkIdentifiers", params.getBulkIdentifiers()); + } + for(Map.Entry<String, Object> entry:queryParams.entrySet()) { + rawQuery.setParameter(entry.getKey(), entry.getValue()); + } + if(!admin) { + rawQuery.setParameter("identityKey", identity.getKey()); + } + + Date now = new Date(); + List<Object[]> rawObjects = rawQuery.getResultList(); + Map<Membership,LectureBlockIdentityStatistics> stats = new HashMap<>(); + for(Object[] rawObject:rawObjects) { + int pos = 0;//jump roll call key + Long identityKey = (Long)rawObject[pos++]; + Long lecturesAttended = PersistenceHelper.extractLong(rawObject, pos++); + Long lecturesAbsent = PersistenceHelper.extractLong(rawObject, pos++); + Boolean absenceAuthorized = (Boolean)rawObject[pos++]; + + pos++;//jump block key + boolean compulsory = PersistenceHelper.extractBoolean(rawObject, pos++, true); + Long plannedLecturesNumber = PersistenceHelper.extractLong(rawObject, pos++); + Long effectiveLecturesNumber = PersistenceHelper.extractLong(rawObject, pos++); + if(effectiveLecturesNumber == null) { + effectiveLecturesNumber = plannedLecturesNumber; + } + String status = (String)rawObject[pos++]; + String rollCallStatus = (String)rawObject[pos++]; + Date rollCallEndDate = (Date)rawObject[pos++]; + + //entry and config + Long repoKey = PersistenceHelper.extractLong(rawObject, pos++); + String repoDisplayname = (String)rawObject[pos++]; + boolean overrideDefault = PersistenceHelper.extractBoolean(rawObject, pos++, false); + Boolean repoCalculateRate = (Boolean)rawObject[pos++]; + Double repoRequiredRate = (Double)rawObject[pos++]; + + //summary + Date firstAdmissionDate = (Date)rawObject[pos++]; + Double persoRequiredRate = (Double)rawObject[pos++]; + + LectureBlockIdentityStatistics entryStatistics; + + Membership memberKey = new Membership(identityKey, repoKey); + if(stats.containsKey(memberKey)) { + entryStatistics = stats.get(memberKey); + } else { + + //user data + int numOfProperties = userPropertyHandlers.size(); + String[] identityProps = new String[numOfProperties]; + for(int i=0; i<numOfProperties; i++) { + identityProps[i] = (String)rawObject[pos++]; + } + + entryStatistics = createIdentityStatistics(identityKey, identityProps, + repoKey, repoDisplayname, + overrideDefault, repoCalculateRate, repoRequiredRate, + persoRequiredRate, calculateAttendanceRate, requiredAttendanceRateDefault); + stats.put(memberKey, entryStatistics); + } + + appendStatistics(entryStatistics, compulsory, status, + rollCallEndDate, rollCallStatus, + lecturesAttended, lecturesAbsent, + absenceAuthorized, absenceDefaultAuthorized, + plannedLecturesNumber, effectiveLecturesNumber, + firstAdmissionDate, now); + } + + List<LectureBlockIdentityStatistics> statisticsList = new ArrayList<>(stats.values()); + calculateAttendanceRate(statisticsList, countAuthorizedAbsenceAsAttendant); + return statisticsList; + } + + private void appendUsersStatisticsSearchParams(LectureStatisticsSearchParameters params, Map<String,Object> queryParams, StringBuilder sb) { + if(StringHelper.containsNonWhitespace(params.getLogin())) { + String login = PersistenceHelper.makeFuzzyQueryString(params.getLogin()); + if (login.contains("_") && dbInstance.isOracle()) { + //oracle needs special ESCAPE sequence to search for escaped strings + sb.append(" and lower(ident.name) like :login ESCAPE '\\'"); + } else if (dbInstance.isMySQL()) { + sb.append(" and ident.name like :login"); + } else { + sb.append(" and lower(ident.name) like :login"); + } + queryParams.put("login", login); + } + + if(params.getUserProperties() != null && params.getUserProperties().size() > 0) { + Map<String,String> searchParams = new HashMap<>(params.getUserProperties()); + + int count = 0; + for(Map.Entry<String, String> entry:searchParams.entrySet()) { + String propName = entry.getKey(); + String propValue = entry.getValue(); + String qName = "p_" + ++count; + + UserPropertyHandler handler = userManager.getUserPropertiesConfig().getPropertyHandler(propName); + if(dbInstance.isMySQL()) { + sb.append(" and user.").append(handler.getName()).append(" like :").append(qName); + } else { + sb.append(" and lower(user.").append(handler.getName()).append(") like :").append(qName); + if(dbInstance.isOracle()) { + sb.append(" escape '\\'"); + } + } + queryParams.put(qName, PersistenceHelper.makeFuzzyQueryString(propValue)); + } + } + } + public List<LectureBlockStatistics> getStatistics(RepositoryEntry entry, RepositoryEntryLectureConfiguration config, @@ -424,7 +632,7 @@ public class LectureBlockRollCallDAO { return statisticsList; } - private void calculateAttendanceRate(List<LectureBlockStatistics> statisticsList, boolean countAuthorizedAbsenceAsAttendant) { + private void calculateAttendanceRate(List<? extends LectureBlockStatistics> statisticsList, boolean countAuthorizedAbsenceAsAttendant) { for(LectureBlockStatistics statistics:statisticsList) { long totalAttendedLectures = statistics.getTotalEffectiveLectures(); long totalAbsentLectures = statistics.getTotalAbsentLectures(); @@ -452,6 +660,24 @@ public class LectureBlockRollCallDAO { Boolean overrideDefault, Boolean repoCalculateRate, Double repoRequiredRate, Double persoRequiredRate, boolean calculateAttendanceRate, double requiredAttendanceRateDefault) { + RequiredRate requiredRate = calculateRequiredRate(overrideDefault, repoCalculateRate, repoRequiredRate, + persoRequiredRate, calculateAttendanceRate, requiredAttendanceRateDefault); + return new LectureBlockStatistics(identityKey, entryKey, displayName, requiredRate.isCalculateRate(), requiredRate.getRequiredRate()); + } + + private LectureBlockIdentityStatistics createIdentityStatistics(Long identityKey, String[] identityProps, Long entryKey, String displayName, + Boolean overrideDefault, Boolean repoCalculateRate, Double repoRequiredRate, + Double persoRequiredRate, boolean calculateAttendanceRate, double requiredAttendanceRateDefault) { + + RequiredRate requiredRate = calculateRequiredRate(overrideDefault, repoCalculateRate, repoRequiredRate, + persoRequiredRate, calculateAttendanceRate, requiredAttendanceRateDefault); + return new LectureBlockIdentityStatistics(identityKey, identityProps, + entryKey, displayName, requiredRate.isCalculateRate(), requiredRate.getRequiredRate()); + } + + private RequiredRate calculateRequiredRate(Boolean overrideDefault, Boolean repoCalculateRate, Double repoRequiredRate, + Double persoRequiredRate, boolean calculateAttendanceRate, double requiredAttendanceRateDefault) { + final boolean calculateRate; if(repoCalculateRate != null && overrideDefault != null && !overrideDefault.booleanValue()) { calculateRate = repoCalculateRate.booleanValue(); @@ -469,7 +695,8 @@ public class LectureBlockRollCallDAO { requiredRate = requiredAttendanceRateDefault; } } - return new LectureBlockStatistics(identityKey, entryKey, displayName, calculateRate, requiredRate); + + return new RequiredRate(calculateRate, requiredRate); } private void appendStatistics(LectureBlockStatistics statistics, boolean compulsory, String blockStatus, @@ -518,4 +745,51 @@ public class LectureBlockRollCallDAO { statistics.addTotalPlannedLectures(plannedLecturesNumber.longValue()); } } + + private static class Membership { + private final Long identityKey; + private final Long repoEntryKey; + + public Membership(Long identityKey, Long repoEntryKey) { + this.identityKey = identityKey; + this.repoEntryKey = repoEntryKey; + } + + @Override + public int hashCode() { + return identityKey.hashCode() + repoEntryKey.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof Membership) { + Membership membership = (Membership)obj; + return identityKey != null && identityKey.equals(membership.identityKey) + && repoEntryKey != null && repoEntryKey.equals(membership.repoEntryKey); + } + return false; + } + } + + private static class RequiredRate { + + private final boolean calculateRate; + private final double requiredRate; + + public RequiredRate(boolean calculateRate, double requiredRate) { + this.calculateRate = calculateRate; + this.requiredRate = requiredRate; + } + + public boolean isCalculateRate() { + return calculateRate; + } + + public double getRequiredRate() { + return requiredRate; + } + } } diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java index 9d1393427af..acb43e0e74e 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java @@ -66,10 +66,12 @@ import org.olat.modules.lecture.LectureService; import org.olat.modules.lecture.Reason; import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; import org.olat.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; import org.olat.modules.lecture.model.LectureBlockImpl; import org.olat.modules.lecture.model.LectureBlockStatistics; import org.olat.modules.lecture.model.LectureBlockToTeacher; import org.olat.modules.lecture.model.LectureBlockWithTeachers; +import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; import org.olat.modules.lecture.model.ParticipantAndLectureSummary; import org.olat.modules.lecture.ui.ConfigurationHelper; import org.olat.modules.lecture.ui.LectureAdminController; @@ -78,6 +80,7 @@ import org.olat.repository.RepositoryEntryRef; import org.olat.repository.manager.RepositoryEntryDAO; import org.olat.user.UserDataDeletable; import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -593,6 +596,18 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable { absenceDefaultAuthorized, countAuthorizedAbsenceAsAttendant, calculateAttendanceRate, defaultRequiredAttendanceRate); } + + @Override + public List<LectureBlockIdentityStatistics> getLecturesStatistics(LectureStatisticsSearchParameters params, + List<UserPropertyHandler> userPropertyHandlers, Identity identity, boolean admin) { + boolean calculateAttendanceRate = lectureModule.isRollCallCalculateAttendanceRateDefaultEnabled(); + boolean absenceDefaultAuthorized = lectureModule.isAbsenceDefaultAuthorized(); + boolean countAuthorizedAbsenceAsAttendant = lectureModule.isCountAuthorizedAbsenceAsAttendant(); + double defaultRequiredAttendanceRate = lectureModule.getRequiredAttendanceRateDefault(); + return lectureBlockRollCallDao.getStatistics(params, userPropertyHandlers, identity, admin, + absenceDefaultAuthorized, countAuthorizedAbsenceAsAttendant, + calculateAttendanceRate, defaultRequiredAttendanceRate); + } @Override public List<LectureBlockAndRollCall> getParticipantLectureBlocks(RepositoryEntryRef entry, IdentityRef participant) { diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockIdentityStatistics.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockIdentityStatistics.java new file mode 100644 index 00000000000..1e15fb1a0c8 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockIdentityStatistics.java @@ -0,0 +1,28 @@ +package org.olat.modules.lecture.model; + +/** + * + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LectureBlockIdentityStatistics extends LectureBlockStatistics { + + private final String[] identityProps; + + public LectureBlockIdentityStatistics(Long identityKey, String[] identityProps, + Long repoKey, String displayName, boolean calculateRate, double requiredRate) { + super(identityKey, repoKey, displayName, calculateRate, requiredRate); + this.identityProps = identityProps; + } + + public String[] getIdentityProps() { + return identityProps; + } + + public String getIdentityProp(int pos) { + return identityProps[pos]; + } + +} diff --git a/src/main/java/org/olat/modules/lecture/model/LectureStatisticsSearchParameters.java b/src/main/java/org/olat/modules/lecture/model/LectureStatisticsSearchParameters.java new file mode 100644 index 00000000000..374489ad7fb --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/model/LectureStatisticsSearchParameters.java @@ -0,0 +1,72 @@ +package org.olat.modules.lecture.model; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.olat.repository.model.RepositoryEntryLifecycle; + +/** + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LectureStatisticsSearchParameters { + + private String login; + private List<String> bulkIdentifiers; + private Map<String,String> userProperties; + + private Date startDate; + private Date endDate; + private RepositoryEntryLifecycle lifecycle; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public RepositoryEntryLifecycle getLifecycle() { + return lifecycle; + } + + public void setLifecycle(RepositoryEntryLifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + public List<String> getBulkIdentifiers() { + return bulkIdentifiers; + } + + public void setBulkIdentifiers(List<String> bulkIdentifiers) { + this.bulkIdentifiers = bulkIdentifiers; + } + + public Map<String, String> getUserProperties() { + return userProperties; + } + + public void setUserProperties(Map<String, String> userProperties) { + this.userProperties = userProperties; + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LecturesListController.java b/src/main/java/org/olat/modules/lecture/ui/LecturesListController.java new file mode 100644 index 00000000000..a8f6c270ad0 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/LecturesListController.java @@ -0,0 +1,111 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.List; + +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; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +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.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.lecture.LectureModule; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; +import org.olat.modules.lecture.ui.LecturesListDataModel.StatsCols; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LecturesListController extends FormBasicController { + + public static final int USER_PROPS_OFFSET = 500; + + private FlexiTableElement tableEl; + private LecturesListDataModel tableModel; + + private final String propsIdentifier; + private final boolean authorizedAbsenceEnabled; + private final List<UserPropertyHandler> userPropertyHandlers; + private final List<LectureBlockIdentityStatistics> statistics; + + @Autowired + private UserManager userManager; + @Autowired + private LectureModule lectureModule; + + public LecturesListController(UserRequest ureq, WindowControl wControl, + List<LectureBlockIdentityStatistics> statistics, + List<UserPropertyHandler> userPropertyHandlers, String propsIdentifier) { + super(ureq, wControl, "lectures_coaching"); + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + this.statistics = statistics; + this.propsIdentifier = propsIdentifier; + this.userPropertyHandlers = userPropertyHandlers; + authorizedAbsenceEnabled = lectureModule.isAuthorizedAbsenceEnabled(); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, StatsCols.id)); + + int colIndex = USER_PROPS_OFFSET; + for (int i = 0; i < userPropertyHandlers.size(); i++) { + UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i); + boolean visible = userManager.isMandatoryUserProperty(propsIdentifier, userPropertyHandler); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex++, null, + true, userPropertyHandler.i18nColumnDescriptorLabelKey())); + } + + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(StatsCols.entry)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(StatsCols.plannedLectures)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(StatsCols.attendedLectures)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(StatsCols.absentLectures)); + if(authorizedAbsenceEnabled) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(StatsCols.authorizedAbsenceLectures)); + } + + tableModel = new LecturesListDataModel(columnsModel); + tableModel.setObjects(statistics); + tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout); + tableEl.setExportEnabled(true); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + // + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LecturesListDataModel.java b/src/main/java/org/olat/modules/lecture/ui/LecturesListDataModel.java new file mode 100644 index 00000000000..6e3f6a37cc6 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/LecturesListDataModel.java @@ -0,0 +1,115 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.List; + +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.FlexiSortableColumnDef; +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; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; + +/** + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LecturesListDataModel extends DefaultFlexiTableDataModel<LectureBlockIdentityStatistics> +implements SortableFlexiTableDataModel<LectureBlockIdentityStatistics>{ + + public LecturesListDataModel(FlexiTableColumnModel columnModel) { + super(columnModel); + } + + @Override + public void sort(SortKey orderBy) { + SortableFlexiTableModelDelegate<LectureBlockIdentityStatistics> sorter + = new SortableFlexiTableModelDelegate<>(orderBy, this, null); + List<LectureBlockIdentityStatistics> views = sorter.sort(); + super.setObjects(views); + } + + @Override + public Object getValueAt(int row, int col) { + LectureBlockIdentityStatistics stats = getObject(row); + return getValueAt(stats, col); + } + + @Override + public Object getValueAt(LectureBlockIdentityStatistics row, int col) { + if(col >= 0 && col < StatsCols.values().length) { + switch(StatsCols.values()[col]) { + case id: return row.getIdentityKey(); + case entry: return row.getDisplayName(); + case plannedLectures: return positive(row.getTotalPersonalPlannedLectures()); + case attendedLectures: return positive(row.getTotalAttendedLectures()); + case absentLectures: return positive(row.getTotalAbsentLectures()); + case authorizedAbsenceLectures: return positive(row.getTotalAuthorizedAbsentLectures()); + } + } + + int propPos = col - LecturesListController.USER_PROPS_OFFSET; + return row.getIdentityProp(propPos); + } + + private static final long positive(long pos) { + return pos < 0 ? 0 : pos; + } + + @Override + public DefaultFlexiTableDataModel<LectureBlockIdentityStatistics> createCopyWithEmptyList() { + return new LecturesListDataModel(getTableColumnModel()); + } + + public enum StatsCols implements FlexiSortableColumnDef { + id("table.header.id"), + entry("table.header.entry"), + plannedLectures("table.header.planned.lectures"), + attendedLectures("table.header.attended.lectures"), + absentLectures("table.header.absent.lectures"), + authorizedAbsenceLectures("table.header.authorized.absence") + ; + + private final String i18nKey; + + private StatsCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LecturesSearchController.java b/src/main/java/org/olat/modules/lecture/ui/LecturesSearchController.java new file mode 100644 index 00000000000..2b98e696d5d --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/LecturesSearchController.java @@ -0,0 +1,106 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.Roles; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; +import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LecturesSearchController extends BasicController implements Activateable2 { + + private final TooledStackedPanel stackPanel; + + private LecturesListController listCtrl; + private LecturesSearchFormController searchForm; + + private final boolean admin; + + @Autowired + private LectureService lectureService; + + public LecturesSearchController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel) { + super(ureq, wControl); + this.stackPanel = stackPanel; + Roles roles = ureq.getUserSession().getRoles(); + admin = (roles.isUserManager() || roles.isOLATAdmin()); + + searchForm = new LecturesSearchFormController(ureq, getWindowControl()); + listenTo(searchForm); + putInitialPanel(searchForm.getInitialComponent()); + } + + @Override + protected void doDispose() { + // + } + + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(searchForm == source) { + if(event == Event.DONE_EVENT) { + doSearch(ureq); + } + } + super.event(ureq, source, event); + } + + private void doSearch(UserRequest ureq) { + LectureStatisticsSearchParameters params = searchForm.getSearchParameters(); + List<UserPropertyHandler> userPropertyHandlers = searchForm.getUserPropertyHandlers(); + List<LectureBlockIdentityStatistics> statistics = lectureService + .getLecturesStatistics(params, userPropertyHandlers, getIdentity(), admin); + listCtrl = new LecturesListController(ureq, getWindowControl(), statistics, + userPropertyHandlers, LecturesSearchFormController.PROPS_IDENTIFIER); + listenTo(listCtrl); + stackPanel.popUpToRootController(ureq); + stackPanel.pushController(translate("results"), listCtrl); + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LecturesSearchFormController.java b/src/main/java/org/olat/modules/lecture/ui/LecturesSearchFormController.java new file mode 100644 index 00000000000..5a205406cd7 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/LecturesSearchFormController.java @@ -0,0 +1,279 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.olat.basesecurity.BaseSecurityModule; +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.DateChooser; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; +import org.olat.repository.manager.RepositoryEntryLifecycleDAO; +import org.olat.repository.model.RepositoryEntryLifecycle; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.EmailProperty; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * + * Initial date: 16 juin 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LecturesSearchFormController extends FormBasicController { + + protected static final String PROPS_IDENTIFIER = LecturesSearchFormController.class.getName(); + private static final String[] dateKeys = new String[]{ "none", "private", "public"}; + + private TextElement login; + private TextElement bulkEl; + private FormLink searchButton; + private DateChooser startDateEl, endDateEl; + private FormLayoutContainer privateDatesCont; + private SingleSelection dateTypesEl, publicDatesEl; + + private final boolean adminProps; + private List<UserPropertyHandler> userPropertyHandlers; + private final Map<String,FormItem> propFormItems = new HashMap<>(); + + @Autowired + private UserManager userManager; + @Autowired + private BaseSecurityModule securityModule; + @Autowired + private RepositoryEntryLifecycleDAO lifecycleDao; + + public LecturesSearchFormController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + adminProps = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); + + initForm(ureq); + updateDatesVisibility(); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + login = uifactory.addTextElement("login", "search.form.login", 128, "", formLayout); + login.setVisible(adminProps); + + userPropertyHandlers = userManager.getUserPropertyHandlersFor(PROPS_IDENTIFIER, adminProps); + + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + FormItem fi = userPropertyHandler.addFormItem(getLocale(), null, PROPS_IDENTIFIER, false, formLayout); + // DO NOT validate email field => see OLAT-3324, OO-155, OO-222 + if (userPropertyHandler instanceof EmailProperty && fi instanceof TextElement) { + TextElement textElement = (TextElement)fi; + textElement.setItemValidatorProvider(null); + } + + propFormItems.put(userPropertyHandler.getName(), fi); + } + } + + bulkEl = uifactory.addTextAreaElement("bulk", 4, 72, "", formLayout); + + String[] dateValues = new String[] { + translate("dates.none"), + translate("dates.private"), + translate("dates.public") + }; + dateTypesEl = uifactory.addRadiosVertical("dates", formLayout, dateKeys, dateValues); + dateTypesEl.select(dateKeys[0], true); + dateTypesEl.addActionListener(FormEvent.ONCHANGE); + + List<RepositoryEntryLifecycle> cycles = lifecycleDao.loadPublicLifecycle(); + List<RepositoryEntryLifecycle> filteredCycles = new ArrayList<>(); + for(RepositoryEntryLifecycle cycle:cycles) { + if(cycle.getValidTo() == null) { + filteredCycles.add(cycle); + } + } + + String[] publicKeys = new String[filteredCycles.size()]; + String[] publicValues = new String[filteredCycles.size()]; + int count = 0; + for(RepositoryEntryLifecycle cycle:filteredCycles) { + publicKeys[count] = cycle.getKey().toString(); + + StringBuilder sb = new StringBuilder(32); + boolean labelAvailable = StringHelper.containsNonWhitespace(cycle.getLabel()); + if(labelAvailable) { + sb.append(cycle.getLabel()); + } + if(StringHelper.containsNonWhitespace(cycle.getSoftKey())) { + if(labelAvailable) sb.append(" - "); + sb.append(cycle.getSoftKey()); + } + publicValues[count++] = sb.toString(); + } + publicDatesEl = uifactory.addDropdownSingleselect("public.dates", formLayout, publicKeys, publicValues, null); + + String privateDatePage = velocity_root + "/cycle_dates.html"; + privateDatesCont = FormLayoutContainer.createCustomFormLayout("private.date", getTranslator(), privateDatePage); + privateDatesCont.setRootForm(mainForm); + privateDatesCont.setLabel("private.dates", null); + formLayout.add("private.date", privateDatesCont); + + startDateEl = uifactory.addDateChooser("date.start", "date.start", null, privateDatesCont); + startDateEl.setElementCssClass("o_sel_repo_lifecycle_validfrom"); + endDateEl = uifactory.addDateChooser("date.end", "date.end", null, privateDatesCont); + endDateEl.setElementCssClass("o_sel_repo_lifecycle_validto"); + + FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonCont); + uifactory.addFormSubmitButton("search", buttonCont); + } + + private void updateDatesVisibility() { + if(dateTypesEl.isOneSelected()) { + String type = dateTypesEl.getSelectedKey(); + if("none".equals(type)) { + publicDatesEl.setVisible(false); + privateDatesCont.setVisible(false); + } else if("public".equals(type)) { + publicDatesEl.setVisible(true); + privateDatesCont.setVisible(false); + } else if("private".equals(type)) { + publicDatesEl.setVisible(false); + privateDatesCont.setVisible(true); + } + } + } + + @Override + protected void doDispose() { + // + } + + public List<UserPropertyHandler> getUserPropertyHandlers() { + return userPropertyHandlers; + } + + public LectureStatisticsSearchParameters getSearchParameters() { + LectureStatisticsSearchParameters params = new LectureStatisticsSearchParameters(); + + String type = dateTypesEl.getSelectedKey(); + if("none".equals(type)) { + params.setStartDate(null); + params.setEndDate(null); + params.setLifecycle(null); + } else if("public".equals(type)) { + params.setStartDate(null); + params.setEndDate(null); + if(publicDatesEl.isOneSelected() && StringHelper.isLong(publicDatesEl.getSelectedKey())) { + RepositoryEntryLifecycle lifecycle = lifecycleDao.loadById(new Long(publicDatesEl.getSelectedKey())); + params.setLifecycle(lifecycle); + } else { + params.setLifecycle(null); + } + } else if("private".equals(type)) { + params.setStartDate(startDateEl.getDate()); + params.setEndDate(endDateEl.getDate()); + params.setLifecycle(null); + } + + params.setLogin(getLogin()); + params.setBulkIdentifiers(getBulkIdentifiers()); + params.setUserProperties(getSearchProperties()); + return params; + } + + private String getLogin() { + return login.isVisible() && StringHelper.containsNonWhitespace(login.getValue()) + ? login.getValue() : null; + } + + private Map<String,String> getSearchProperties() { + Map<String, String> userPropertiesSearch = new HashMap<>(); + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + FormItem ui = propFormItems.get(userPropertyHandler.getName()); + String uiValue = userPropertyHandler.getStringValue(ui); + if(userPropertyHandler.getName().startsWith("genericCheckboxProperty")) { + if(!"false".equals(uiValue)) { + userPropertiesSearch.put(userPropertyHandler.getName(), uiValue); + } + } else if (StringHelper.containsNonWhitespace(uiValue) && !uiValue.equals("-")) { + userPropertiesSearch.put(userPropertyHandler.getName(), uiValue); + } + } + } + return userPropertiesSearch; + } + + private List<String> getBulkIdentifiers() { + String val = bulkEl.getValue(); + + List<String> identifiers = new ArrayList<String>(); + String[] lines = val.split("\r?\n"); + for (int i = 0; i < lines.length; i++) { + String username = lines[i].trim(); + if(username.length() > 0) { + identifiers.add(username); + } + } + return identifiers; + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == dateTypesEl) { + updateDatesVisibility(); + } else if (source == searchButton) { + if(validate()) { + fireEvent (ureq, Event.DONE_EVENT); + } + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + return validate() & super.validateFormLogic(ureq); + } + + private boolean validate() { + return true; + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/cycle_dates.html b/src/main/java/org/olat/modules/lecture/ui/_content/cycle_dates.html new file mode 100644 index 00000000000..9284e39ceb0 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/_content/cycle_dates.html @@ -0,0 +1,6 @@ +<div class="o_date form-inline"> + <span class="form-control-static">$r.translate("date.start")</span> + <div class="form-group o_sel_repo_lifecycle_validfrom">$r.render("date.start")</div> + <span class="form-control-static">$r.translate("date.end")</span> + <div class="form-group o_sel_repo_lifecycle_validto">$r.render("date.end")</div> +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/lectures_coaching.html b/src/main/java/org/olat/modules/lecture/ui/_content/lectures_coaching.html new file mode 100644 index 00000000000..bade9402acd --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/_content/lectures_coaching.html @@ -0,0 +1 @@ +$r.render("table") \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties index e570ad734a7..e78f902f115 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties @@ -13,6 +13,7 @@ appeal.title=Rekurs f\u00FCr\: "{0}" authorized.absence=Entschuldigt authorized.absence.reason=Begr\u00FCndung autoclosed=Automatisch geschlossen +bulk=Bulk Email cancelled=Abgesagt cancel.lecture.blocks=Lektionen absagen closed=Geschlossen @@ -28,6 +29,15 @@ confirm.delete.lectures=Wollen Sie wirklich diese Lektionenblock "{0}" l\u00F6sc confirm.delete.reason=Wollen Sie wirklich diese Begr\u00FCndung "{0}" l\u00F6schen? copy=Kopieren current.lecture=Aktueller Lektionenblock +dates=$org.olat.repository\:cif.dates +dates.none=$org.olat.repository\:cif.dates.none +dates.private=$org.olat.repository\:cif.dates.private +dates.public=$org.olat.repository\:cif.dates.public +date.start=$org.olat.repository\:cif.date.start +date.end=$org.olat.repository\:cif.date.end +dates.public=$org.olat.repository\:cif.dates.public +private.dates=$org.olat.repository\:cif.private.dates +public.dates=$org.olat.repository\:cif.public.dates delete.lectures.title=Lektionenblock l\u00F6schen delete.title=Begr\u00FCndung l\u00F6schen details=Details @@ -125,11 +135,13 @@ repo.lectures=Lektionen repo.lectures.block=Lektionenbl\u00F6cke repo.participants=Teilnehmer repo.settings=Konfiguration +results=Resultaten rollcall=Anwesenheitskontrolle rollcall.comment=Bemerkung rollcall.status=Status Lektionenblock save.next=Speichern und weiter save.temporary=Zwischen speichern +search.form.login=Benutzername start.wizard=Wizard starten sync.participants.calendar.enabled=Teilnehmer Kalender synchronizieren sync.teachers.calendar.enabled=Lehrer Kalender synchronizieren diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties index 608fefed713..f04474f47de 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties @@ -13,6 +13,7 @@ appeal.title=Appeal for\: "{0}" authorized.absence=Authorized authorized.absence.reason=Reason autoclosed=Automatically closed +bulk=Bulk Email cancelled=Cancelled cancel.lecture.blocks=Cancel lectures closed=Closed @@ -28,6 +29,15 @@ confirm.delete.lectures=Do you really want to delete these lectures "{0}"? confirm.delete.reason=Do you really want to delete this reason "{0}"? copy=Copy current.lecture=Current lectures +dates=$org.olat.repository\:cif.dates +dates.none=$org.olat.repository\:cif.dates.none +dates.private=$org.olat.repository\:cif.dates.private +dates.public=$org.olat.repository\:cif.dates.public +date.start=$org.olat.repository\:cif.date.start +date.end=$org.olat.repository\:cif.date.end +dates.public=$org.olat.repository\:cif.dates.public +private.dates=$org.olat.repository\:cif.private.dates +public.dates=$org.olat.repository\:cif.public.dates delete.lectures.title=Delete lectures delete.title=Delete reason details=Details @@ -124,11 +134,13 @@ repo.lectures=Lectures repo.lectures.block=Lectures blocs repo.participants=Participants repo.settings=Configuration +results=Results rollcall=Roll call rollcall.comment=Comment rollcall.status=Roll call status save.next=Save and next save.temporary=Quick save +search.form.login=Username start.wizard=Start wizard sync.participants.calendar.enabled=Synchronize participants calendars sync.teachers.calendar.enabled=Synchronize teachers calendars diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index b21b2155678..cdcaf70b801 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -1264,6 +1264,29 @@ </bean> </entry> + <entry key="org.olat.modules.lecture.ui.LecturesSearchFormController"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Properties used in the user search for lectures of the coaching tool." /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyEmail" /> + <ref bean="userPropertyInstitutionalName" /> + <ref bean="userPropertyInstitutionalUserIdentifier" /> + <ref bean="userPropertyInstitutionalEmail" /> + <ref bean="userPropertyOrgUnit" /> + </list> + </property> + <property name="mandatoryProperties"> + <set> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + </set> + </property> + </bean> + </entry> + <entry key="org.olat.modules.portfolio.ui.PortfolioHomeController"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> <property name="description" value="Properties used in the portfolio v2.0." /> -- GitLab