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
+
+ 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 = `
-
-
-
+
-
-
-
+
+
${this.i18n.main_link_name}
${(props.disabled === 'true') ? `` : ""}
${(props.hidden === 'true' && props.disabled !== 'true') ? `` : ""}
@@ -61,7 +60,7 @@ class SubPageNavigation {
${subpages.map((subpage) => `
-
-
+
${subpage.name}
${(props.disabled === 'true' || subpage.disabled === 'true') ? `` : ``}
@@ -91,34 +90,26 @@ class SubPageNavigation {
});
}
- buildSubpageUrlFor(subpage) {
- return `/portal/site/${subpage.siteId}`
- + `/tool/${subpage.toolId}`
- + `/ShowPage?sendingPage=${subpage.sendingPage}`
- + `&itemId=${subpage.itemId}`
- + "&path=clear_and_push"
- + `&title=${subpage.name}`
- + "&newTopLevel=false";
- }
-
getCurrentPlacement() {
const parts = (new URL(window.location.href)).pathname.split('/');
return parts.length >= 6 ? parts[5] : '';
}
getSubPageElement() {
- const subPageNavToolIdInput = document.getElementById('lessonsSubnavToolId');
- const subPageNavPageIdInput = document.getElementById('lessonsSubnavPageId');
- const subPageNavItemIdInput = document.getElementById('lessonsSubnavItemId');
- let subPageElement = null;
+ const selectedPage = document.querySelector('#toolMenu > li.nav-item:has(a.selected-page)');
- if (subPageNavToolIdInput && subPageNavPageIdInput && subPageNavItemIdInput) {
- subPageElement = document.querySelector(`#toolMenu a[href*="/tool/${subPageNavToolIdInput.value}/ShowPage?sendingPage=${subPageNavPageIdInput.value}&itemId=${subPageNavItemIdInput.value}&"]`);
+ let subPageElement = null;
+ if (selectedPage) {
+ // determine if the selected page is a sub page
+ subPageElement = selectedPage.querySelector('a.data-page-id');
}
- // If the current page is not a subpage, then highlight the main page.
- if (subPageElement && subPageNavToolIdInput) {
- subPageElement = document.querySelector(`#toolMenu a[href$="/tool-reset/${subPageNavToolIdInput.value}"]`);
+ // If the current page is not a subpage, then select the main page.
+ if (subPageElement) {
+ let parts = subPageElement.dataset.pageId.split('+');
+ if (parts.length === 2) {
+ subPageElement = document.querySelector(`#toolMenu a[href$="/tool-reset/${parts[0]}"]`);
+ }
}
return subPageElement;
diff --git a/portal/portal-api/api/pom.xml b/portal/portal-api/api/pom.xml
index 5f34fbc2558d..12db0687335b 100644
--- a/portal/portal-api/api/pom.xml
+++ b/portal/portal-api/api/pom.xml
@@ -75,6 +75,10 @@
org.springframework.data
spring-data-jpa
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
diff --git a/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalService.java b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalService.java
index 1cba0b24bc0d..dd451d4eb8d3 100644
--- a/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalService.java
+++ b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalService.java
@@ -426,5 +426,5 @@ public interface PortalService
*
* @return a JSON string
*/
- String getSubPageData(String name, String siteId, String userId, Collection pageIds);
+ String getSubPageJson(String siteId, String userId, Map> toolPageMap);
}
diff --git a/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageData.java b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageData.java
new file mode 100644
index 000000000000..7c9cbbd959cf
--- /dev/null
+++ b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageData.java
@@ -0,0 +1,73 @@
+package org.sakaiproject.portal.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class PortalSubPageData {
+ private String siteId;
+ private String userId;
+
+ private Map> pages;
+ private List topLevelPageProps;
+ private I18n i18n;
+
+ public PortalSubPageData(String siteId, String userId) {
+ this();
+ this.siteId = siteId;
+ this.userId = userId;
+ }
+
+ public PortalSubPageData() {
+ pages = new HashMap<>();
+ topLevelPageProps = new ArrayList<>();
+ i18n = new I18n();
+ }
+
+ @Data
+ public static class PageProps {
+ private String icon;
+ private String name;
+ private String releaseDate;
+ private String siteId;
+ private String toolId;
+ private String pageId;
+ private boolean completed;
+ private boolean disabled;
+ private boolean disabledDueToPrerequisite;
+ private boolean hidden;
+ private boolean prerequisite;
+ private boolean required;
+ }
+
+ @Data
+ public static class PageData extends PageProps {
+ private String description;
+ private String itemId;
+ private String sakaiPageId;
+ private String sendingPage;
+ private String url;
+ }
+
+ @Data
+ public static class I18n {
+ @JsonProperty("prerequisite_and_disabled")
+ private String prerequisiteAndDisabled;
+ private String expand;
+ @JsonProperty("open_top_level_page")
+ private String openTopLevelPage;
+ private String hidden;
+ @JsonProperty("hidden_with_release_date")
+ private String hiddenWithReleaseDate;
+ private String prerequisite;
+ @JsonProperty("main_link_name")
+ private String mainLinkName;
+ private String collapse;
+ }
+}
diff --git a/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageNavProvider.java b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageNavProvider.java
index 19ac2f316f2c..ad94055b388b 100644
--- a/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageNavProvider.java
+++ b/portal/portal-api/api/src/java/org/sakaiproject/portal/api/PortalSubPageNavProvider.java
@@ -22,7 +22,7 @@ public interface PortalSubPageNavProvider {
* Each provider should return a unique name, a good choice is the ENTITY_PREFIX
* @return a name that uniquely identifies this provider
*/
- String getName();
+ String getSubPageProviderName();
/**
* Each provider must provide its sub-page data in the following way
@@ -31,5 +31,5 @@ public interface PortalSubPageNavProvider {
* @param pageIds the pages
* @return sub page data for the given site, user, and pages
*/
- String getData(String siteId, String userId, Collection pageIds);
+ void getSubPageData(PortalSubPageData data, Collection pageIds);
}
diff --git a/portal/portal-impl/impl/src/java/org/sakaiproject/portal/charon/site/PortalSiteHelperImpl.java b/portal/portal-impl/impl/src/java/org/sakaiproject/portal/charon/site/PortalSiteHelperImpl.java
index e5724664f11d..c81f332aaede 100644
--- a/portal/portal-impl/impl/src/java/org/sakaiproject/portal/charon/site/PortalSiteHelperImpl.java
+++ b/portal/portal-impl/impl/src/java/org/sakaiproject/portal/charon/site/PortalSiteHelperImpl.java
@@ -293,9 +293,7 @@ private String getSubPages(String userId, String siteId, List pageList
toolPageMap.putIfAbsent(toolId, pageIds);
}
});
- return toolPageMap.keySet().stream()
- .map(toolId -> portalService.getSubPageData(toolId, siteId, userId, toolPageMap.get(toolId)))
- .collect(Collectors.joining());
+ return portalService.getSubPageJson(siteId, userId, toolPageMap);
}
private Map getSiteMap(Site site, String currentSiteId, String userId, boolean pinned, boolean hidden, boolean includePages) {
diff --git a/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeBodyScripts.vm b/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeBodyScripts.vm
index 9295103510cb..638582006528 100644
--- a/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeBodyScripts.vm
+++ b/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeBodyScripts.vm
@@ -61,7 +61,7 @@
iconClasses: ["bi-info-circle", "bi-info-circle-fill"],
});
- // Setup lessons subpage navigaion
+ // Setup subpage navigaion
let subPages = []; // exclude the current site
let currentSubPage = null;
@@ -71,9 +71,9 @@
#foreach ($site in $siteList)
#if ($site && $site.subPages)
#if ($site.isCurrent)
- currentSubPage = new SubPageNavigation(${site.subPages});
+ currentSubPage = new SubPageNavigation(${site.subPages});
#else
- subPages.push(${site.subPages});
+ subPages.push(${site.subPages});
#end
#end
#end
@@ -87,7 +87,7 @@
// a SubPageNavigation instance for each site
subPages.filter((v, i) => subPages.findIndex(val => val.siteId === v.siteId) === i)
- .forEach(data => new SubPageNavigation(data));
+ .forEach(data => new SubPageNavigation(data));
if (currentSubPage !== null && sakai && sakai.subnav) {
currentSubPage.setCurrentPage();
diff --git a/portal/portal-service-impl/impl/pom.xml b/portal/portal-service-impl/impl/pom.xml
index 8e23502f3e37..ca9752e50558 100644
--- a/portal/portal-service-impl/impl/pom.xml
+++ b/portal/portal-service-impl/impl/pom.xml
@@ -33,7 +33,6 @@
org.sakaiproject.kernel
sakai-kernel-test
- test
org.sakaiproject.lessonbuilder
@@ -70,11 +69,6 @@
org.mockito
mockito-core
- test
-
-
- org.projectlombok
- lombok
org.hibernate
@@ -96,6 +90,10 @@
org.hsqldb
hsqldb
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
diff --git a/portal/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/PortalServiceImpl.java b/portal/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/PortalServiceImpl.java
index c528feb5b4a9..ce8f88d833dd 100644
--- a/portal/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/PortalServiceImpl.java
+++ b/portal/portal-service-impl/impl/src/java/org/sakaiproject/portal/service/PortalServiceImpl.java
@@ -43,6 +43,8 @@
import javax.servlet.http.HttpServletRequest;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -74,6 +76,7 @@
import org.sakaiproject.portal.api.PortalHandler;
import org.sakaiproject.portal.api.PortalRenderEngine;
import org.sakaiproject.portal.api.PortalService;
+import org.sakaiproject.portal.api.PortalSubPageData;
import org.sakaiproject.portal.api.PortletApplicationDescriptor;
import org.sakaiproject.portal.api.PortletDescriptor;
import org.sakaiproject.portal.api.SiteNeighbourhoodService;
@@ -125,6 +128,7 @@ public class PortalServiceImpl implements PortalService, Observer
private Map portals = new ConcurrentHashMap<>();
private Map renderEngines = new ConcurrentHashMap<>();
private Collection portalSubPageNavProviders;
+ private ObjectMapper objectMapper;
public void init() {
try {
@@ -140,6 +144,7 @@ public void init() {
}
eventTrackingService.addLocalObserver(this);
portalSubPageNavProviders = new HashSet<>();
+ objectMapper = new ObjectMapper();
}
@Override
@@ -1123,9 +1128,9 @@ public void registerSubPageNavProvider(PortalSubPageNavProvider portalSubPageNav
if (portalSubPageNavProvider != null) {
Collection providers = new HashSet<>(portalSubPageNavProviders);
if (providers.contains(portalSubPageNavProvider)) {
- log.debug("Overriding existing SubPageNavProvider [{}]", portalSubPageNavProvider.getName());
+ log.debug("Overriding existing SubPageNavProvider [{}]", portalSubPageNavProvider.getSubPageProviderName());
} else {
- log.debug("Registering new SubPageNavProvider [{}]", portalSubPageNavProvider.getName());
+ log.debug("Registering new SubPageNavProvider [{}]", portalSubPageNavProvider.getSubPageProviderName());
}
providers.add(portalSubPageNavProvider);
portalSubPageNavProviders = providers;
@@ -1133,15 +1138,26 @@ public void registerSubPageNavProvider(PortalSubPageNavProvider portalSubPageNav
}
@Override
- public String getSubPageData(String name, String siteId, String userId, Collection pageIds) {
- for (PortalSubPageNavProvider portalSubPageNavProvider : portalSubPageNavProviders) {
- if (portalSubPageNavProvider.getName().equals(name)) {
- String data = portalSubPageNavProvider.getData(siteId, userId, pageIds);
- log.debug("Retrieved sub page nav data from provider [{}], data={}", name, data);
- return data;
+ public String getSubPageJson(String siteId, String userId, Map> toolPageMap) {
+
+ PortalSubPageData subPageData = new PortalSubPageData(siteId, userId);
+ for (Map.Entry> entry : toolPageMap.entrySet()) {
+ String toolId = entry.getKey();
+ for (PortalSubPageNavProvider portalSubPageNavProvider : portalSubPageNavProviders) {
+ if (portalSubPageNavProvider.getSubPageProviderName().equals(toolId)) {
+ portalSubPageNavProvider.getSubPageData(subPageData, entry.getValue());
+ log.debug("Processed sub page nav data for site [{}] and user [{}] from provider [{}]", siteId, userId, toolId);
+ }
}
}
- return StringUtils.EMPTY;
+ String json = "";
+ try {
+ json = objectMapper.writeValueAsString(subPageData);
+ } catch (JsonProcessingException jpe) {
+ log.warn("Could not serialize sub page data for site [{}], user[{}], {}", siteId, userId, jpe.toString());
+ }
+ log.debug("Sub page data serialized for site [{}] and user [{}] is {}", siteId, userId, json);
+ return json;
}
}
diff --git a/portal/portal-service-impl/impl/src/test/org/sakaiproject/portal/service/PortalSubPageTests.java b/portal/portal-service-impl/impl/src/test/org/sakaiproject/portal/service/PortalSubPageTests.java
new file mode 100644
index 000000000000..27d82dbce5d0
--- /dev/null
+++ b/portal/portal-service-impl/impl/src/test/org/sakaiproject/portal/service/PortalSubPageTests.java
@@ -0,0 +1,82 @@
+package org.sakaiproject.portal.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Assert;
+import org.junit.Test;
+import org.sakaiproject.portal.api.PortalSubPageData;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class PortalSubPageTests {
+
+ private static String loadFromFile(String fileName) {
+ Resource resource = new ClassPathResource(fileName);
+ try (InputStream inputStream = resource.getInputStream()) {
+ return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Can't read file: " + fileName, e);
+ }
+ }
+
+ // TODO more tests need to be added
+ @Test
+ public void testDeserializePortalSubPageData() throws Exception {
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ String json = loadFromFile("subpage.json");
+
+ // Deserialize from JSON
+ PortalSubPageData deserializedData = objectMapper.readValue(json, PortalSubPageData.class);
+
+ // Assertions
+ Assert.assertNotNull(deserializedData);
+ Assert.assertEquals("ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e", deserializedData.getSiteId());
+ Assert.assertEquals("6cf7782d-8eb8-4f37-a4ce-1f1b1374f7d2", deserializedData.getUserId());
+
+ List pages = deserializedData.getPages().get("783d0765-bd24-4253-8a63-ac8889cb25e0");
+ Assert.assertEquals(1, pages.size());
+
+ // PageData Assertions
+ PortalSubPageData.PageData page = pages.get(0);
+ Assert.assertEquals("783d0765-bd24-4253-8a63-ac8889cb25e0", page.getToolId());
+ Assert.assertEquals("11", page.getItemId());
+ Assert.assertFalse(page.isHidden());
+ Assert.assertEquals("Chapter One", page.getName());
+ Assert.assertFalse(page.isPrerequisite());
+ Assert.assertEquals("ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e", page.getSiteId());
+ Assert.assertEquals("", page.getDescription());
+ Assert.assertEquals("f6060e88-82e0-412f-ae3b-3a98a45c2619", page.getSakaiPageId());
+ Assert.assertTrue(page.isCompleted());
+ Assert.assertEquals("8", page.getSendingPage());
+ Assert.assertFalse(page.isRequired());
+
+ // TopLevelPage Assertions
+ List topLevelPages = deserializedData.getTopLevelPageProps();
+ Assert.assertEquals(1, topLevelPages.size());
+
+ PortalSubPageData.PageProps topLevelPage = topLevelPages.get(0);
+ Assert.assertEquals("783d0765-bd24-4253-8a63-ac8889cb25e0", topLevelPage.getToolId());
+ Assert.assertFalse(topLevelPage.isHidden());
+ Assert.assertEquals("Lessons", topLevelPage.getName());
+ Assert.assertFalse(topLevelPage.isPrerequisite());
+ Assert.assertEquals("ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e", topLevelPage.getSiteId());
+ Assert.assertTrue(topLevelPage.isCompleted());
+ Assert.assertFalse(topLevelPage.isRequired());
+
+ // i18n Assertions
+ PortalSubPageData.I18n i18n = deserializedData.getI18n();
+ Assert.assertEquals("[You must complete all prerequisites before viewing this item]", i18n.getPrerequisiteAndDisabled());
+ Assert.assertEquals("Expand to show subpages", i18n.getExpand());
+ Assert.assertEquals("Click to open top-level page", i18n.getOpenTopLevelPage());
+ Assert.assertEquals("[Hidden]", i18n.getHidden());
+ Assert.assertEquals("[Not released until {releaseDate}]", i18n.getHiddenWithReleaseDate());
+ Assert.assertEquals("[Has prerequisites]", i18n.getPrerequisite());
+ Assert.assertEquals("Main Page", i18n.getMainLinkName());
+ Assert.assertEquals("Collapse to hide subpages", i18n.getCollapse());
+ }
+}
diff --git a/portal/portal-service-impl/impl/src/test/resources/subpage.json b/portal/portal-service-impl/impl/src/test/resources/subpage.json
new file mode 100644
index 000000000000..46532d17d4e6
--- /dev/null
+++ b/portal/portal-service-impl/impl/src/test/resources/subpage.json
@@ -0,0 +1,42 @@
+{
+ "pages": {
+ "783d0765-bd24-4253-8a63-ac8889cb25e0": [
+ {
+ "toolId": "783d0765-bd24-4253-8a63-ac8889cb25e0",
+ "itemId": "11",
+ "hidden": "false",
+ "name": "Chapter One",
+ "prerequisite": "false",
+ "siteId": "ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e",
+ "description": "",
+ "sakaiPageId": "f6060e88-82e0-412f-ae3b-3a98a45c2619",
+ "completed": "true",
+ "sendingPage": "8",
+ "required": "false"
+ }
+ ]
+ },
+ "userId": "6cf7782d-8eb8-4f37-a4ce-1f1b1374f7d2",
+ "topLevelPageProps": [
+ {
+ "toolId": "783d0765-bd24-4253-8a63-ac8889cb25e0",
+ "hidden": "false",
+ "name": "Lessons",
+ "prerequisite": "false",
+ "siteId": "ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e",
+ "completed": "true",
+ "required": "false"
+ }
+ ],
+ "siteId": "ff4fb877-4f84-48d1-b1b1-6f1cda7fcb4e",
+ "i18n": {
+ "prerequisite_and_disabled": "[You must complete all prerequisites before viewing this item]",
+ "expand": "Expand to show subpages",
+ "open_top_level_page": "Click to open top-level page",
+ "hidden": "[Hidden]",
+ "hidden_with_release_date": "[Not released until {releaseDate}]",
+ "prerequisite": "[Has prerequisites]",
+ "main_link_name": "Main Page",
+ "collapse": "Collapse to hide subpages"
+ }
+}
\ No newline at end of file
diff --git a/profile2/api/pom.xml b/profile2/api/pom.xml
index a68cd01e2219..0c19c2ca3e66 100644
--- a/profile2/api/pom.xml
+++ b/profile2/api/pom.xml
@@ -29,12 +29,10 @@
org.sakaiproject.common
sakai-common-api
- provided
org.sakaiproject.entitybroker
entitybroker-api
- provided
org.sakaiproject.kernel
@@ -56,7 +54,6 @@
org.hibernate
hibernate-core
-
diff --git a/site-manage/datemanager/impl/pom.xml b/site-manage/datemanager/impl/pom.xml
index 54a053e0e3fb..63f434376da9 100644
--- a/site-manage/datemanager/impl/pom.xml
+++ b/site-manage/datemanager/impl/pom.xml
@@ -67,6 +67,10 @@
org.sakaiproject.scheduler
scheduler-api
+
+ org.sakaiproject.portal
+ sakai-portal-api
+
org.sakaiproject.signup
signup-api
diff --git a/sitestats/sitestats-impl/pom.xml b/sitestats/sitestats-impl/pom.xml
index cddec0486318..556114a079aa 100644
--- a/sitestats/sitestats-impl/pom.xml
+++ b/sitestats/sitestats-impl/pom.xml
@@ -67,6 +67,10 @@
+
+ org.sakaiproject.portal
+ sakai-portal-api
+
org.sakaiproject.entitybroker
entitybroker-api