diff --git a/assignment/impl/pom.xml b/assignment/impl/pom.xml index c38a094c8364..7b2a2e3639b0 100644 --- a/assignment/impl/pom.xml +++ b/assignment/impl/pom.xml @@ -84,6 +84,10 @@ org.sakaiproject.tags tags-api + + org.sakaiproject.portal + sakai-portal-api + commons-codec commons-codec @@ -138,6 +142,10 @@ org.springframework spring-test + + org.springframework + spring-web + com.fasterxml.jackson.core jackson-core diff --git a/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java b/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java index 75ddd266015d..d9076138950a 100644 --- a/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java +++ b/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java @@ -36,6 +36,7 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -46,7 +47,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -105,7 +105,6 @@ import org.sakaiproject.authz.api.Member; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; -import org.sakaiproject.lti.util.SakaiLTIUtil; import org.sakaiproject.calendar.api.Calendar; import org.sakaiproject.calendar.api.CalendarEvent; import org.sakaiproject.calendar.api.CalendarService; @@ -148,7 +147,9 @@ import org.sakaiproject.grading.api.GradebookInformation; import org.sakaiproject.grading.api.GradingService; import org.sakaiproject.lti.api.LTIService; -import org.sakaiproject.util.foorm.Foorm; +import org.sakaiproject.portal.api.PortalService; +import org.sakaiproject.portal.api.PortalSubPageData; +import org.sakaiproject.portal.api.PortalSubPageNavProvider; import org.sakaiproject.messaging.api.Message; import org.sakaiproject.messaging.api.MessageMedium; import org.sakaiproject.messaging.api.UserMessagingService; @@ -157,6 +158,7 @@ import org.sakaiproject.search.api.SearchService; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; +import org.sakaiproject.site.api.SitePage; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.taggable.api.TaggingManager; @@ -190,6 +192,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -207,7 +210,7 @@ */ @Slf4j @Transactional(readOnly = true) -public class AssignmentServiceImpl implements AssignmentService, EntityTransferrer, ContentExistsAware, ApplicationContextAware { +public class AssignmentServiceImpl implements ApplicationContextAware, AssignmentService, ContentExistsAware, EntityTransferrer, PortalSubPageNavProvider { @Setter private AnnouncementService announcementService; @Setter private ApplicationContext applicationContext; @@ -230,6 +233,7 @@ public class AssignmentServiceImpl implements AssignmentService, EntityTransferr @Setter private GradingService gradingService; @Setter private LearningResourceStoreService learningResourceStoreService; @Setter private LinkMigrationHelper linkMigrationHelper; + @Setter private PortalService portalService; @Setter private TransactionTemplate transactionTemplate; @Setter private ResourceLoader resourceLoader; @Setter private RubricsService rubricsService; @@ -266,6 +270,7 @@ public void init() { exposeContentReviewErrorsToUI = serverConfigurationService.getBoolean("contentreview.expose.errors.to.ui", true); createGroupsOnImport = serverConfigurationService.getBoolean("assignment.create.groups.on.import", true); + portalService.registerSubPageNavProvider(this); // register as an entity producer entityManager.registerEntityProducer(this, REFERENCE_ROOT); @@ -294,6 +299,11 @@ public boolean isTimeSheetEnabled(String siteId) { return timeSheetService.isTimeSheetEnabled(siteId); } + @Override + public String getSubPageProviderName() { + return getToolId(); + } + @Override public String getLabel() { return "assignment"; @@ -417,6 +427,80 @@ public String archive(String siteId, Document doc, Stack stack, String return results.toString(); } + @Override + public void getSubPageData(PortalSubPageData data, Collection pageIds) { + String siteId = data.getSiteId(); + for (String pageId : pageIds) { + String toolId = siteService.getOptionalSite(siteId) + .map(site -> site.getPage(pageId)) + .filter(Objects::nonNull) + .map(SitePage::getTools) + .filter(tools -> !tools.isEmpty()) + .map(tools -> tools.get(0).getId()) + .orElse(""); + + if (toolId.isEmpty()) { + log.warn("could not fetch tool id on page [{}], in site [{}]", pageId, siteId); + continue; + } + + data.getPages().computeIfAbsent(toolId, key -> new ArrayList<>()); + + Collection assignments = getAssignmentsForContext(siteId); + for (Assignment assignment : assignments) { + String assignmentReference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); + PortalSubPageData.PageData pageData = new PortalSubPageData.PageData(); + pageData.setSiteId(siteId); + pageData.setToolId(toolId); + pageData.setItemId(assignment.getId()); + pageData.setName(assignment.getTitle()); + pageData.setDescription(assignment.getInstructions()); + pageData.setDisabled(assignment.getDeleted() || assignment.getDraft()); + pageData.setHidden(assignment.getDeleted() || assignment.getDraft()); + + DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(rb.getLocale()); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(assignment.getOpenDate(), userTimeService.getLocalTimeZone().toZoneId()); + pageData.setReleaseDate(dtf.format(zonedDateTime)); + + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(serverConfigurationService.getPortalUrl()); + uriBuilder.pathSegment("site", pageData.getSiteId(), "tool", toolId); + uriBuilder.queryParam("assignmentId", assignmentReference); + uriBuilder.queryParam("panel", "Main"); + uriBuilder.queryParam("sakai_action", "doView_assignment"); + pageData.setUrl(uriBuilder.build().toUriString()); + + data.getPages().get(toolId).add(pageData); + + } + PortalSubPageData.PageProps pageProps = new PortalSubPageData.PageProps(); + pageProps.setToolId(toolId); + pageProps.setSiteId(siteId); + pageProps.setName("Assignments"); + pageProps.setIcon("si-sakai-assignment-grades"); + data.getTopLevelPageProps().add(pageProps); + } + + // TODO this needs translating + PortalSubPageData.I18n i18n = data.getI18n(); + i18n.setExpand("Expand to show subpages"); + i18n.setCollapse("Collapse to hide subpages"); + i18n.setOpenTopLevelPage("Click to open top-level page"); + i18n.setHidden("[Hidden]"); + i18n.setHiddenWithReleaseDate("[Not released until {releaseDate}]"); + i18n.setMainLinkName("List of Assignments"); + i18n.setPrerequisite("[Has prerequisites]"); + i18n.setPrerequisiteAndDisabled("[You must complete all prerequisites before viewing this item]"); +// i18n.setExpand(rb.getString("")); +// i18n.setCollapse(rb.getString("")); +// i18n.setOpenTopLevelPage(rb.getString("")); +// i18n.setHidden(rb.getString("")); +// i18n.setHiddenWithReleaseDate(rb.getString("")); +// i18n.setMainLinkName(rb.getString("")); +// i18n.setPrerequisite(rb.getString("")); +// i18n.setPrerequisiteAndDisabled(rb.getString("")); + + } + private void addSupplementaryItemAttachments(Document doc, Element item, List itemAttachments, List archiveAttachments) { if (itemAttachments.isEmpty()) { @@ -3043,7 +3127,7 @@ public String getGradeDisplay(String grade, Assignment.GradeType typeOfGrade, In switch (typeOfGrade) { case SCORE_GRADE_TYPE: if (!returnGrade.isEmpty() && !"0".equals(returnGrade)) { - int dec = new Double(Math.log10(scaleFactor)).intValue(); + int dec = Double.valueOf(Math.log10(scaleFactor)).intValue(); String decSeparator = formattedText.getDecimalSeparator(); String decimalGradePoint = returnGrade; try { @@ -4699,7 +4783,7 @@ private LRS_Result getLRS_Result(Assignment a, AssignmentSubmission s, boolean c String gradeDisplay = StringUtils.replace(getGradeDisplay(s.getGrade(), a.getTypeOfGrade(), a.getScaleFactor()), decSeparator, "."); if (Assignment.GradeType.SCORE_GRADE_TYPE == a.getTypeOfGrade() && NumberUtils.isCreatable(gradeDisplay)) { // Points String maxGradePointDisplay = StringUtils.replace(getMaxPointGradeDisplay(a.getScaleFactor(), a.getMaxGradePoint()), decSeparator, "."); - result = new LRS_Result(new Float(gradeDisplay), 0.0f, new Float(maxGradePointDisplay), null); + result = new LRS_Result(Float.parseFloat(gradeDisplay), 0.0f, Float.parseFloat(maxGradePointDisplay), null); result.setCompletion(completed); } else { result = new LRS_Result(completed); diff --git a/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java b/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java index 81bcb98fb781..1a3cc5063025 100644 --- a/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java +++ b/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java @@ -53,6 +53,7 @@ import org.sakaiproject.hibernate.AssignableUUIDGenerator; import org.sakaiproject.lti.api.LTIService; import org.sakaiproject.messaging.api.UserMessagingService; +import org.sakaiproject.portal.api.PortalService; import org.sakaiproject.rubrics.api.RubricsService; import org.sakaiproject.search.api.SearchIndexBuilder; import org.sakaiproject.search.api.SearchService; @@ -272,6 +273,11 @@ public LinkMigrationHelper linkMigrationHelper() { return mock(LinkMigrationHelper.class); } + @Bean(name = "org.sakaiproject.portal.api.PortalService") + public PortalService portalService() { + return mock(PortalService.class); + } + @Bean(name = "org.sakaiproject.time.api.UserTimeService") public UserTimeService userTimeService() { return mock(UserTimeService.class); diff --git a/assignment/impl/src/webapp/WEB-INF/components.xml b/assignment/impl/src/webapp/WEB-INF/components.xml index a334f4a01831..b38531f5c7f9 100644 --- a/assignment/impl/src/webapp/WEB-INF/components.xml +++ b/assignment/impl/src/webapp/WEB-INF/components.xml @@ -37,6 +37,7 @@ + diff --git a/lessonbuilder/api/pom.xml b/lessonbuilder/api/pom.xml index b9976f059b81..0f204a1c8264 100644 --- a/lessonbuilder/api/pom.xml +++ b/lessonbuilder/api/pom.xml @@ -46,6 +46,10 @@ org.hibernate hibernate-core + + org.sakaiproject.portal + sakai-portal-api + diff --git a/lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDao.java b/lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDao.java index 1647406a8712..2b1c5194e2d8 100644 --- a/lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDao.java +++ b/lessonbuilder/api/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDao.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.Collection; -import java.util.Optional; import org.sakaiproject.lessonbuildertool.SimplePage; import org.sakaiproject.lessonbuildertool.SimplePageComment; @@ -45,6 +44,7 @@ import org.sakaiproject.lessonbuildertool.SimpleChecklistItem; import org.sakaiproject.lessonbuildertool.ChecklistItemStatus; +import org.sakaiproject.portal.api.PortalSubPageData; import org.sakaiproject.site.api.ToolConfiguration; import org.springframework.orm.hibernate5.HibernateTemplate; @@ -334,7 +334,7 @@ public class PageData { public boolean doesPageFolderExist(final String siteId, final String folder); - public String getLessonSubPageJSON(String userId, String siteId, Collection pageIds); + public void getLessonSubPageData(PortalSubPageData data, Collection pageIds); public List getTopLevelPages(String siteId); diff --git a/lessonbuilder/components/pom.xml b/lessonbuilder/components/pom.xml index d93fa042ed55..c94466144e8f 100644 --- a/lessonbuilder/components/pom.xml +++ b/lessonbuilder/components/pom.xml @@ -72,6 +72,10 @@ org.springframework spring-orm + + org.springframework + spring-web + com.googlecode.json-simple json-simple diff --git a/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDaoImpl.java b/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDaoImpl.java index 6fd23a452fa5..f9fa93051b9f 100644 --- a/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDaoImpl.java +++ b/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/model/SimplePageToolDaoImpl.java @@ -34,6 +34,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -60,6 +61,7 @@ import org.json.simple.JSONValue; import org.sakaiproject.portal.api.PortalService; +import org.sakaiproject.portal.api.PortalSubPageData; import org.sakaiproject.portal.api.PortalSubPageNavProvider; import org.sakaiproject.time.api.UserTimeService; import org.springframework.dao.DataAccessException; @@ -1905,14 +1907,24 @@ public List findAllChecklistsInSite(String siteId) { @Override - public String getLessonSubPageJSON(final String userId, final String siteId, final Collection pageIds) { + public void getLessonSubPageData(PortalSubPageData data, Collection pageIds) { + String siteId = data.getSiteId(); + String userId = data.getUserId(); if (!pageIds.isEmpty()) { - String ref = siteService.siteReference(siteId); - boolean canSeeAll = securityService.unlock(userId, SimplePage.PERMISSION_LESSONBUILDER_UPDATE, ref) || securityService.unlock(userId, SimplePage.PERMISSION_LESSONBUILDER_SEE_ALL, ref); + String siteRef = siteService.siteReference(siteId); + boolean isInstructor = securityService.unlock(userId, SimplePage.PERMISSION_LESSONBUILDER_UPDATE, siteRef) + || securityService.unlock(userId, SimplePage.PERMISSION_LESSONBUILDER_SEE_ALL, siteRef); try { - final List groupIds = siteService.getSite(siteId).getGroupsWithMember(userId).stream().map(Group::getId).collect(Collectors.toList()); - final LessonsSubNavBuilder lessonsSubNavBuilder = new LessonsSubNavBuilder(userTimeService, siteId, canSeeAll, groupIds); + Collection groups = siteService.getOptionalSite(siteId) + .map(s -> s.getGroupsWithMember(userId)) + .orElse(Collections.emptyList()); + + List groupIds = groups.stream() + .map(Group::getId) + .collect(Collectors.toList()); + + LessonsSubNavBuilder lessonsSubNavBuilder = new LessonsSubNavBuilder(serverConfigurationService, userTimeService, data, isInstructor, groupIds); List lp = new ArrayList<>(); Map m = new HashMap<>(); @@ -1924,40 +1936,46 @@ public String getLessonSubPageJSON(final String userId, final String siteId, fin } lp.add(p); - try { - String sakaiToolId = siteService.getSite(siteId).getPage(p.getToolId()).getTools().get(0).getId(); - m.put(p, sakaiToolId); - findSubPageItemsByPageId(p.getPageId()).forEach(spi -> lessonsSubNavBuilder.processResult( - sakaiToolId, - p, - spi, - findPage(Long.parseLong(spi.getSakaiId())), - getLogEntry(userId, spi.getId(), -1L)) - ); - } catch (Exception e) { - // This condition commonly occurs when executing Site Info > Import from Site, such that - // the top-level pages for a target site do not yet exist or have just been created. The - // problem resolves upon the next browser refresh. - log.warn("page id [{}] in site [{}] not found, {}", pageId, siteId, e); + String toolId = siteService.getOptionalSite(siteId) + .map(site -> site.getPage(pageId)) + .filter(Objects::nonNull) + .map(SitePage::getTools) + .filter(tools -> !tools.isEmpty()) + .map(tools -> tools.get(0).getId()) + .orElse(""); + + if (toolId.isEmpty()) { + log.warn("could not fetch tool id on page [{}], in site [{}]", pageId, siteId); + continue; } + + m.put(p, toolId); + findSubPageItemsByPageId(p.getPageId()) + .forEach(spi -> lessonsSubNavBuilder.processResult( + pageId, + toolId, + p, + spi, + findPage(Long.parseLong(spi.getSakaiId())), + getLogEntry(userId, spi.getId(), -1L)) + ); } for (SimplePage p : lp) { - findTopLevelPageItem(p.getPageId()).ifPresent(spi -> { - SimplePageLogEntry le = getLogEntry(userId, spi.getId(), -1L); - lessonsSubNavBuilder.processTopLevelPageProperties(m.get(p), p, spi, le); - }); + findTopLevelPageItem(p.getPageId()) + .ifPresent(spi -> { + SimplePageLogEntry le = getLogEntry(userId, spi.getId(), -1L); + lessonsSubNavBuilder.processTopLevelPageProperties(m.get(p), p, spi, le); + }); } - - return lessonsSubNavBuilder.toJSON(); - } catch (Exception impossible) { - log.warn("Could not retrieve groups for site [{}]", impossible, impossible); + lessonsSubNavBuilder.toSubPageData(pageIds); + } catch (Exception e) { + log.warn("Could not retrieve groups for site [{}]", e.toString()); } } - return null; } - // returns top level pages; null if none + @Override public List getTopLevelPages(final String siteId) { // set of all top level pages, actually the items pointing to them try { @@ -2041,12 +2059,12 @@ public void deleteCommentsForLessonsItem(SimplePageItem item) { } @Override - public String getName() { + public String getSubPageProviderName() { return "sakai.lessonbuildertool"; } @Override - public String getData(String siteId, String userId, Collection pageIds) { - return getLessonSubPageJSON(userId, siteId, pageIds); + public void getSubPageData(PortalSubPageData data, Collection pageIds) { + getLessonSubPageData(data, pageIds); } } diff --git a/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/util/LessonsSubNavBuilder.java b/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/util/LessonsSubNavBuilder.java index ccd40a39a3a7..9f31cf50c478 100644 --- a/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/util/LessonsSubNavBuilder.java +++ b/lessonbuilder/components/src/java/org/sakaiproject/lessonbuildertool/util/LessonsSubNavBuilder.java @@ -27,7 +27,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.json.simple.JSONObject; +import org.sakaiproject.component.api.ServerConfigurationService; +import org.sakaiproject.portal.api.PortalSubPageData; import org.sakaiproject.time.api.UserTimeService; import org.sakaiproject.util.ResourceLoader; @@ -36,8 +37,8 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Arrays; @@ -46,6 +47,7 @@ import org.sakaiproject.lessonbuildertool.SimplePage; import org.sakaiproject.lessonbuildertool.SimplePageItem; import org.sakaiproject.lessonbuildertool.SimplePageLogEntry; +import org.springframework.web.util.UriComponentsBuilder; @Slf4j public class LessonsSubNavBuilder { @@ -53,66 +55,64 @@ public class LessonsSubNavBuilder { private static final ResourceLoader rb = new ResourceLoader("subnav"); private final UserTimeService userTimeService; + private final ServerConfigurationService serverConfigurationService; - private final List groups; private final boolean isInstructor; - private final String siteId; - private final Map>> subnavData; - private final List> topLevelPageProps; - - public LessonsSubNavBuilder(UserTimeService userTimeService, String siteId, boolean isInstructor, List groups) { + private final List groups; + private final PortalSubPageData subPageData; + + public LessonsSubNavBuilder(ServerConfigurationService serverConfigurationService, + UserTimeService userTimeService, + PortalSubPageData data, + boolean isInstructor, + List groups) { + this.serverConfigurationService = serverConfigurationService; this.userTimeService = userTimeService; - this.siteId = siteId; - this.isInstructor = isInstructor; this.groups = groups; - this.subnavData = new HashMap<>(); - this.topLevelPageProps = new ArrayList<>(); + this.subPageData = data; + this.isInstructor = isInstructor; } - public String toJSON() { - applyPrerequisites(); - - final Map objectToSerialize = new HashMap<>(); - objectToSerialize.put("pages", this.subnavData); - objectToSerialize.put("topLevelPageProps", this.topLevelPageProps); - objectToSerialize.put("i18n", getI18n()); - objectToSerialize.put("siteId", this.siteId); - objectToSerialize.put("isInstructor", this.isInstructor); + public void toSubPageData(Collection pageIds) { + applyPrerequisites(pageIds); + setI18n(); + } - return JSONObject.toJSONString(objectToSerialize); + private void buildSubpageUrl(PortalSubPageData.PageData subpage) { + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(serverConfigurationService.getPortalUrl()); + uriBuilder.pathSegment("site", subpage.getSiteId(), "tool", subpage.getToolId(), "ShowPage"); + uriBuilder.queryParam("sendingPage", subpage.getSakaiPageId()); + uriBuilder.queryParam("itemId", subpage.getItemId()); + uriBuilder.queryParam("path", "clear_and_push"); + uriBuilder.queryParam("title", subpage.getName()); + uriBuilder.queryParam("newTopLevel", "false"); + subpage.setUrl(uriBuilder.build().toUriString()); } + public void processResult(String pageId, String toolId, SimplePage parentPage, SimplePageItem spi, SimplePage page, SimplePageLogEntry le) { + if (isHidden(page)) return; - public static List collectPageIds(final List> pages) { - return pages.stream() - .filter(p -> !p.containsKey("wellKnownToolId") || "sakai.lessonbuildertool".equals(p.get("wellKnownToolId"))) - .map(p -> String.valueOf(p.get("pageId"))) - .collect(Collectors.toList()); - } + List subPages = subPageData + .getPages() + .computeIfAbsent(toolId, k -> new ArrayList<>()); + PortalSubPageData.PageData subPageItem = new PortalSubPageData.PageData(); - public void processResult(final String sakaiToolId, SimplePage parentPage, SimplePageItem spi, SimplePage page, SimplePageLogEntry le) { - if (isHidden(page)) return; + subPageItem.setPageId(pageId); + subPageItem.setToolId(toolId); + subPageItem.setSiteId(page.getSiteId()); + subPageItem.setSakaiPageId(parentPage.getToolId()); + subPageItem.setItemId(Long.toString(spi.getId())); + subPageItem.setSendingPage(spi.getSakaiId()); + subPageItem.setName(spi.getName()); + subPageItem.setDescription(spi.getDescription()); + subPageItem.setHidden(page.isHidden()); + subPageItem.setRequired(spi.isRequired()); + subPageItem.setCompleted(le != null && le.isComplete()); + subPageItem.setPrerequisite(spi.isPrerequisite()); + buildSubpageUrl(subPageItem); - if (!this.subnavData.containsKey(sakaiToolId)) { - this.subnavData.put(sakaiToolId, new ArrayList<>()); - } - - final Map subnavItem = new HashMap<>(); - - subnavItem.put("toolId", sakaiToolId); - subnavItem.put("siteId", page.getSiteId()); - subnavItem.put("sakaiPageId", parentPage.getToolId()); - subnavItem.put("itemId", Long.toString(spi.getId())); - subnavItem.put("sendingPage", spi.getSakaiId()); - subnavItem.put("name", spi.getName()); - subnavItem.put("description", spi.getDescription()); - subnavItem.put("hidden", String.valueOf(page.isHidden())); - subnavItem.put("required", String.valueOf(spi.isRequired())); - subnavItem.put("completed", String.valueOf(le != null && le.isComplete())); - subnavItem.put("prerequisite", String.valueOf(spi.isPrerequisite())); - - processDateReleased(page, subnavItem); + processDateReleased(page, subPageItem); boolean contains = true; String group = spi.getGroups(); @@ -120,88 +120,89 @@ public void processResult(final String sakaiToolId, SimplePage parentPage, Simpl contains = Arrays.stream(group.split(",")).anyMatch(groups::contains); // nothing needed for if the user is in the group it will display as normal // if the user is not in the groups, subpage is marked hidden above - if (!contains) subnavItem.put("hidden", "true"); + if (!contains) subPageItem.setHidden(true); } // only send the subpage if user is in the group - if (contains) this.subnavData.get(sakaiToolId).add(subnavItem); + if (contains) subPages.add(subPageItem); } - public void processTopLevelPageProperties(final String sakaiToolId, SimplePage page, SimplePageItem spi, SimplePageLogEntry le) { + public void processTopLevelPageProperties(String toolId, SimplePage page, SimplePageItem spi, SimplePageLogEntry le) { if (isHidden(page)) return; - final Map pageProps = new HashMap<>(); + PortalSubPageData.PageProps pageProps = new PortalSubPageData.PageProps(); - pageProps.put("toolId", sakaiToolId); - pageProps.put("siteId", page.getSiteId()); - pageProps.put("name", page.getTitle()); - pageProps.put("hidden", String.valueOf(page.isHidden())); - pageProps.put("required", String.valueOf(spi.isRequired())); - pageProps.put("completed", String.valueOf(le != null && le.isComplete())); - pageProps.put("prerequisite", String.valueOf(spi.isPrerequisite())); + pageProps.setToolId(toolId); + pageProps.setSiteId(page.getSiteId()); + pageProps.setName(page.getTitle()); + pageProps.setIcon("si-sakai-lessonbuildertool"); + pageProps.setHidden(page.isHidden()); + pageProps.setRequired(spi.isRequired()); + pageProps.setCompleted(le != null && le.isComplete()); + pageProps.setPrerequisite(spi.isPrerequisite()); processDateReleased(page, pageProps); - this.topLevelPageProps.add(pageProps); - + subPageData.getTopLevelPageProps().add(pageProps); } - - private void processDateReleased(SimplePage page, Map pageProps) { + private void processDateReleased(SimplePage page, PortalSubPageData.PageProps pageProps) { if (page.getReleaseDate() != null) { Date releaseDate = page.getReleaseDate(); if (releaseDate.getTime() > System.currentTimeMillis()) { - pageProps.put("disabled", "true"); + pageProps.setDisabled(true); DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(rb.getLocale()); ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(releaseDate.toInstant(), userTimeService.getLocalTimeZone().toZoneId()); - pageProps.put("releaseDate", dtf.format(zonedDateTime)); + pageProps.setReleaseDate(dtf.format(zonedDateTime)); } } } - private boolean isHidden(final SimplePage p) { - if (this.isInstructor) return false; + if (isInstructor) return false; return p.isHidden(); } - private Map getI18n() { - final Map translations = new HashMap<>(); - - translations.put("expand", rb.getString("lessons_subnav.expand")); - translations.put("collapse", rb.getString("lessons_subnav.collapse")); - translations.put("open_top_level_page", rb.getString("lessons_subnav.open_top_level_page")); - translations.put("hidden", rb.getString("lessons_subnav.hidden")); - translations.put("hidden_with_release_date", rb.getString("lessons_subnav.hidden_with_release_date")); - translations.put("main_link_name", rb.getString("lessons_subnav.main_link_name")); - translations.put("prerequisite", rb.getString("lessons_subnav.prerequisite")); - translations.put("prerequisite_and_disabled", rb.getString("lessons_subnav.prerequisite_and_disabled")); - - return translations; + // TODO does this need to be different for every tool? + private void setI18n() { + PortalSubPageData.I18n i18n = subPageData.getI18n(); + i18n.setExpand(rb.getString("lessons_subnav.expand")); + i18n.setCollapse(rb.getString("lessons_subnav.collapse")); + i18n.setOpenTopLevelPage(rb.getString("lessons_subnav.open_top_level_page")); + i18n.setHidden(rb.getString("lessons_subnav.hidden")); + i18n.setHiddenWithReleaseDate(rb.getString("lessons_subnav.hidden_with_release_date")); + i18n.setMainLinkName(rb.getString("lessons_subnav.main_link_name")); + i18n.setPrerequisite(rb.getString("lessons_subnav.prerequisite")); + i18n.setPrerequisiteAndDisabled(rb.getString("lessons_subnav.prerequisite_and_disabled")); } - private void applyPrerequisites() { - for (final String pageId : this.subnavData.keySet()) { - applyPrerequisitesToPageList(this.subnavData.get(pageId)); - } - - applyPrerequisitesToPageList(this.topLevelPageProps); + private void applyPrerequisites(Collection pageIds) { + subPageData.getPages().forEach((toolId, dataList) -> { + if (dataList.stream().anyMatch(page -> pageIds.contains(page.getPageId()))) { + List pageProps = new ArrayList<>(); + subPageData.getTopLevelPageProps().stream() + .filter(props -> props.getToolId().equals(toolId)) + .findAny().ifPresent(pageProps::add); + pageProps.addAll(dataList); + applyPrerequisitesToPageList(pageProps); + } + }); } - private void applyPrerequisitesToPageList(List> pages) { + public void applyPrerequisitesToPageList(List pageProps) { boolean prerequisiteApplies = false; - for (Map pageData : pages) { + for (PortalSubPageData.PageProps props : pageProps) { // if a sibling page with a smaller sequence is required // then disable the current page for students - if (pageData.get("prerequisite").equals("true") && prerequisiteApplies) { - pageData.put("disabledDueToPrerequisite", "true"); - pageData.put("disabled", String.valueOf(!this.isInstructor)); + if (props.isPrerequisite() && prerequisiteApplies) { + props.setDisabledDueToPrerequisite(true); + props.setDisabled(!isInstructor); } // only disable pages that have prerequisites below the current page // when the current page is required and the user is yet to complete it - if (pageData.get("required").equals("true")) { - if (pageData.get("completed").equals("false")) { + if (props.isRequired()) { + if (!props.isCompleted()) { prerequisiteApplies = true; } } diff --git a/lessonbuilder/tool/pom.xml b/lessonbuilder/tool/pom.xml index 2ed23127d5e6..285986e3a2ed 100644 --- a/lessonbuilder/tool/pom.xml +++ b/lessonbuilder/tool/pom.xml @@ -31,6 +31,10 @@ org.sakaiproject.lti lti-common + + org.sakaiproject.portal + sakai-portal-api + org.sakaiproject.portal sakai-portal-util diff --git a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/tool/producers/ShowPageProducer.java b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/tool/producers/ShowPageProducer.java index dd6e31e429d4..dc65a82143d5 100644 --- a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/tool/producers/ShowPageProducer.java +++ b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/tool/producers/ShowPageProducer.java @@ -1125,21 +1125,6 @@ public int compare(SimpleStudentPage o1, SimpleStudentPage o2) { createDialogs(tofill, currentPage, pageItem, cssLink); - // Add pageids to the page so the portal lessons subnav menu can update its state - List path = simplePageBean.getHierarchy(); - if (path.size() > 2) { - SimplePageBean.PathEntry topLevelSubPage = path.get(1); - UIOutput.make(tofill, "lessonsSubnavTopLevelPageId") - .decorate(new UIFreeAttributeDecorator("value", String.valueOf(topLevelSubPage.pageId))); - } else { - UIOutput.make(tofill, "lessonsSubnavPageId") - .decorate(new UIFreeAttributeDecorator("value", String.valueOf(simplePageBean.getCurrentPage().getPageId()))); - } - UIOutput.make(tofill, "lessonsSubnavToolId") - .decorate(new UIFreeAttributeDecorator("value", String.valueOf(placement.getId()))); - UIOutput.make(tofill, "lessonsSubnavItemId") - .decorate(new UIFreeAttributeDecorator("value", String.valueOf(pageItem.getId()))); - String currentPageId = String.valueOf(simplePageBean.getCurrentPage().getPageId()); UIOutput.make(tofill, "lessonsCurrentPageId") .decorate(new UIFreeAttributeDecorator("value", currentPageId)); diff --git a/lessonbuilder/tool/src/webapp/templates/ShowPage.html b/lessonbuilder/tool/src/webapp/templates/ShowPage.html index e7058da09957..6be6b5d6cbe0 100644 --- a/lessonbuilder/tool/src/webapp/templates/ShowPage.html +++ b/lessonbuilder/tool/src/webapp/templates/ShowPage.html @@ -34,11 +34,6 @@ - - - - - diff --git a/library/src/webapp/js/sub-page-navigation.js b/library/src/webapp/js/sub-page-navigation.js index d561bfc0bd9f..cbb3088cde74 100644 --- a/library/src/webapp/js/sub-page-navigation.js +++ b/library/src/webapp/js/sub-page-navigation.js @@ -2,19 +2,18 @@ class SubPageNavigation { constructor(data) { if (!data.hasOwnProperty('pages')) { - console.warn('No page data for SubPageNavigation'); + window.console.warn('No page data for SubPageNavigation'); return; } if (!data.hasOwnProperty('i18n')) { - console.warn('No i18n data for SubPageNavigation'); + window.console.warn('No i18n data for SubPageNavigation'); return; } this.data = data.pages; this.i18n = data.i18n; this.siteId = data.siteId; - this.isInstructor = data.isInstructor; this.topLevelPageProps = {}; data.topLevelPageProps.forEach((p) => this.topLevelPageProps[p.toolId] = p); @@ -36,11 +35,11 @@ class SubPageNavigation { const siteListItem = element.parentElement; const mainLink = element.href?.replace(/\/tool\//, "/tool-reset/"); - const collapseId = `page-${pageId}-lessons-subpages`; + const collapseId = `page-${pageId}-subpages`; const isExpanded = subpages[0].toolId === this.getCurrentPlacement(); const template = ` -
-
-
+