Java tutorial
/** * Copyright (c) 2003-2017 The Apereo Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://opensource.org/licenses/ecl2 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sakaiproject.assignment.impl; import static org.sakaiproject.assignment.api.AssignmentServiceConstants.*; import static org.sakaiproject.assignment.api.model.Assignment.Access.*; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.text.DecimalFormat; import java.text.Normalizer; import java.text.NumberFormat; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; 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; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.xml.parsers.DocumentBuilderFactory; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.math.NumberUtils; import org.sakaiproject.announcement.api.AnnouncementChannel; import org.sakaiproject.announcement.api.AnnouncementMessage; import org.sakaiproject.announcement.api.AnnouncementMessageEdit; import org.sakaiproject.announcement.api.AnnouncementService; import org.sakaiproject.assignment.api.AssignmentConstants; import org.sakaiproject.assignment.api.AssignmentEntity; import org.sakaiproject.assignment.api.AssignmentReferenceReckoner; import org.sakaiproject.assignment.api.AssignmentService; import org.sakaiproject.assignment.api.AssignmentServiceConstants; import org.sakaiproject.assignment.api.ContentReviewResult; import org.sakaiproject.assignment.api.model.Assignment; import org.sakaiproject.assignment.api.model.AssignmentAllPurposeItem; import org.sakaiproject.assignment.api.model.AssignmentAllPurposeItemAccess; import org.sakaiproject.assignment.api.model.AssignmentModelAnswerItem; import org.sakaiproject.assignment.api.model.AssignmentNoteItem; import org.sakaiproject.assignment.api.model.AssignmentSubmission; import org.sakaiproject.assignment.api.model.AssignmentSubmissionSubmitter; import org.sakaiproject.assignment.api.model.AssignmentSupplementItemAttachment; import org.sakaiproject.assignment.api.model.AssignmentSupplementItemService; import org.sakaiproject.assignment.impl.sort.AnonymousSubmissionComparator; import org.sakaiproject.assignment.impl.sort.AssignmentSubmissionComparator; import org.sakaiproject.assignment.impl.sort.UserComparator; import org.sakaiproject.assignment.api.persistence.AssignmentRepository; import org.sakaiproject.assignment.api.taggable.AssignmentActivityProducer; import org.sakaiproject.authz.api.AuthzGroup; import org.sakaiproject.authz.api.AuthzGroupService; import org.sakaiproject.authz.api.AuthzPermissionException; import org.sakaiproject.authz.api.FunctionManager; import org.sakaiproject.authz.api.GroupNotDefinedException; import org.sakaiproject.authz.api.Member; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.calendar.api.Calendar; import org.sakaiproject.calendar.api.CalendarEvent; import org.sakaiproject.calendar.api.CalendarService; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.content.api.ContentResourceEdit; import org.sakaiproject.contentreview.dao.ContentReviewConstants; import org.sakaiproject.contentreview.dao.ContentReviewItem; import org.sakaiproject.contentreview.exception.QueueException; import org.sakaiproject.contentreview.service.ContentReviewService; import org.sakaiproject.email.api.DigestService; import org.sakaiproject.email.api.EmailService; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.EntityTransferrer; import org.sakaiproject.entity.api.HttpAccess; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.event.api.LearningResourceStoreService; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Actor; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Object; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Result; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Statement; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Verb; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Verb.SAKAI_VERB; import org.sakaiproject.event.api.NotificationService; import org.sakaiproject.exception.IdInvalidException; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.IdUsedException; import org.sakaiproject.exception.InUseException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.exception.ServerOverloadException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.rubrics.logic.model.ToolItemRubricAssociation; import org.sakaiproject.rubrics.logic.RubricsConstants; import org.sakaiproject.rubrics.logic.RubricsService; import org.sakaiproject.service.gradebook.shared.AssessmentNotFoundException; import org.sakaiproject.service.gradebook.shared.CategoryDefinition; import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService; import org.sakaiproject.service.gradebook.shared.GradebookFrameworkService; import org.sakaiproject.service.gradebook.shared.GradebookInformation; import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException; import org.sakaiproject.service.gradebook.shared.GradebookService; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.taggable.api.TaggingManager; import org.sakaiproject.taggable.api.TaggingProvider; import org.sakaiproject.time.api.UserTimeService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.Tool; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.user.api.CandidateDetailProvider; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.util.BaseResourceProperties; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.SortedIterator; import org.sakaiproject.util.Validator; import org.sakaiproject.util.api.FormattedText; import org.sakaiproject.util.api.LinkMigrationHelper; import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; /** * Created by enietzel on 3/3/17. */ @Slf4j @Transactional(readOnly = true) public class AssignmentServiceImpl implements AssignmentService, EntityTransferrer, ApplicationContextAware { @Setter private AnnouncementService announcementService; @Setter private ApplicationContext applicationContext; @Setter private AssignmentActivityProducer assignmentActivityProducer; @Setter private ObjectFactory<AssignmentEntity> assignmentEntityFactory; @Setter private AssignmentRepository assignmentRepository; @Setter private AssignmentSupplementItemService assignmentSupplementItemService; @Setter private AuthzGroupService authzGroupService; @Setter private CalendarService calendarService; @Setter private CandidateDetailProvider candidateDetailProvider; @Setter private ContentHostingService contentHostingService; @Setter private ContentReviewService contentReviewService; @Setter private DigestService digestService; @Setter private EmailService emailService; @Setter private EmailUtil emailUtil; @Setter private EntityManager entityManager; @Setter private EventTrackingService eventTrackingService; @Setter private FormattedText formattedText; @Setter private FunctionManager functionManager; @Setter private GradebookExternalAssessmentService gradebookExternalAssessmentService; @Setter private GradebookFrameworkService gradebookFrameworkService; @Setter private GradebookService gradebookService; @Setter private GradeSheetExporter gradeSheetExporter; @Setter private LearningResourceStoreService learningResourceStoreService; @Setter private LinkMigrationHelper linkMigrationHelper; @Setter private TransactionTemplate transactionTemplate; @Setter private ResourceLoader resourceLoader; @Setter private RubricsService rubricsService; @Setter private SecurityService securityService; @Setter private SessionManager sessionManager; @Setter private ServerConfigurationService serverConfigurationService; @Setter private SiteService siteService; @Setter private TaggingManager taggingManager; @Setter private ToolManager toolManager; @Setter private UserDirectoryService userDirectoryService; @Setter private UserTimeService userTimeService; private boolean allowSubmitByInstructor; private boolean exposeContentReviewErrorsToUI; public void init() { allowSubmitByInstructor = serverConfigurationService.getBoolean("assignments.instructor.submit.for.student", true); if (!allowSubmitByInstructor) { log.info( "Instructor submission of assignments is disabled - add assignments.instructor.submit.for.student=true to sakai config to enable"); } else { log.info("Instructor submission of assignments is enabled"); } exposeContentReviewErrorsToUI = serverConfigurationService.getBoolean("contentreview.expose.errors.to.ui", true); // register as an entity producer entityManager.registerEntityProducer(this, REFERENCE_ROOT); // register functions functionManager.registerFunction(SECURE_ALL_GROUPS); functionManager.registerFunction(SECURE_ADD_ASSIGNMENT); functionManager.registerFunction(SECURE_ADD_ASSIGNMENT_SUBMISSION); functionManager.registerFunction(SECURE_REMOVE_ASSIGNMENT); functionManager.registerFunction(SECURE_ACCESS_ASSIGNMENT); functionManager.registerFunction(SECURE_UPDATE_ASSIGNMENT); functionManager.registerFunction(SECURE_GRADE_ASSIGNMENT_SUBMISSION); functionManager.registerFunction(SECURE_ASSIGNMENT_RECEIVE_NOTIFICATIONS); functionManager.registerFunction(SECURE_SHARE_DRAFTS); // this is needed to avoid a circular dependency, notice we set the AssignmentService proxy and not this assignmentSupplementItemService.setAssignmentService(applicationContext.getBean(AssignmentService.class)); } @Override public String getLabel() { return "assignment"; } @Override public String getToolTitle() { Tool tool = toolManager.getTool(AssignmentServiceConstants.ASSIGNMENT_TOOL_ID); String toolTitle = null; if (tool == null) toolTitle = "Assignments"; else toolTitle = tool.getTitle(); return toolTitle; } @Override public boolean willArchiveMerge() { return true; } @Override public String archive(String siteId, Document doc, Stack<Element> stack, String archivePath, List<Reference> attachments) { String message = "archiving " + getLabel() + " context " + Entity.SEPARATOR + siteId + Entity.SEPARATOR + SiteService.MAIN_CONTAINER + ".\n"; log.debug(message); // start with an element with our very own (service) name Element element = doc.createElement(AssignmentService.class.getName()); stack.peek().appendChild(element); stack.push(element); Collection<Assignment> assignments = getAssignmentsForContext(siteId); for (Assignment assignment : assignments) { String xml = assignmentRepository.toXML(assignment); try { InputSource in = new InputSource(new StringReader(xml)); Document assignmentDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in); Element assignmentElement = assignmentDocument.getDocumentElement(); Node assignmentNode = doc.importNode(assignmentElement, true); element.appendChild(assignmentNode); } catch (Exception e) { log.warn("could not append assignment {} to archive, {}", assignment.getId(), e.getMessage()); } } stack.pop(); return message; } @Override public String merge(String siteId, Element root, String archivePath, String fromSiteId, Map<String, String> attachmentNames, Map<String, String> userIdTrans, Set<String> userListAllowImport) { return null; } @Override public boolean parseEntityReference(String stringReference, Reference reference) { if (StringUtils.startsWith(stringReference, REFERENCE_ROOT)) { AssignmentReferenceReckoner.AssignmentReference reckoner = AssignmentReferenceReckoner.reckoner() .reference(stringReference).reckon(); reference.set(SAKAI_ASSIGNMENT, reckoner.getSubtype(), reckoner.getId(), reckoner.getContainer(), reckoner.getContext()); return true; } return false; } @Override public String getEntityDescription(Reference reference) { String description = "Assignment: " + reference.getReference(); try { switch (reference.getSubType()) { case REF_TYPE_CONTENT: case REF_TYPE_ASSIGNMENT: Assignment a = getAssignment(reference.getReference()); description = "Assignment: " + a.getId() + " (" + a.getContext() + ")"; break; case REF_TYPE_SUBMISSION: AssignmentSubmission s = getSubmission(reference.getReference()); description = "AssignmentSubmission: " + s.getId() + " (" + s.getAssignment().getContext() + ")"; break; default: log.warn("Unknown Entity subtype: {} in ref: {}", reference.getSubType(), reference.getReference()); } } catch (Exception e) { log.warn("Could not get the entity description for ref = {}", reference.getReference(), e); } return description; } @Override public ResourceProperties getEntityResourceProperties(Reference reference) { ResourceProperties properties = null; try { switch (reference.getSubType()) { case REF_TYPE_CONTENT: case REF_TYPE_ASSIGNMENT: Assignment a = getAssignment(reference.getReference()); properties = new BaseResourceProperties(a.getProperties()); break; case REF_TYPE_SUBMISSION: AssignmentSubmission s = getSubmission(reference.getReference()); properties = new BaseResourceProperties(s.getProperties()); break; default: log.warn("Unknown Entity subtype: {} in ref: {}", reference.getSubType(), reference.getReference()); } } catch (Exception e) { log.warn("Could not get the entity properties for ref = {}", reference.getReference(), e); } return properties; } @Override public Entity getEntity(Reference reference) { Objects.requireNonNull(reference); Entity entity = null; switch (reference.getSubType()) { case REF_TYPE_CONTENT: case REF_TYPE_ASSIGNMENT: entity = createAssignmentEntity(reference.getId()); break; case REF_TYPE_SUBMISSION: // TODO assignment submission entities log.warn("Submission Entity not implemented open a JIRA, reference: {}", reference.getReference()); break; default: log.warn("Unknown Entity subtype: {} in ref: {}", reference.getSubType(), reference.getReference()); } return entity; } @Override public Entity createAssignmentEntity(String assignmentId) { AssignmentEntity entity = assignmentEntityFactory.getObject(); entity.initEntity(assignmentId); return entity; } @Override public Entity createAssignmentEntity(Assignment assignment) { AssignmentEntity entity = assignmentEntityFactory.getObject(); entity.initEntity(assignment); return entity; } @Override public String getEntityUrl(Reference reference) { return getEntity(reference).getUrl(); } @Override public Collection<String> getEntityAuthzGroups(Reference reference, String userId) { Collection<String> references = new ArrayList<>(); // for AssignmentService assignments: // if access set to SITE, use the assignment and site authzGroups. // if access set to GROUP, use the assignment, and the groups, but not the site authzGroups. // if the user has SECURE_ALL_GROUPS in the context, ignore GROUP access and treat as if SITE try { switch (reference.getSubType()) { case REF_TYPE_CONTENT: case REF_TYPE_ASSIGNMENT: references.add(reference.getReference()); boolean grouped = false; Collection groups = null; // check SECURE_ALL_GROUPS - if not, check if the assignment has groups or not // TODO: the last param needs to be a ContextService.getRef(ref.getContext())... or a ref.getContextAuthzGroup() -ggolden if ((userId == null) || ((!securityService.isSuperUser(userId)) && (!securityService.unlock(userId, SECURE_ALL_GROUPS, siteService.siteReference(reference.getContext()))))) { // get the channel to get the message to get group information // TODO: check for efficiency, cache and thread local caching usage -ggolden if (reference.getId() != null) { Assignment a = getAssignment(reference); if (a != null) { grouped = GROUP == a.getTypeOfAccess(); groups = a.getGroups(); } } } if (grouped) { // groups references.addAll(groups); } else { // not grouped reference.addSiteContextAuthzGroup(references); } break; case REF_TYPE_SUBMISSION: // for submission, use site security setting references.add(reference.getReference()); reference.addSiteContextAuthzGroup(references); break; default: log.warn("Unknown Entity subtype: {} in ref: {}", reference.getSubType(), reference.getReference()); } } catch (Exception e) { log.warn("Could not get the Entity's authz groups with ref = {}", reference.getReference(), e); } return references; } @Override public HttpAccess getHttpAccess() { return (req, res, ref, copyrightAcceptedRefs) -> { if (sessionManager.getCurrentSessionUserId() == null) { log.warn("Only logged in users can access assignment downloads"); } else { // determine the type of download to create using the reference that was requested AssignmentReferenceReckoner.AssignmentReference refReckoner = AssignmentReferenceReckoner.reckoner() .reference(ref.getReference()).reckon(); if (REFERENCE_ROOT.equals("/" + refReckoner.getType())) { // don't process any references that are not of type assignment switch (refReckoner.getSubtype()) { case REF_TYPE_CONTENT: case REF_TYPE_ASSIGNMENT: String date = DateTimeFormatter.ofPattern("yyyyMMddHHmmss") .withZone(userTimeService.getLocalTimeZone().toZoneId()) .format(ZonedDateTime.now()); String queryString = req.getQueryString(); if (StringUtils.isNotBlank(refReckoner.getId())) { // if subtype is assignment then were downloading all submissions for an assignment try { Assignment a = getAssignment(refReckoner.getId()); String filename = a.getTitle() + "_" + date; res.setContentType("application/zip"); res.setHeader("Content-Disposition", "attachment; filename = \"" + filename + ".zip\""); transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { try (OutputStream out = res.getOutputStream()) { getSubmissionsZip(out, ref.getReference(), queryString); } catch (Exception e) { log.warn("Could not stream the submissions for reference: {}", ref.getReference(), e); } } }); } catch (Exception e) { log.warn("Could not find assignment for ref = {}", ref.getReference(), e); } } else { String filename = "bulk_download_" + date; // if subtype is assignment and there is no assignmentId then were downloading grades res.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); res.setHeader("Content-Disposition", "attachment; filename = \"export_grades_" + filename + ".xlsx\""); try (OutputStream out = res.getOutputStream()) { gradeSheetExporter.getGradesSpreadsheet(out, ref.getReference(), queryString); } catch (Exception e) { log.warn("Could not stream the grades for reference: {}", ref.getReference(), e); } } break; case REF_TYPE_SUBMISSION: default: log.warn("Assignments download unhandled download type for reference: {}", ref.getReference()); } } } }; } @Override public boolean allowReceiveSubmissionNotification(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); if (permissionCheck(SECURE_ASSIGNMENT_RECEIVE_NOTIFICATIONS, resourceString, null)) return true; return false; } @Override public List<User> allowReceiveSubmissionNotificationUsers(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); return securityService.unlockUsers(SECURE_ASSIGNMENT_RECEIVE_NOTIFICATIONS, resourceString); } @Override public boolean allowAddSiteAssignment(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); return permissionCheck(SECURE_ADD_ASSIGNMENT, resourceString, null); } @Override public boolean allowAllGroups(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); if (permissionCheck(SECURE_ALL_GROUPS, resourceString, null)) return true; return false; } @Override public boolean allowGetAssignment(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); return permissionCheck(SECURE_ACCESS_ASSIGNMENT, resourceString, null); } @Override public Collection<Group> getGroupsAllowAddAssignment(String context) { return getGroupsAllowFunction(SECURE_ADD_ASSIGNMENT, context, null); } @Override public Collection<Group> getGroupsAllowUpdateAssignment(String context) { return getGroupsAllowFunction(SECURE_UPDATE_ASSIGNMENT, context, null); } @Override public Collection<Group> getGroupsAllowGradeAssignment(String assignmentReference) { AssignmentReferenceReckoner.AssignmentReference referenceReckoner = AssignmentReferenceReckoner.reckoner() .reference(assignmentReference).reckon(); if (allowGradeSubmission(assignmentReference)) { Collection<Group> groupsAllowed = getGroupsAllowFunction(SECURE_GRADE_ASSIGNMENT_SUBMISSION, referenceReckoner.getContext(), null); Assignment assignment = assignmentRepository.findAssignment(referenceReckoner.getId()); if (assignment != null) { switch (assignment.getTypeOfAccess()) { case SITE: // return all groups for site access return groupsAllowed; case GROUP: // return only matching groups for group access Set<String> assignmentGroups = assignment.getGroups(); return groupsAllowed.stream().filter(g -> assignmentGroups.contains(g.getReference())) .collect(Collectors.toSet()); } } } return Collections.emptySet(); } @Override public boolean allowUpdateAssignmentInContext(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); if (permissionCheck(SECURE_UPDATE_ASSIGNMENT, resourceString, null)) return true; // if not, see if the user has any groups to which updates are allowed return (!getGroupsAllowUpdateAssignment(context).isEmpty()); } @Override public boolean allowUpdateAssignment(String assignmentReference) { AssignmentReferenceReckoner.AssignmentReference referenceReckoner = AssignmentReferenceReckoner.reckoner() .reference(assignmentReference).reckon(); return allowUpdateAssignmentInContext(referenceReckoner.getContext()); } @Override public boolean allowRemoveAssignment(String assignmentReference) { return permissionCheck(SECURE_REMOVE_ASSIGNMENT, assignmentReference, null); } @Override public Collection<Group> getGroupsAllowRemoveAssignment(String context) { return getGroupsAllowFunction(SECURE_REMOVE_ASSIGNMENT, context, null); } @Override public boolean allowAddAssignment(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); if (permissionCheck(SECURE_ADD_ASSIGNMENT, resourceString, null)) return true; // if not, see if the user has any groups to which adds are allowed return (!getGroupsAllowAddAssignment(context).isEmpty()); } @Override public boolean allowRemoveAssignmentInContext(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); if (permissionCheck(SECURE_REMOVE_ASSIGNMENT, resourceString, null)) return true; // if not, see if the user has any groups to which remove is allowed return (!getGroupsAllowRemoveAssignment(context).isEmpty()); } @Override public boolean allowAddSubmission(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).subtype("s").reckon() .getReference(); return permissionCheck(SECURE_ADD_ASSIGNMENT_SUBMISSION, resourceString, null); } @Override public boolean allowAddSubmissionCheckGroups(Assignment assignment) { return permissionCheckWithGroups(SECURE_ADD_ASSIGNMENT_SUBMISSION, assignment); } @Override public List<User> allowAddSubmissionUsers(String assignmentReference) { return securityService.unlockUsers(SECURE_ADD_ASSIGNMENT_SUBMISSION, assignmentReference); } @Override public List<User> allowGradeAssignmentUsers(String assignmentReference) { List<User> users = securityService.unlockUsers(SECURE_GRADE_ASSIGNMENT_SUBMISSION, assignmentReference); String assignmentId = AssignmentReferenceReckoner.reckoner().reference(assignmentReference).reckon() .getId(); try { Assignment a = getAssignment(assignmentId); if (a.getTypeOfAccess() == GROUP) { // for grouped assignment, need to include those users that with "all.groups" and "grade assignment" permissions on the site level try { AuthzGroup group = authzGroupService.getAuthzGroup(siteService.siteReference(a.getContext())); // get the roles which are allowed for submission but not for all_site control Set<String> rolesAllowAllSite = group.getRolesIsAllowed(SECURE_ALL_GROUPS); Set<String> rolesAllowGradeAssignment = group .getRolesIsAllowed(SECURE_GRADE_ASSIGNMENT_SUBMISSION); // save all the roles with both "all.groups" and "grade assignment" permissions if (rolesAllowAllSite != null) rolesAllowAllSite.retainAll(rolesAllowGradeAssignment); if (rolesAllowAllSite != null && rolesAllowAllSite.size() > 0) { for (String aRolesAllowAllSite : rolesAllowAllSite) { Set<String> userIds = group.getUsersHasRole(aRolesAllowAllSite); if (userIds != null) { for (String userId : userIds) { try { User u = userDirectoryService.getUser(userId); if (!users.contains(u)) { users.add(u); } } catch (Exception ee) { log.warn("problem with getting user = {}, {}", userId, ee.getMessage()); } } } } } } catch (GroupNotDefinedException gnde) { log.warn("Cannot get authz group for site = {}, {}", a.getContext(), gnde.getMessage()); } } } catch (Exception e) { log.warn("Could not fetch assignment with assignmentId = {}", assignmentId, e); } return users; } @Override public List<String> allowAddAnySubmissionUsers(String context) { List<String> rv = new ArrayList<>(); try { AuthzGroup group = authzGroupService.getAuthzGroup(context); // get the roles which are allowed for submission but not for all_site control Set<String> rolesAllowSubmission = group.getRolesIsAllowed(SECURE_ADD_ASSIGNMENT_SUBMISSION); Set<String> rolesAllowAllSite = group.getRolesIsAllowed(SECURE_ALL_GROUPS); rolesAllowSubmission.removeAll(rolesAllowAllSite); for (String role : rolesAllowSubmission) { rv.addAll(group.getUsersHasRole(role)); } } catch (Exception e) { log.warn("Could not get authz group where context = {}", context); } return rv; } @Override public List<User> allowAddAssignmentUsers(String context) { String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); return securityService.unlockUsers(SECURE_ADD_ASSIGNMENT, resourceString); } @Override public boolean allowGetSubmission(String submissionReference) { if (permissionCheck(SECURE_ACCESS_ASSIGNMENT_SUBMISSION, submissionReference, null)) return true; return permissionCheck(SECURE_ACCESS_ASSIGNMENT, submissionReference, null); } @Override public boolean allowUpdateSubmission(String submissionReference) { if (permissionCheck(SECURE_UPDATE_ASSIGNMENT_SUBMISSION, submissionReference, null)) return true; return permissionCheck(SECURE_UPDATE_ASSIGNMENT, submissionReference, null); } @Override public boolean allowRemoveSubmission(String submissionReference) { return permissionCheck(SECURE_REMOVE_ASSIGNMENT_SUBMISSION, submissionReference, null); } @Override public boolean allowReviewService(Site site) { return serverConfigurationService.getBoolean("assignment.useContentReview", false) && contentReviewService != null && contentReviewService.isSiteAcceptable(site); } @Override public boolean allowGradeSubmission(String assignmentReference) { return permissionCheck(SECURE_GRADE_ASSIGNMENT_SUBMISSION, assignmentReference, null); } @Override @Transactional public Assignment addAssignment(String context) throws PermissionException { // security check if (!allowAddAssignment(context)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ADD_ASSIGNMENT, null); } Assignment assignment = new Assignment(); assignment.setContext(context); assignment.setAuthor(sessionManager.getCurrentSessionUserId()); assignment.setPosition(0); assignmentRepository.newAssignment(assignment); log.debug("Created new assignment {}", assignment.getId()); // String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); // AssignmentAction#post_save_assignment contains the logic for adding a new assignment // the event should come at the end of that logic, eventually that logic should be moved here // eventTrackingService.post(eventTrackingService.newEvent(AssignmentConstants.EVENT_ADD_ASSIGNMENT, reference, true)); return assignment; } @Override @Transactional public Assignment mergeAssignment(Element el) throws IdInvalidException, IdUsedException, PermissionException { // TODO need to write a test for this // this may also need to handle submission serialization? Assignment assignmentFromXml = assignmentRepository.fromXML(el.toString()); return addAssignment(assignmentFromXml.getContext()); } @Override @Transactional public Assignment addDuplicateAssignment(String context, String assignmentId) throws IdInvalidException, PermissionException, IdUsedException, IdUnusedException { Assignment assignment = null; if (StringUtils.isNoneBlank(context, assignmentId)) { if (!assignmentRepository.existsAssignment(assignmentId)) { throw new IdUnusedException(assignmentId); } else { if (!allowAddAssignment(context)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ADD_ASSIGNMENT, null); } log.debug("duplicating assignment with ref = {}", assignmentId); Assignment existingAssignment = getAssignment(assignmentId); assignment = new Assignment(); assignment.setContext(context); assignment.setAuthor(sessionManager.getCurrentSessionUserId()); assignment.setTitle( existingAssignment.getTitle() + " - " + resourceLoader.getString("assignment.copy")); assignment.setInstructions(existingAssignment.getInstructions()); assignment.setHonorPledge(existingAssignment.getHonorPledge()); assignment.setSection(existingAssignment.getSection()); assignment.setOpenDate(existingAssignment.getOpenDate()); assignment.setDueDate(existingAssignment.getDueDate()); assignment.setDropDeadDate(existingAssignment.getDropDeadDate()); assignment.setCloseDate(existingAssignment.getCloseDate()); assignment.setHideDueDate(existingAssignment.getHideDueDate()); assignment.setDraft(true); assignment.setPosition(existingAssignment.getPosition()); assignment.setIsGroup(existingAssignment.getIsGroup()); assignment.setAllowPeerAssessment(existingAssignment.getAllowPeerAssessment()); if (!existingAssignment.getGroups().isEmpty()) { assignment.setGroups(new HashSet<>(existingAssignment.getGroups())); assignment.setTypeOfAccess(GROUP); } // peer properties assignment.setPeerAssessmentInstructions(existingAssignment.getPeerAssessmentInstructions()); assignment.setPeerAssessmentAnonEval(existingAssignment.getPeerAssessmentAnonEval()); assignment.setPeerAssessmentNumberReviews(existingAssignment.getPeerAssessmentNumberReviews()); assignment.setPeerAssessmentPeriodDate(existingAssignment.getPeerAssessmentPeriodDate()); assignment.setPeerAssessmentStudentReview(existingAssignment.getPeerAssessmentStudentReview()); assignment.setTypeOfSubmission(existingAssignment.getTypeOfSubmission()); assignment.setTypeOfGrade(existingAssignment.getTypeOfGrade()); assignment.setMaxGradePoint(existingAssignment.getMaxGradePoint()); assignment.setScaleFactor(existingAssignment.getScaleFactor()); assignment.setIndividuallyGraded(existingAssignment.getIndividuallyGraded()); assignment.setReleaseGrades(existingAssignment.getReleaseGrades()); assignment.setAllowAttachments(existingAssignment.getAllowAttachments()); // for ContentReview service assignment.setContentReview(existingAssignment.getContentReview()); //duplicating attachments Set<String> tempAttach = existingAssignment.getAttachments(); if (tempAttach != null && !tempAttach.isEmpty()) { for (String attachId : tempAttach) { Reference tempRef = entityManager.newReference(attachId); if (tempRef != null) { String tempRefId = tempRef.getId(); String tempRefCollectionId = contentHostingService.getContainingCollectionId(tempRefId); try { // get the original attachment display name ResourceProperties p = contentHostingService.getProperties(tempRefId); String displayName = p.getProperty(ResourceProperties.PROP_DISPLAY_NAME); // add another attachment instance String newItemId = contentHostingService.copyIntoFolder(tempRefId, tempRefCollectionId); ContentResourceEdit copy = contentHostingService.editResource(newItemId); // with the same display name ResourcePropertiesEdit pedit = copy.getPropertiesEdit(); pedit.addProperty(ResourceProperties.PROP_DISPLAY_NAME, displayName); contentHostingService.commitResource(copy, NotificationService.NOTI_NONE); Reference newRef = entityManager.newReference(copy.getReference()); assignment.getAttachments().add(newRef.getReference()); } catch (Exception e) { log.warn("ERROR DUPLICATING ATTACHMENTS : " + e.toString()); } } } } Map<String, String> properties = assignment.getProperties(); existingAssignment.getProperties().entrySet().stream() .filter(e -> !PROPERTIES_EXCLUDED_FROM_DUPLICATE_ASSIGNMENTS.contains(e.getKey())) .forEach(e -> properties.put(e.getKey(), e.getValue())); assignmentRepository.newAssignment(assignment); log.debug("Created duplicate assignment {} from {}", assignment.getId(), assignmentId); //copy rubric try { Optional<ToolItemRubricAssociation> rubricAssociation = rubricsService .getRubricAssociation(RubricsConstants.RBCS_TOOL_ASSIGNMENT, assignmentId); if (rubricAssociation.isPresent()) { rubricsService.saveRubricAssociation(RubricsConstants.RBCS_TOOL_ASSIGNMENT, assignment.getId(), rubricAssociation.get().getFormattedAssociation()); } } catch (Exception e) { log.error("Error while trying to duplicate Rubrics: {} ", e.getMessage()); } String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); // event for tracking eventTrackingService.post( eventTrackingService.newEvent(AssignmentConstants.EVENT_ADD_ASSIGNMENT, reference, true)); } } return assignment; } @Override @Transactional public void deleteAssignment(Assignment assignment) throws PermissionException { Objects.requireNonNull(assignment, "Assignment cannot be null"); log.debug("Attempting to delete assignment with id = {}", assignment.getId()); String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); if (!allowRemoveAssignment(reference)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_REMOVE_ASSIGNMENT, null); } assignmentRepository.deleteAssignment(assignment.getId()); eventTrackingService .post(eventTrackingService.newEvent(AssignmentConstants.EVENT_REMOVE_ASSIGNMENT, reference, true)); // remove any realm defined for this resource try { authzGroupService.removeAuthzGroup(reference); log.debug("successful delete for assignment with id = {}", assignment.getId()); } catch (AuthzPermissionException e) { log.warn("deleting realm for assignment reference = {}", reference, e); } } @Override @Transactional public void softDeleteAssignment(Assignment assignment) throws PermissionException { Objects.requireNonNull(assignment, "Assignment cannot be null"); // we don't actually want to delete assignments just mark them as deleted "soft delete feature" log.debug("Attempting to soft delete assignment with id = {}", assignment.getId()); String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); if (!allowRemoveAssignment(reference)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_REMOVE_ASSIGNMENT, null); } assignmentRepository.softDeleteAssignment(assignment.getId()); // we post the same event as remove assignment eventTrackingService .post(eventTrackingService.newEvent(AssignmentConstants.EVENT_REMOVE_ASSIGNMENT, reference, true)); } // TODO removing related content from other tools shouldn't be the concern for assignments service // it should post an event and let those tools take action. // * Unless a transaction is required @Override @Transactional public void deleteAssignmentAndAllReferences(Assignment assignment) throws PermissionException { Objects.requireNonNull(assignment, "Assignment cannot be null"); log.debug("Removing all associated reference to assignment with id = {}", assignment.getId()); Entity entity = createAssignmentEntity(assignment.getId()); // CHECK PERMISSION permissionCheck(SECURE_REMOVE_ASSIGNMENT, entity.getReference(), null); // we may need to remove associated calendar events and annc, so get the basic info here // ResourcePropertiesEdit pEdit = assignment.getPropertiesEdit(); // String context = assignment.getContext(); // 1. remove associated calendar events, if exists removeAssociatedCalendarItem(getCalendar(assignment.getContext()), assignment); // 2. remove associated announcement, if exists removeAssociatedAnnouncementItem(getAnnouncementChannel(assignment.getContext()), assignment); // 3. remove Gradebook items, if linked removeAssociatedGradebookItem(assignment); // 4. remove tags as necessary removeAssociatedTaggingItem(assignment); // 5. remove assignment submissions // List submissions = getSubmissions(assignment); // if (submissions != null) { // for (Iterator sIterator = submissions.iterator(); sIterator.hasNext(); ) { // AssignmentSubmission s = (AssignmentSubmission) sIterator.next(); // String sReference = s.getReference(); // try { // removeSubmission(editSubmission(sReference)); // } catch (PermissionException e) { // M_log.warn("removeAssignmentAndAllReference: User does not have permission to remove submission " + sReference + " for assignment: " + assignment.getId() + e.getMessage()); // } catch (InUseException e) { // M_log.warn("removeAssignmentAndAllReference: submission " + sReference + " for assignment: " + assignment.getId() + " is in use. " + e.getMessage()); // } catch (IdUnusedException e) { // M_log.warn("removeAssignmentAndAllReference: submission " + sReference + " for assignment: " + assignment.getId() + " does not exist. " + e.getMessage()); // } // } // } // 6. remove associated content object // try { // removeAssignmentContent(editAssignmentContent(assignment.getContent().getReference())); // } catch (AssignmentContentNotEmptyException e) { // M_log.warn(" deleteAssignmentAndAllReferences(): cannot remove non-empty AssignmentContent object for assignment = " + assignment.getId() + ". " + e.getMessage()); // } catch (PermissionException e) { // M_log.warn(" deleteAssignmentAndAllReferences(): not allowed to remove AssignmentContent object for assignment = " + assignment.getId() + ". " + e.getMessage()); // } catch (InUseException e) { // M_log.warn(" deleteAssignmentAndAllReferences(): AssignmentContent object for assignment = " + assignment.getId() + " is in used. " + e.getMessage()); // } catch (IdUnusedException e) { // M_log.warn(" deleteAssignmentAndAllReferences(): cannot find AssignmentContent object for assignment = " + assignment.getId() + ". " + e.getMessage()); // } // 7. remove assignment softDeleteAssignment(assignment); // close the edit object // ((BaseAssignmentEdit) assignment).closeEdit(); // 8. remove any realm defined for this resource // try { // authzGroupService.removeAuthzGroup(assignment.getReference()); // } catch (AuthzPermissionException e) { // M_log.warn(" deleteAssignment: removing realm for assignment reference=" + assignment.getReference() + " : " + e.getMessage()); // } // // // track event // eventTrackingService.post(eventTrackingService.newEvent(AssignmentConstants.EVENT_REMOVE_ASSIGNMENT, assignment.getReference(), true)); } @Override @Transactional public AssignmentSubmission addSubmission(String assignmentId, String submitter) throws PermissionException { Assignment assignment; try { assignment = getAssignment(assignmentId); } catch (IdUnusedException iue) { log.warn("A submission cannot be added to an unknown assignment: {}", assignmentId); return null; } if (assignment != null) { String assignmentReference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); // check permissions first if (assignment.getTypeOfAccess() == GROUP) { if (!permissionCheckWithGroups(SECURE_ADD_ASSIGNMENT_SUBMISSION, assignment)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ADD_ASSIGNMENT_SUBMISSION, assignmentReference); } } else { if (!permissionCheck(SECURE_ADD_ASSIGNMENT_SUBMISSION, assignmentReference, null)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ADD_ASSIGNMENT_SUBMISSION, assignmentReference); } } // Prevent users from having more than one submission, currently assignments expects groups or users to // only have a single submission. When assignments decides to support multiple submissions this should be removed. if (assignment.getIsGroup()) { AssignmentSubmission existingSubmission = assignmentRepository .findSubmissionForGroup(assignment.getId(), submitter); if (existingSubmission != null) { return existingSubmission; } } else { AssignmentSubmission existingSubmission = assignmentRepository .findSubmissionForUser(assignment.getId(), submitter); if (existingSubmission != null) { return existingSubmission; } } Site site; try { site = siteService.getSite(assignment.getContext()); } catch (IdUnusedException iue) { log.warn("Site not found while attempting to add a submission to assignment: {}, site: {}", assignmentId, assignment.getContext()); return null; } Set<AssignmentSubmissionSubmitter> submissionSubmitters = new HashSet<>(); Optional<String> groupId = Optional.empty(); if (site != null) { if (assignment.getIsGroup()) { Group group = site.getGroup(submitter); if (group != null && assignment.getGroups().contains(group.getReference())) { group.getMembers().stream() .filter(m -> (m.getRole().isAllowed(SECURE_ADD_ASSIGNMENT_SUBMISSION) || group.isAllowed(m.getUserId(), SECURE_ADD_ASSIGNMENT_SUBMISSION)) && !m.getRole().isAllowed(SECURE_GRADE_ASSIGNMENT_SUBMISSION) && !group.isAllowed(m.getUserId(), SECURE_GRADE_ASSIGNMENT_SUBMISSION)) .forEach(member -> { AssignmentSubmissionSubmitter ass = new AssignmentSubmissionSubmitter(); ass.setSubmitter(member.getUserId()); submissionSubmitters.add(ass); }); groupId = Optional.of(submitter); } else { log.warn("A submission cannot be added for group {} to assignment {}", submitter, assignmentId); } } else { if (site.getMember(submitter) != null) { AssignmentSubmissionSubmitter submissionSubmitter = new AssignmentSubmissionSubmitter(); submissionSubmitter.setSubmitter(submitter); submissionSubmitters.add(submissionSubmitter); } else { log.warn( "Cannot add a submission for submitter {} to assignment {} as they are not a member of the site", submitter, assignmentId); } } } if (submissionSubmitters.isEmpty()) { log.warn("A new submission can't be added to assignment {} with no submitters"); return null; } // identify who the submittee is using the session String currentUser = sessionManager.getCurrentSessionUserId(); submissionSubmitters.stream().filter(s -> s.getSubmitter().equals(currentUser)).findFirst() .ifPresent(s -> s.setSubmittee(true)); AssignmentSubmission submission = assignmentRepository.newSubmission(assignment.getId(), groupId, Optional.of(submissionSubmitters), Optional.empty(), Optional.empty(), Optional.empty()); String submissionReference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon() .getReference(); eventTrackingService.post(eventTrackingService .newEvent(AssignmentConstants.EVENT_ADD_ASSIGNMENT_SUBMISSION, submissionReference, true)); log.debug("New submission: {} added to assignment: {}", submission.getId(), assignmentId); return submission; } return null; } @Override public AssignmentSubmission mergeSubmission(Element el) throws IdInvalidException, IdUsedException, PermissionException { // TODO this will probably be handled in merge Assignments as submissions are children of assignments // AssignmentSubmission submissionFromXml = new AssignmentSubmission(); // // // check for a valid submission name // if (!Validator.checkResourceId(submissionFromXml.getId())) throw new IdInvalidException(submissionFromXml.getId()); // // // check security (throws if not permitted) // unlock(SECURE_ADD_ASSIGNMENT_SUBMISSION, submissionFromXml.getReference()); // // // reserve a submission with this id from the info store - if it's in use, this will return null // AssignmentSubmissionEdit submission = m_submissionStorage.put( submissionFromXml.getId(), // submissionFromXml.getAssignmentId(), // submissionFromXml.getSubmitterIdString(), // (submissionFromXml.getTimeSubmitted() != null)?String.valueOf(submissionFromXml.getTimeSubmitted().getTime()):null, // Boolean.valueOf(submissionFromXml.getSubmitted()).toString(), // Boolean.valueOf(submissionFromXml.getGraded()).toString()); // if (submission == null) // { // throw new IdUsedException(submissionFromXml.getId()); // } // // // transfer from the XML read submission object to the SubmissionEdit // ((BaseAssignmentSubmissionEdit) submission).set(submissionFromXml); // // ((BaseAssignmentSubmissionEdit) submission).setEvent(AssignmentConstants.EVENT_ADD_ASSIGNMENT_SUBMISSION); // // return submission; return null; } @Override @Transactional public void removeSubmission(AssignmentSubmission submission) throws PermissionException { if (submission != null) { String reference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon() .getReference(); // check security if (!permissionCheck(SECURE_REMOVE_ASSIGNMENT_SUBMISSION, reference, null)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_REMOVE_ASSIGNMENT_SUBMISSION, reference); } // remove submission assignmentRepository.deleteSubmission(submission.getId()); // track event eventTrackingService.post(eventTrackingService .newEvent(AssignmentConstants.EVENT_REMOVE_ASSIGNMENT_SUBMISSION, reference, true)); try { authzGroupService.removeAuthzGroup(authzGroupService.getAuthzGroup(reference)); } catch (AuthzPermissionException e) { log.warn("removing realm for : {} : {}", reference, e.getMessage()); } catch (GroupNotDefinedException e) { log.warn("cannot find group for submission : {} : {}", reference, e.getMessage()); } } } @Override @Transactional public void updateAssignment(Assignment assignment) throws PermissionException { Assert.notNull(assignment, "Assignment cannot be null"); Assert.notNull(assignment.getId(), "Assignment doesn't appear to have been persisted yet"); String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); // security check if (!allowUpdateAssignment(reference)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_UPDATE_ASSIGNMENT, null); } eventTrackingService .post(eventTrackingService.newEvent(AssignmentConstants.EVENT_UPDATE_ASSIGNMENT, reference, true)); assignment.setDateModified(Instant.now()); assignment.setModifier(sessionManager.getCurrentSessionUserId()); assignmentRepository.update(assignment); } @Override @Transactional public void updateSubmission(AssignmentSubmission submission) throws PermissionException { Assert.notNull(submission, "Submission cannot be null"); Assert.notNull(submission.getId(), "Submission doesn't appear to have been persisted yet"); String reference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon().getReference(); if (!allowUpdateSubmission(reference)) { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_UPDATE_ASSIGNMENT_SUBMISSION, null); } eventTrackingService.post(eventTrackingService .newEvent(AssignmentConstants.EVENT_UPDATE_ASSIGNMENT_SUBMISSION, reference, true)); assignmentRepository.updateSubmission(submission); // Assignment Submission Notifications Instant dateReturned = submission.getDateReturned(); Instant dateSubmitted = submission.getDateSubmitted(); if (!submission.getSubmitted()) { // if the submission is not submitted then saving a submission event eventTrackingService.post(eventTrackingService .newEvent(AssignmentConstants.EVENT_SAVE_ASSIGNMENT_SUBMISSION, reference, true)); } else if (dateReturned == null && !submission.getReturned() && (dateSubmitted == null || submission.getDateModified().toEpochMilli() - dateSubmitted.toEpochMilli() > 1000 * 60)) { // make sure the last modified time is at least one minute after the submit time if (!(StringUtils.trimToNull(submission.getSubmittedText()) == null && submission.getAttachments().isEmpty() && StringUtils.trimToNull(submission.getGrade()) == null && StringUtils.trimToNull(submission.getFeedbackText()) == null && StringUtils.trimToNull(submission.getFeedbackComment()) == null && submission.getFeedbackAttachments().isEmpty())) { if (submission.getGraded()) { //TODO: This should use an LRS_Group when that exists rather than firing off individual events for each LRS_Actor KNL-1560 for (AssignmentSubmissionSubmitter submitter : submission.getSubmitters()) { try { User user = userDirectoryService.getUser(submitter.getSubmitter()); LRS_Statement statement = getStatementForAssignmentGraded(reference, submission.getAssignment(), submission, user); // graded and saved before releasing it Event event = eventTrackingService.newEvent( AssignmentConstants.EVENT_GRADE_ASSIGNMENT_SUBMISSION, reference, null, true, NotificationService.NOTI_OPTIONAL, statement); eventTrackingService.post(event); } catch (UserNotDefinedException e) { log.warn("Assignments could not find user ({}) while registering Event for LRSS", submitter.getSubmitter()); } } } } } else if (dateReturned != null && submission.getGraded() && (dateSubmitted == null || dateReturned.isAfter(dateSubmitted) || dateSubmitted.isAfter(dateReturned) && submission.getDateModified().isAfter(dateSubmitted))) { if (submission.getGraded()) { //TODO: This should use an LRS_Group when that exists rather than firing off individual events for each LRS_Actor KNL-1560 for (AssignmentSubmissionSubmitter submitter : submission.getSubmitters()) { try { User user = userDirectoryService.getUser(submitter.getSubmitter()); LRS_Statement statement = getStatementForAssignmentGraded(reference, submission.getAssignment(), submission, user); // releasing a submitted assignment or releasing grade to an unsubmitted assignment Event event = eventTrackingService.newEvent( AssignmentConstants.EVENT_GRADE_ASSIGNMENT_SUBMISSION, reference, null, true, NotificationService.NOTI_OPTIONAL, statement); eventTrackingService.post(event); } catch (UserNotDefinedException e) { log.warn("Assignments could not find user ({}) while registering Event for LRSS", submitter.getSubmitter()); } } } // if this is releasing grade, depending on the release grade notification setting, send email notification to student sendGradeReleaseNotification(submission); } else if (dateSubmitted == null) { //TODO: This should use an LRS_Group when that exists rather than firing off individual events for each LRS_Actor KNL-1560 for (AssignmentSubmissionSubmitter submitter : submission.getSubmitters()) { try { User user = userDirectoryService.getUser(submitter.getSubmitter()); LRS_Statement statement = getStatementForUnsubmittedAssignmentGraded(reference, submission.getAssignment(), submission, user); // releasing a submitted assignment or releasing grade to an unsubmitted assignment Event event = eventTrackingService.newEvent( AssignmentConstants.EVENT_GRADE_ASSIGNMENT_SUBMISSION, reference, null, true, NotificationService.NOTI_OPTIONAL, statement); eventTrackingService.post(event); } catch (UserNotDefinedException e) { log.warn("Assignments could not find user ({}) while registering Event for LRSS", submitter.getSubmitter()); } } } else { // submitting a submission Assignment a = submission.getAssignment(); LRS_Statement statement = getStatementForSubmitAssignment(a.getId(), serverConfigurationService.getAccessUrl(), a.getTitle()); eventTrackingService .post(eventTrackingService.newEvent(AssignmentConstants.EVENT_SUBMIT_ASSIGNMENT_SUBMISSION, reference, null, true, NotificationService.NOTI_OPTIONAL, statement)); // only doing the notification for real online submissions if (submission.getAssignment() .getTypeOfSubmission() != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION) { // instructor notification notificationToInstructors(submission, submission.getAssignment()); // student notification, whether the student gets email notification once he submits an assignment notificationToStudent(submission); } } } @Override public Assignment getAssignment(Reference reference) throws IdUnusedException, PermissionException { Assert.notNull(reference, "Reference cannot be null"); if (StringUtils.equals(SAKAI_ASSIGNMENT, reference.getType())) { return getAssignment(reference.getId()); } return null; } @Override public Assignment getAssignment(String assignmentId) throws IdUnusedException, PermissionException { log.debug("GET ASSIGNMENT : ID : {}", assignmentId); Assignment assignment = assignmentRepository.findAssignment(assignmentId); if (assignment == null) throw new IdUnusedException(assignmentId); String currentUserId = sessionManager.getCurrentSessionUserId(); // check security on the assignment return checkAssignmentAccessibleForUser(assignment, currentUserId); } @Override public AssignmentConstants.Status getAssignmentCannonicalStatus(String assignmentId) throws IdUnusedException, PermissionException { Assignment assignment = getAssignment(assignmentId); ZonedDateTime currentTime = ZonedDateTime.now(); // TODO these status's should be an enum and translation should occur in tool if (assignment.getDraft()) { return AssignmentConstants.Status.DRAFT; } else if (assignment.getOpenDate().isAfter(currentTime.toInstant())) { return AssignmentConstants.Status.NOT_OPEN; } else if (assignment.getDueDate().isAfter(currentTime.toInstant())) { return AssignmentConstants.Status.OPEN; } else if ((assignment.getCloseDate() != null) && (assignment.getCloseDate().isBefore(currentTime.toInstant()))) { return AssignmentConstants.Status.CLOSED; } else { return AssignmentConstants.Status.DUE; } } @Override public Collection<Assignment> getAssignmentsForContext(String context) { log.debug("GET ASSIGNMENTS : CONTEXT : {}", context); List<Assignment> assignments = new ArrayList<>(); if (StringUtils.isBlank(context)) return assignments; // access is checked for each assignment: // - drafts accessible by owner or if user has share drafts permission // - if assignment is restricted to groups only those in the group // - minimally user needs read permission for (Assignment assignment : assignmentRepository.findAssignmentsBySite(context)) { if (assignment.getDraft()) { if (isDraftAssignmentVisible(assignment)) { // only those who can see a draft assignment assignments.add(assignment); } } else if (assignment.getTypeOfAccess() == GROUP) { if (permissionCheckWithGroups(AssignmentServiceConstants.SECURE_ACCESS_ASSIGNMENT, assignment)) { assignments.add(assignment); } } else if (allowGetAssignment(context)) { assignments.add(assignment); } } return assignments; } @Override public Collection<Assignment> getDeletedAssignmentsForContext(String context) { log.debug("GET DELETED ASSIGNMENTS : CONTEXT : {}", context); List<Assignment> assignments = new ArrayList<>(); if (StringUtils.isBlank(context)) return assignments; return assignmentRepository.findDeletedAssignmentsBySite(context); } @Override public Map<Assignment, List<String>> getSubmittableAssignmentsForContext(String context) { Map<Assignment, List<String>> submittable = new HashMap<>(); if (!allowGetAssignment(context)) { // no permission to read assignment in context return submittable; } try { Site site = siteService.getSite(context); Set<String> siteSubmitterIds = authzGroupService.getUsersIsAllowed(SECURE_ADD_ASSIGNMENT_SUBMISSION, Arrays.asList(site.getReference())); Map<String, Set<String>> groupIdUserIds = new HashMap<>(); for (Group group : site.getGroups()) { String groupRef = group.getReference(); for (Member member : group.getMembers()) { if (member.getRole().isAllowed(SECURE_ADD_ASSIGNMENT_SUBMISSION)) { if (!groupIdUserIds.containsKey(groupRef)) { groupIdUserIds.put(groupRef, new HashSet<>()); } groupIdUserIds.get(groupRef).add(member.getUserId()); } } } // TODO this called getAccessibleAssignments need to implement Collection<Assignment> assignments = getAssignmentsForContext(context); for (Assignment assignment : assignments) { Set<String> userIds = new HashSet<>(); if (assignment.getTypeOfAccess() == GROUP) { for (String groupRef : assignment.getGroups()) { if (groupIdUserIds.containsKey(groupRef)) { userIds.addAll(groupIdUserIds.get(groupRef)); } } } else { userIds.addAll(siteSubmitterIds); } submittable.put(assignment, new ArrayList<>(userIds)); } } catch (IdUnusedException e) { log.debug("Could not retrieve submittable assignments for nonexistent site: {}", context); } return submittable; } @Override public AssignmentSubmission getSubmission(String submissionId) throws PermissionException { AssignmentSubmission submission = assignmentRepository.findSubmission(submissionId); if (submission != null) { String reference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon() .getReference(); if (allowGetSubmission(reference)) { return submission; } else { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ACCESS_ASSIGNMENT_SUBMISSION, reference); } } else { // submission not found log.debug("Submission ID does not exist {}", submissionId); } return null; } @Override @Transactional public AssignmentSubmission getSubmission(String assignmentId, User person) throws PermissionException { return getSubmission(assignmentId, person.getId()); } @Override @Transactional public AssignmentSubmission getSubmission(String assignmentId, String submitterId) throws PermissionException { if (!StringUtils.isAnyBlank(assignmentId, submitterId)) { // normal submission lookup where submitterId is for a user AssignmentSubmission submission = assignmentRepository.findSubmissionForUser(assignmentId, submitterId); if (submission == null) { // if not found submitterId could be a group id submission = assignmentRepository.findSubmissionForGroup(assignmentId, submitterId); } if (submission != null) { String reference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon() .getReference(); if (allowGetSubmission(reference)) { return submission; } else { throw new PermissionException(sessionManager.getCurrentSessionUserId(), SECURE_ACCESS_ASSIGNMENT_SUBMISSION, reference); } } else { // submission not found looked for a user submission and group submission log.debug("No submission found for user {} in assignment {}", submitterId, assignmentId); } } return null; } @Override public AssignmentSubmission getSubmission(List<AssignmentSubmission> submissions, User person) { throw new UnsupportedOperationException("Method is deprecated, remove"); } @Override public Set<AssignmentSubmission> getSubmissions(Assignment assignment) { assignmentRepository.initializeAssignment(assignment); return assignment.getSubmissions(); } @Override public String getAssignmentStatus(String assignmentId) { Assignment assignment = null; try { assignment = getAssignment(assignmentId); Instant now = Instant.now(); if (assignment.getDraft()) { return resourceLoader.getString("gen.dra1"); } else if (assignment.getOpenDate().isAfter(now)) { return resourceLoader.getString("gen.notope"); } else if (assignment.getDueDate().isAfter(now)) { return resourceLoader.getString("gen.open"); } else if ((assignment.getCloseDate() != null) && (assignment.getCloseDate().isBefore(now))) { return resourceLoader.getString("gen.closed"); } else { return resourceLoader.getString("gen.due"); } } catch (IdUnusedException | PermissionException e) { log.warn("Could not determine the status for assignment: {}, {}", assignmentId, e.getMessage()); } return null; } @Override public String getSubmissionStatus(String submissionId) { String status = ""; AssignmentSubmission submission; try { submission = getSubmission(submissionId); } catch (PermissionException e) { log.warn("Could not get submission with id {}, {}", submissionId, e.getMessage()); return status; } Assignment assignment = submission.getAssignment(); String assignmentReference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); boolean allowGrade = assignment != null && allowGradeSubmission(assignmentReference); Instant submitTime = submission.getDateSubmitted(); Instant returnTime = submission.getDateReturned(); Instant lastModTime = submission.getDateModified(); if (submission.getSubmitted() || (!submission.getSubmitted() && allowGrade)) { if (submitTime != null) { if (submission.getReturned()) { if (returnTime != null && returnTime.isBefore(submitTime)) { if (!submission.getGraded()) { status = resourceLoader.getString("gen.resub") + " " + getUsersLocalDateTimeString(submitTime); if (submitTime.isAfter(assignment.getDueDate())) { status = status + resourceLoader.getString("gen.late2"); } } else status = resourceLoader.getString("gen.returned"); } else status = resourceLoader.getString("gen.returned"); } else if (submission.getGraded() && allowGrade) { status = StringUtils.isNotBlank(submission.getGrade()) ? resourceLoader.getString("grad3") : resourceLoader.getString("gen.commented"); } else { if (allowGrade) { // ungraded submission status = resourceLoader.getString("ungra"); } else { status = resourceLoader.getString("gen.subm4") + " " + getUsersLocalDateTimeString(submitTime); } } } else { if (submission.getReturned()) { // instructor can return grading to non-submitted user status = resourceLoader.getString("gen.returned"); } else if (submission.getGraded() && allowGrade) { // instructor can grade non-submitted ones status = StringUtils.isNotBlank(submission.getGrade()) ? resourceLoader.getString("grad3") : resourceLoader.getString("gen.commented"); } else { if (allowGrade) { // show "no submission" to graders status = resourceLoader.getString("listsub.nosub"); } else { if (assignment.getHonorPledge() && submission.getHonorPledge()) { status = resourceLoader.getString("gen.hpsta"); } else { // show "not started" to students status = resourceLoader.getString("gen.notsta"); } } } } } else { if (submission.getGraded()) { if (submission.getReturned()) { // modified time is after returned time + 10 seconds if (lastModTime != null && returnTime != null && lastModTime.isAfter(returnTime.plusSeconds(10)) && !allowGrade) { // working on a returned submission now status = resourceLoader.getString("gen.dra2") + " " + resourceLoader.getString("gen.inpro"); } else { // not submitted submmission has been graded and returned status = resourceLoader.getString("gen.returned"); } } else if (allowGrade) { // grade saved but not release yet, show this to graders status = StringUtils.isNotBlank(submission.getGrade()) ? resourceLoader.getString("grad3") : resourceLoader.getString("gen.commented"); } else { // submission saved, not submitted. status = resourceLoader.getString("gen.dra2") + " " + resourceLoader.getString("gen.inpro"); } } else { if (allowGrade) status = resourceLoader.getString("ungra"); else { // TODO add a submission state of draft so we can eliminate the date check here if (assignment.getHonorPledge() && submission.getHonorPledge() && submission.getDateCreated().equals(submission.getDateModified())) { status = resourceLoader.getString("gen.hpsta"); } else { // submission saved, not submitted, status = resourceLoader.getString("gen.dra2") + " " + resourceLoader.getString("gen.inpro"); } } } } return status; } // TODO this could probably be removed @Override public List<User> getSortedGroupUsers(Group g) { List<User> users = new ArrayList<>(); g.getMembers().stream() .filter(m -> (m.getRole().isAllowed(SECURE_ADD_ASSIGNMENT_SUBMISSION) || g.isAllowed(m.getUserId(), SECURE_ADD_ASSIGNMENT_SUBMISSION)) && !m.getRole().isAllowed(SECURE_GRADE_ASSIGNMENT_SUBMISSION) && !g.isAllowed(m.getUserId(), SECURE_GRADE_ASSIGNMENT_SUBMISSION)) .forEach(member -> { try { users.add(userDirectoryService.getUser(member.getUserId())); } catch (Exception e) { log.warn("Creating a list of users, user = {}, {}", member.getUserId(), e.getMessage()); } }); users.sort(new UserComparator()); return users; } @Override public int countSubmissions(String assignmentReference, Boolean graded) { String assignmentId = AssignmentReferenceReckoner.reckoner().reference(assignmentReference).reckon() .getId(); try { Assignment assignment = getAssignment(assignmentId); boolean isNonElectronic = false; if (assignment .getTypeOfSubmission() == Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION) { isNonElectronic = true; } List<User> allowAddSubmissionUsers = allowAddSubmissionUsers(assignmentReference); // SAK-28055 need to take away those users who have the permissions defined in sakai.properties String resourceString = AssignmentReferenceReckoner.reckoner().context(assignment.getContext()).reckon() .getReference(); String[] permissions = serverConfigurationService.getStrings("assignment.submitter.remove.permission"); if (permissions != null) { for (String permission : permissions) { allowAddSubmissionUsers.removeAll(securityService.unlockUsers(permission, resourceString)); } } else { allowAddSubmissionUsers .removeAll(securityService.unlockUsers(SECURE_ADD_ASSIGNMENT, resourceString)); } List<String> userIds = allowAddSubmissionUsers.stream().map(User::getId).collect(Collectors.toList()); // if the assignment is non-electronic don't include submission date or is user submission return (int) assignmentRepository.countAssignmentSubmissions(assignmentId, graded, !isNonElectronic, !isNonElectronic, userIds); } catch (Exception e) { log.warn("Couldn't count submissions for assignment reference {}, {}", assignmentReference, e.getMessage()); } return 0; } @Override public byte[] getGradesSpreadsheet(String ref) throws IdUnusedException, PermissionException { return new byte[0]; } @Override public void getSubmissionsZip(OutputStream out, String reference, String query) throws IdUnusedException, PermissionException { boolean withStudentSubmissionText = false; boolean withStudentSubmissionAttachment = false; boolean withGradeFile = false; boolean withFeedbackText = false; boolean withFeedbackComment = false; boolean withFeedbackAttachment = false; boolean withoutFolders = false; boolean includeNotSubmitted = false; String gradeFileFormat = "csv"; String viewString = ""; String contextString = ""; String searchString = ""; String searchFilterOnly = ""; if (query != null) { StringTokenizer queryTokens = new StringTokenizer(query, "&"); // Parsing the range list while (queryTokens.hasMoreTokens()) { String token = queryTokens.nextToken().trim(); // check against the content elements selection if (token.contains("studentSubmissionText")) { // should contain student submission text information withStudentSubmissionText = true; } else if (token.contains("studentSubmissionAttachment")) { // should contain student submission attachment information withStudentSubmissionAttachment = true; } else if (token.contains("gradeFile")) { // should contain grade file withGradeFile = true; if (token.contains("gradeFileFormat=csv")) { gradeFileFormat = "csv"; } else if (token.contains("gradeFileFormat=excel")) { gradeFileFormat = "excel"; } } else if (token.contains("feedbackTexts")) { // inline text withFeedbackText = true; } else if (token.contains("feedbackComments")) { // comments should be available withFeedbackComment = true; } else if (token.contains("feedbackAttachments")) { // feedback attachment withFeedbackAttachment = true; } else if (token.contains("withoutFolders")) { // feedback attachment withoutFolders = true; } else if (token.contains("includeNotSubmitted")) { // include empty submissions includeNotSubmitted = true; } else if (token.contains("contextString")) { // context contextString = token.contains("=") ? token.substring(token.indexOf("=") + 1) : ""; } else if (token.contains("viewString")) { // view viewString = token.contains("=") ? token.substring(token.indexOf("=") + 1) : ""; } else if (token.contains("searchString")) { // search searchString = token.contains("=") ? token.substring(token.indexOf("=") + 1) : ""; } else if (token.contains("searchFilterOnly")) { // search and group filter only searchFilterOnly = token.contains("=") ? token.substring(token.indexOf("=") + 1) : ""; } } } byte[] rv = null; try { String id = AssignmentReferenceReckoner.reckoner().reference(reference).reckon().getId(); Assignment assignment = getAssignment(id); if (assignment.getIsGroup()) { Collection<Group> submitterGroups = getSubmitterGroupList(searchFilterOnly, viewString.length() == 0 ? AssignmentConstants.ALL : viewString, searchString, id, contextString == null ? assignment.getContext() : contextString); if (submitterGroups != null && !submitterGroups.isEmpty()) { List<AssignmentSubmission> submissions = new ArrayList<>(); for (Group g : submitterGroups) { log.debug("ZIP GROUP " + g.getTitle()); AssignmentSubmission sub = getSubmission(id, g.getId()); log.debug("ZIP GROUP " + g.getTitle() + " SUB " + (sub == null ? "null" : sub.getId())); if (sub != null) { submissions.add(sub); } } StringBuilder exceptionMessage = new StringBuilder(); if (allowGradeSubmission(reference)) { zipGroupSubmissions(reference, assignment.getTitle(), assignment.getTypeOfGrade().toString(), assignment.getTypeOfSubmission(), new SortedIterator(submissions.iterator(), new AssignmentSubmissionComparator( applicationContext.getBean(AssignmentService.class), siteService, userDirectoryService)), out, exceptionMessage, withStudentSubmissionText, withStudentSubmissionAttachment, withGradeFile, withFeedbackText, withFeedbackComment, withFeedbackAttachment, gradeFileFormat, includeNotSubmitted); if (exceptionMessage.length() > 0) { // log any error messages log.warn( "Encountered an issue while zipping submissions for ref = {}, exception message {}", reference, exceptionMessage); } } } } else { //List<String> submitterIds = getSubmitterIdList(searchFilterOnly, viewString.length() == 0 ? AssignmentConstants.ALL:viewString, searchString, aRef, contextString == null? a.getContext():contextString); Map<User, AssignmentSubmission> submitters = getSubmitterMap(searchFilterOnly, viewString.length() == 0 ? AssignmentConstants.ALL : viewString, searchString, reference, contextString == null ? assignment.getContext() : contextString); if (!submitters.isEmpty()) { List<AssignmentSubmission> submissions = new ArrayList<AssignmentSubmission>( submitters.values()); StringBuilder exceptionMessage = new StringBuilder(); SortedIterator sortedIterator; if (assignmentUsesAnonymousGrading(assignment)) { sortedIterator = new SortedIterator(submissions.iterator(), new AnonymousSubmissionComparator()); } else { sortedIterator = new SortedIterator(submissions.iterator(), new AssignmentSubmissionComparator( applicationContext.getBean(AssignmentService.class), siteService, userDirectoryService)); } if (allowGradeSubmission(reference)) { zipSubmissions(reference, assignment.getTitle(), assignment.getTypeOfGrade(), assignment.getTypeOfSubmission(), sortedIterator, out, exceptionMessage, withStudentSubmissionText, withStudentSubmissionAttachment, withGradeFile, withFeedbackText, withFeedbackComment, withFeedbackAttachment, withoutFolders, gradeFileFormat, includeNotSubmitted, assignment.getContext()); if (exceptionMessage.length() > 0) { log.warn( "Encountered and issue while zipping submissions for ref = {}, exception message {}", reference, exceptionMessage); } } } } } catch (Exception e) { log.warn("Cannot create submissions zip file for reference = {}", reference, e); } } @Override public String assignmentReference(String context, String id) { return AssignmentReferenceReckoner.reckoner().context(context).id(id).reckon().getReference(); } @Override public String assignmentReference(String id) { Assignment assignment = assignmentRepository.findAssignment(id); if (assignment != null) { return AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); } return null; } @Override public String submissionReference(String context, String id, String assignmentId) { return AssignmentReferenceReckoner.reckoner().context(context).id(id).container(assignmentId).subtype("s") .reckon().getReference(); } @Override public boolean canSubmit(Assignment a, String userId) { if (a == null) return false; // submissions are never allowed to non-electronic assignments if (a.getTypeOfSubmission() == Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION) { return false; } // return false only if the user is not allowed to submit and not allowed to add to the assignment if (!allowAddSubmissionCheckGroups(a) && !allowAddAssignment(a.getContext())) return false; //If userId is not defined look it up if (userId == null) { userId = sessionManager.getCurrentSessionUserId(); } try { // if user the user can access this assignment checkAssignmentAccessibleForUser(a, userId); // get user User u = userDirectoryService.getUser(userId); Instant currentTime = Instant.now(); // return false if the assignment is draft or is not open yet Instant openTime = a.getOpenDate(); if (a.getDraft() || openTime.isAfter(currentTime)) { return false; } // whether the current time is after the assignment close date inclusive boolean isBeforeAssignmentCloseDate = !currentTime.isAfter(a.getCloseDate()); // get user's submission AssignmentSubmission submission = getSubmission( AssignmentReferenceReckoner.reckoner().assignment(a).reckon().getId(), u); if (submission != null) { // check for allow resubmission or not first // return true if resubmission is allowed and current time is before resubmission close time // get the resubmit settings from submission object first String allowResubmitNumString = submission.getProperties() .get(AssignmentConstants.ALLOW_RESUBMIT_NUMBER); if (NumberUtils.isParsable(allowResubmitNumString) && submission.getSubmitted() && submission.getDateSubmitted() != null) { String allowResubmitCloseTime = submission.getProperties() .get(AssignmentConstants.ALLOW_RESUBMIT_CLOSETIME); try { int allowResubmitNumber = Integer.parseInt(allowResubmitNumString); Instant resubmitCloseTime; if (NumberUtils.isParsable(allowResubmitCloseTime)) { // see if a resubmission close time is set on submission level resubmitCloseTime = Instant.ofEpochMilli(Long.parseLong(allowResubmitCloseTime)); } else { // otherwise, use assignment close time as the resubmission close time resubmitCloseTime = a.getCloseDate(); } return (allowResubmitNumber > 0 || allowResubmitNumber == -1) && !currentTime.isAfter(resubmitCloseTime); } catch (NumberFormatException e) { log.warn("allowResubmitNumString = {}, allowResubmitCloseTime = {}", allowResubmitNumString, allowResubmitCloseTime, e); } } if (isBeforeAssignmentCloseDate && (submission.getDateSubmitted() == null || !submission.getSubmitted())) { // before the assignment close date // and if no date then a submission was never never submitted // or if there is a submitted date and its a not submitted then it is considered a draft return true; } } else { // there is no submission yet so only check if before assignment close date return isBeforeAssignmentCloseDate; } } catch (UserNotDefinedException e) { log.warn("The user {} could not be found while checking if they can submit to assignment {}, {}", userId, a.getId(), e.getMessage()); } catch (PermissionException e) { log.warn("The user {} cannot submit to assignment {}, {}", userId, a.getId(), e.getMessage()); } return false; } @Override public boolean canSubmit(Assignment a) { return canSubmit(a, null); } @Override @Transactional public Collection<Group> getSubmitterGroupList(String searchFilterOnly, String allOrOneGroup, String searchString, String assignmentId, String contextString) { Collection<Group> rv = new ArrayList<Group>(); allOrOneGroup = StringUtils.trimToNull(allOrOneGroup); try { Assignment a = getAssignment(assignmentId); if (a != null) { Site st = siteService.getSite(contextString); if (StringUtils.equals(allOrOneGroup, AssignmentConstants.ALL) || StringUtils.isEmpty(allOrOneGroup)) { if (a.getTypeOfAccess().equals(SITE)) { for (Group group : st.getGroups()) { rv.add(group); } } else { for (String groupRef : a.getGroups()) { Group group = st.getGroup(groupRef); // NO SECTIONS (this might not be valid test for manually created sections) if (group != null) { rv.add(group); } } } } else { Group group = st.getGroup(allOrOneGroup); if (group != null) {// && _gg.getProperties().get(GROUP_SECTION_PROPERTY) == null) { rv.add(group); } } for (Group g : rv) { AssignmentSubmission uSubmission = getSubmission(assignmentId, g.getId()); if (uSubmission == null) { if (allowGradeSubmission( AssignmentReferenceReckoner.reckoner().assignment(a).reckon().getReference())) { if (a.getIsGroup()) { // temporarily allow the user to read and write from assignments (asn.revise permission) SecurityAdvisor securityAdvisor = new MySecurityAdvisor( sessionManager.getCurrentSessionUserId(), new ArrayList<>(Arrays.asList(SECURE_ADD_ASSIGNMENT_SUBMISSION, SECURE_UPDATE_ASSIGNMENT_SUBMISSION)), ""/* no submission id yet, pass the empty string to advisor*/); try { securityService.pushAdvisor(securityAdvisor); log.debug("context {} for assignment {} for group {}", contextString, a.getId(), g.getId()); AssignmentSubmission s = addSubmission(a.getId(), g.getId()); s.setSubmitted(true); s.setUserSubmission(false); // set the resubmission properties // get the assignment setting for resubmitting Map<String, String> assignmentProperties = a.getProperties(); String assignmentAllowResubmitNumber = assignmentProperties .get(AssignmentConstants.ALLOW_RESUBMIT_NUMBER); if (assignmentAllowResubmitNumber != null) { s.getProperties().put(AssignmentConstants.ALLOW_RESUBMIT_NUMBER, assignmentAllowResubmitNumber); String assignmentAllowResubmitCloseDate = assignmentProperties .get(AssignmentConstants.ALLOW_RESUBMIT_CLOSETIME); // if assignment's setting of resubmit close time is null, use assignment close time as the close time for resubmit s.getProperties().put(AssignmentConstants.ALLOW_RESUBMIT_CLOSETIME, assignmentAllowResubmitCloseDate != null ? assignmentAllowResubmitCloseDate : String.valueOf(a.getCloseDate().toEpochMilli())); } assignmentRepository.updateSubmission(s); // clear the permission } catch (Exception e) { log.warn( "exception thrown while creating empty submission for group who has not submitted, {}", e.getMessage()); } finally { securityService.popAdvisor(securityAdvisor); } } } } } } } catch (IdUnusedException aIdException) { log.warn("Assignment id not used: {}, {}", assignmentId, aIdException.getMessage()); } catch (PermissionException aPerException) { log.warn("Not allowed to get assignment {}, {}", assignmentId, aPerException.getMessage()); } return rv; } @Override public boolean getAllowSubmitByInstructor() { return allowSubmitByInstructor; } @Override @Transactional public List<String> getSubmitterIdList(String searchFilterOnly, String allOrOneGroup, String searchString, String aRef, String contextString) { List<String> rv = new ArrayList<>(); Map<User, AssignmentSubmission> submitterMap = getSubmitterMap(searchFilterOnly, allOrOneGroup, searchString, aRef, contextString); for (User u : submitterMap.keySet()) { rv.add(u.getId()); } return rv; } @Override @Transactional public Map<User, AssignmentSubmission> getSubmitterMap(String searchFilterOnly, String allOrOneGroup, String searchString, String aRef, String contextString) { Map<User, AssignmentSubmission> rv = new HashMap<>(); Assignment assignment = null; if (StringUtils.isNotBlank(aRef)) { String id = AssignmentReferenceReckoner.reckoner().reference(aRef).reckon().getId(); try { assignment = getAssignment(id); } catch (IdUnusedException iue) { log.warn("Assignment could not be found with id: {}, {}", id, iue.getMessage()); } catch (PermissionException pe) { log.warn("You do not have permissions to access assignment {}, {}", id, pe.getMessage()); } } if (assignment != null) { List<User> rvUsers; allOrOneGroup = StringUtils.trimToNull(allOrOneGroup); searchString = StringUtils.trimToNull(searchString); boolean bSearchFilterOnly = "true".equalsIgnoreCase(searchFilterOnly); if (assignmentUsesAnonymousGrading(assignment)) { bSearchFilterOnly = false; searchString = ""; } if (bSearchFilterOnly) { if (allOrOneGroup == null && searchString == null) { // if the option is set to "Only show user submissions according to Group Filter and Search result" // if no group filter and no search string is specified, no user will be shown first by default; return rv; } else { List<User> allowAddSubmissionUsers = allowAddSubmissionUsers(aRef); if (allOrOneGroup == null) { // search is done for all submitters rvUsers = getSearchedUsers(searchString, allowAddSubmissionUsers, false); } else { // group filter first rvUsers = getSelectedGroupUsers(allOrOneGroup, contextString, assignment, allowAddSubmissionUsers); if (searchString != null) { // then search rvUsers = getSearchedUsers(searchString, rvUsers, true); } } } } else { List<User> allowAddSubmissionUsers = allowAddSubmissionUsers(aRef); // SAK-28055 need to take away those users who have the permissions defined in sakai.properties String resourceString = AssignmentReferenceReckoner.reckoner().context(assignment.getContext()) .reckon().getReference(); String[] permissions = serverConfigurationService .getStrings("assignment.submitter.remove.permission"); if (permissions != null) { for (String permission : permissions) { allowAddSubmissionUsers.removeAll(securityService.unlockUsers(permission, resourceString)); } } else { allowAddSubmissionUsers .removeAll(securityService.unlockUsers(SECURE_ADD_ASSIGNMENT, resourceString)); } // Step 1: get group if any that is selected rvUsers = getSelectedGroupUsers(allOrOneGroup, contextString, assignment, allowAddSubmissionUsers); // Step 2: get all student that meets the search criteria based on previous group users. If search is null or empty string, return all users. rvUsers = getSearchedUsers(searchString, rvUsers, true); } if (!rvUsers.isEmpty()) { for (User user : rvUsers) { AssignmentSubmission submission = assignmentRepository.findSubmissionForUser(assignment.getId(), user.getId()); if (submission != null) { rv.put(user, submission); } else { String submitter = null; switch (assignment.getTypeOfAccess()) { case SITE: // access is for the entire site and submitter is a user submitter = user.getId(); break; case GROUP: // access is restricted to groups Site site; try { site = siteService.getSite(assignment.getContext()); } catch (IdUnusedException iue) { log.warn( "Could not get the site {} for assignment {} while determining the submitter of the submission", assignment.getContext(), assignment.getId()); break; } Set<String> assignmentGroups = assignment.getGroups(); Collection<Group> userGroups = site.getGroupsWithMember(user.getId()); Set<String> groupIdsMatchingAssignmentForUser = userGroups.stream() .filter(g -> assignmentGroups.contains(g.getReference())).map(Group::getId) .collect(Collectors.toSet()); if (groupIdsMatchingAssignmentForUser.size() < 1) { log.debug("User {} is not a member of any groups for this assignment {}", user.getId(), assignment.getId()); } else if (groupIdsMatchingAssignmentForUser.size() == 1) { if (assignment.getIsGroup()) { submitter = groupIdsMatchingAssignmentForUser.toArray(new String[] {})[0]; } else { submitter = user.getId(); } break; } else if (groupIdsMatchingAssignmentForUser.size() > 1) { log.warn( "User {} is on more than one group for this assignment {}, please remove the user from a group so that they are only a member of a single group", user.getId(), assignment.getId()); } default: log.warn( "Can't determine the type of submission to create for user {} in assignment {}", user.getId(), assignment.getId()); continue; } if (submitter != null) { try { submission = addSubmission(assignment.getId(), submitter); if (submission != null) { // Note: If we had s.setSubmitted(false);, this would put it in 'draft mode' submission.setSubmitted(true); /* * SAK-29314 - Since setSubmitted represents whether the submission is in draft mode state, we need another property. So we created isUserSubmission. * This represents whether the submission was geenrated by a user. * We set it to false because these submissions are generated so that the instructor has something to grade; * the user did not in fact submit anything. */ submission.setUserSubmission(false); // set the resubmission properties // get the assignment setting for resubmitting Map<String, String> assignmentProperties = assignment.getProperties(); String assignmentAllowResubmitNumber = assignmentProperties .get(AssignmentConstants.ALLOW_RESUBMIT_NUMBER); if (StringUtils.isNotBlank(assignmentAllowResubmitNumber)) { Map<String, String> submissionProperties = submission.getProperties(); submissionProperties.put(AssignmentConstants.ALLOW_RESUBMIT_NUMBER, assignmentAllowResubmitNumber); String assignmentAllowResubmitCloseDate = assignmentProperties .get(AssignmentConstants.ALLOW_RESUBMIT_CLOSETIME); // if assignment's setting of resubmit close time is null, use assignment close time as the close time for resubmit submissionProperties.put(AssignmentConstants.ALLOW_RESUBMIT_CLOSETIME, StringUtils.isNotBlank(assignmentAllowResubmitCloseDate) ? assignmentAllowResubmitCloseDate : String.valueOf(assignment.getCloseDate().toEpochMilli())); } assignmentRepository.updateSubmission(submission); rv.put(user, submission); } else { log.warn( "No submission was found/created for user {} in assignment {}, this should never happen", user.getId(), assignment.getId()); } } catch (Exception e) { log.warn( "Exception thrown while creating empty submission for student who has not submitted, {}", e.getMessage(), e); } } } } } } return rv; } private List<User> getSelectedGroupUsers(String allOrOneGroup, String contextString, Assignment a, List allowAddSubmissionUsers) { Collection<String> authzRefs = new ArrayList<>(); List<User> selectedGroupUsers = new ArrayList<>(); if (StringUtils.isNotBlank(allOrOneGroup)) { // now are we view all sections/groups or just specific one? if (allOrOneGroup.equals(AssignmentConstants.ALL)) { if (allowAllGroups(contextString)) { // site range try { Site site = siteService.getSite(contextString); authzRefs.add(site.getReference()); } catch (IdUnusedException e) { log.warn("Cannot find site {} {}", contextString, e.getMessage()); } } else { // get all those groups that user is allowed to grade Collection<Group> groups = getGroupsAllowGradeAssignment( AssignmentReferenceReckoner.reckoner().assignment(a).reckon().getReference()); groups.forEach(g -> authzRefs.add(g.getReference())); } } else { // filter out only those submissions from the selected-group members try { Group group = siteService.getSite(contextString).getGroup(allOrOneGroup); authzRefs.add(group.getReference()); } catch (IdUnusedException e) { log.warn("Cannot add groupId = {}, {}", allOrOneGroup, e.getMessage()); } } for (String ref : authzRefs) { try { AuthzGroup group = authzGroupService.getAuthzGroup(ref); for (String userId : group.getUsers()) { // don't show user multiple times try { User u = userDirectoryService.getUser(userId); if (u != null && allowAddSubmissionUsers.contains(u)) { if (!selectedGroupUsers.contains(u)) { selectedGroupUsers.add(u); } } } catch (UserNotDefinedException uException) { log.warn("User not found with id = {}, {}", userId, uException.getMessage()); } } } catch (GroupNotDefinedException gException) { log.warn("Group not found with reference = {}, {}", ref, gException.getMessage()); } } } return selectedGroupUsers; } private List<User> getSearchedUsers(String searchString, List<User> userList, boolean retain) { List<User> rv = new ArrayList<>(); if (StringUtils.isNotBlank(searchString)) { searchString = searchString.toLowerCase(); for (User u : userList) { // search on user sortname, eid, email String[] fields = { u.getSortName(), u.getEid(), u.getEmail() }; List<String> l = Arrays.asList(fields); for (String s : l) { if (StringUtils.containsIgnoreCase(s, searchString)) { rv.add(u); break; } } } } else if (retain) { // retain the original list rv = userList; } return rv; } @Override public String escapeInvalidCharsEntry(String accentedString) { String decomposed = Normalizer.normalize(accentedString, Normalizer.Form.NFD); return decomposed.replaceAll("\\p{InCombiningDiacriticalMarks}+", ""); } @Override public boolean assignmentUsesAnonymousGrading(Assignment assignment) { if (assignment != null) { return Boolean.valueOf(assignment.getProperties() .get(AssignmentServiceConstants.NEW_ASSIGNMENT_CHECK_ANONYMOUS_GRADING)); } return false; } @Override public Integer getScaleFactor() { Integer decimals = serverConfigurationService.getInt("assignment.grading.decimals", AssignmentConstants.DEFAULT_DECIMAL_POINT); return Double.valueOf(Math.pow(10.0, decimals)).intValue(); } @Override public String getDeepLinkWithPermissions(String context, String assignmentId, boolean allowReadAssignment, boolean allowAddAssignment, boolean allowSubmitAssignment) throws Exception { Assignment a = getAssignment(assignmentId); String assignmentContext = a.getContext(); // assignment context if (allowReadAssignment && a.getOpenDate().isBefore(ZonedDateTime.now().toInstant())) { // this checks if we want to display an assignment link try { Site site = siteService.getSite(assignmentContext); // site id ToolConfiguration fromTool = site.getToolForCommonId("sakai.assignment.grades"); // Three different urls to be rendered depending on the // user's permission if (allowAddAssignment) { return serverConfigurationService.getPortalUrl() + "/directtool/" + fromTool.getId() + "?assignmentId=" + assignmentId + "&assignmentReference=" + AssignmentReferenceReckoner.reckoner().context(context) .id(assignmentId).reckon().getReference() + "&panel=Main&sakai_action=doView_assignment"; } else if (allowSubmitAssignment) { return serverConfigurationService.getPortalUrl() + "/directtool/" + fromTool.getId() + "?assignmentId=" + assignmentId + "&assignmentReference=" + AssignmentReferenceReckoner.reckoner().context(context) .id(assignmentId).reckon().getReference() + "&panel=Main&sakai_action=doView_submission"; } else { // user can read the assignment, but not submit, so // render the appropriate url return serverConfigurationService.getPortalUrl() + "/directtool/" + fromTool.getId() + "?assignmentId=" + assignmentId + "&assignmentReference=" + AssignmentReferenceReckoner.reckoner().context(context) .id(assignmentId).reckon().getReference() + "&panel=Main&sakai_action=doView_assignment_as_student"; } } catch (IdUnusedException e) { // No site found throw new IdUnusedException("No site found while creating assignment url"); } } return ""; } @Override public String getDeepLink(String context, String assignmentId) throws Exception { boolean allowReadAssignment = allowGetAssignment(context); boolean allowAddAssignment = allowAddAssignment(context); boolean allowSubmitAssignment = allowAddSubmission(context); return getDeepLinkWithPermissions(context, assignmentId, allowReadAssignment, allowAddAssignment, allowSubmitAssignment); } @Override public String getCsvSeparator() { String defaultSeparator = ","; //If the decimal separator is a comma if (",".equals(formattedText.getDecimalSeparator())) { defaultSeparator = ";"; } return serverConfigurationService.getString("csv.separator", defaultSeparator); } @Override public String getXmlAssignment(Assignment assignment) { return assignmentRepository.toXML(assignment); } @Override public String getGradeForSubmitter(String submissionId, String submitter) { if (StringUtils.isAnyBlank(submissionId, submitter)) return null; return getGradeForSubmitter(assignmentRepository.findSubmission(submissionId), submitter); } @Override public String getGradeForSubmitter(AssignmentSubmission submission, String submitter) { if (submission == null || StringUtils.isBlank(submitter)) return null; String grade = null; Assignment assignment = submission.getAssignment(); // if this assignment is associated to the gradebook always use that score first String gradebookAssignmentName = assignment.getProperties() .get(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT); if (StringUtils.isNotBlank(gradebookAssignmentName) && !gradebookExternalAssessmentService .isExternalAssignmentDefined(assignment.getContext(), gradebookAssignmentName)) { // associated gradebook item grade = gradebookService.getAssignmentScoreStringByNameOrId(assignment.getContext(), gradebookAssignmentName, submitter); } if (StringUtils.isNotBlank(grade)) { // exists a grade in the gradebook so we use that if (StringUtils.isNumeric(grade)) { Integer score = Integer.parseInt(grade); // convert gradebook to an asssignments score using the scale factor grade = Integer.toString(score * assignment.getScaleFactor()); } } else { // otherwise use grade maintained by assignments or is considered externally mananged or is not released grade = submission.getGrade(); // start with submission grade if (assignment.getIsGroup()) { Optional<AssignmentSubmissionSubmitter> submissionSubmitter = submission.getSubmitters().stream() .filter(s -> s.getSubmitter().equals(submitter)).findAny(); if (submissionSubmitter.isPresent()) { grade = StringUtils.defaultIfBlank(submissionSubmitter.get().getGrade(), grade); // if there is a grade override use that } } } Integer scale = assignment.getScaleFactor() != null ? assignment.getScaleFactor() : getScaleFactor(); grade = getGradeDisplay(grade, assignment.getTypeOfGrade(), scale); return grade; } /** * Contains logic to consistently output a String based version of a grade * Interprets the grade using the scale for display * * This should probably be moved to a static utility class - ern * * @param grade * @param typeOfGrade * @param scaleFactor * @return */ @Override public String getGradeDisplay(String grade, Assignment.GradeType typeOfGrade, Integer scaleFactor) { String returnGrade = StringUtils.trimToEmpty(grade); if (scaleFactor == null) scaleFactor = getScaleFactor(); switch (typeOfGrade) { case SCORE_GRADE_TYPE: if (!returnGrade.isEmpty() && !"0".equals(returnGrade)) { int dec = new Double(Math.log10(scaleFactor)).intValue(); String decSeparator = formattedText.getDecimalSeparator(); String decimalGradePoint = returnGrade; try { Integer.parseInt(returnGrade); // if point grade, display the grade with factor decimal place if (returnGrade.length() > dec) { decimalGradePoint = returnGrade.substring(0, returnGrade.length() - dec) + decSeparator + returnGrade.substring(returnGrade.length() - dec); } else { String newGrade = "0".concat(decSeparator); for (int i = returnGrade.length(); i < dec; i++) { newGrade = newGrade.concat("0"); } decimalGradePoint = newGrade.concat(returnGrade); } } catch (NumberFormatException nfe1) { log.debug("Could not parse grade [{}] as an Integer trying as a Float, {}", returnGrade, nfe1.getMessage()); try { Float.parseFloat(returnGrade); decimalGradePoint = returnGrade; } catch (NumberFormatException nfe2) { log.debug("Could not parse grade [{}] as a Float, {}", returnGrade, nfe2.getMessage()); } } // get localized number format NumberFormat nbFormat = formattedText.getNumberFormat(dec, dec, false); DecimalFormat dcformat = (DecimalFormat) nbFormat; // show grade in localized number format try { Double dblGrade = dcformat.parse(decimalGradePoint).doubleValue(); decimalGradePoint = nbFormat.format(dblGrade); returnGrade = decimalGradePoint; } catch (Exception e) { log.warn("Could not parse grade [{}], {}", returnGrade, e.getMessage()); } } break; case UNGRADED_GRADE_TYPE: if (returnGrade.equalsIgnoreCase("gen.nograd")) { returnGrade = resourceLoader.getString("gen.nograd"); } break; case PASS_FAIL_GRADE_TYPE: if (returnGrade.equalsIgnoreCase("Pass")) { returnGrade = resourceLoader.getString("pass"); } else if (returnGrade.equalsIgnoreCase("Fail")) { returnGrade = resourceLoader.getString("fail"); } else { returnGrade = resourceLoader.getString("ungra"); } break; case CHECK_GRADE_TYPE: if (returnGrade.equalsIgnoreCase("Checked")) { returnGrade = resourceLoader.getString("gen.checked"); } else { returnGrade = resourceLoader.getString("ungra"); } break; default: if (returnGrade.isEmpty()) { returnGrade = resourceLoader.getString("ungra"); } } return returnGrade; } @Override public String getMaxPointGradeDisplay(int factor, int maxGradePoint) { // formated to show factor decimal places, for example, 1000 to 100.0 // get localized number format // NumberFormat nbFormat = FormattedText.getNumberFormat((int)Math.log10(factor),(int)Math.log10(factor),false); // show grade in localized number format // Double dblGrade = new Double(maxGradePoint/(double)factor); // return nbFormat.format(dblGrade); return getGradeDisplay(Integer.toString(maxGradePoint), Assignment.GradeType.SCORE_GRADE_TYPE, factor); } @Override public Optional<AssignmentSubmissionSubmitter> getSubmissionSubmittee(AssignmentSubmission submission) { Objects.requireNonNull(submission, "Submission cannot be null"); return submission.getSubmitters().stream().filter(AssignmentSubmissionSubmitter::getSubmittee).findFirst(); } @Override public Collection<User> getSubmissionSubmittersAsUsers(AssignmentSubmission submission) { Objects.requireNonNull(submission, "Submission cannot be null"); List<User> submitters = new ArrayList<>(); for (AssignmentSubmissionSubmitter submitter : submission.getSubmitters()) { try { User user = userDirectoryService.getUser(submitter.getSubmitter()); submitters.add(user); } catch (UserNotDefinedException e) { log.warn("Could not find user with id: {}", submitter.getSubmitter()); } } return submitters; } @Override public boolean isPeerAssessmentOpen(Assignment assignment) { if (assignment.getAllowPeerAssessment()) { Instant now = Instant.now(); return now.isBefore(assignment.getPeerAssessmentPeriodDate()) && now.isAfter(assignment.getCloseDate()); } return false; } @Override public boolean isPeerAssessmentPending(Assignment assignment) { if (assignment.getAllowPeerAssessment()) { return Instant.now().isBefore(assignment.getCloseDate()); } return false; } @Override public boolean isPeerAssessmentClosed(Assignment assignment) { if (assignment.getAllowPeerAssessment()) { return Instant.now().isAfter(assignment.getPeerAssessmentPeriodDate()); } return false; } private Collection<Group> getGroupsAllowFunction(String function, String context, String userId) { Collection<Group> rv = new HashSet<>(); try { Site site = siteService.getSite(context); if (StringUtils.isBlank(userId)) { userId = sessionManager.getCurrentSessionUserId(); } Collection<Group> groups = site.getGroups(); // if the user has SECURE_ALL_GROUPS in the context (site), select all site groups if (securityService.unlock(userId, SECURE_ALL_GROUPS, siteService.siteReference(context)) && permissionCheck(function, siteService.siteReference(context), null)) { rv.addAll(groups); } else { // get a list of the group refs, which are authzGroup ids Set<String> groupRefs = groups.stream().map(Group::getReference).collect(Collectors.toSet()); // ask the authzGroup service to filter them down based on function Set<String> allowedGroupRefs = authzGroupService.getAuthzGroupsIsAllowed(userId, function, groupRefs); // pick the Group objects from the site's groups to return, those that are in the allowedGroupRefs list rv = groups.stream().filter(g -> allowedGroupRefs.contains(g.getReference())) .collect(Collectors.toSet()); } } catch (IdUnusedException e) { log.debug("site {} not found, {}", context, e.getMessage()); } return rv; } private Assignment checkAssignmentAccessibleForUser(Assignment assignment, String currentUserId) throws PermissionException { if (assignment.getTypeOfAccess() == GROUP) { String context = assignment.getContext(); Collection<String> asgGroups = assignment.getGroups(); Collection<Group> allowedGroups = getGroupsAllowFunction(SECURE_ACCESS_ASSIGNMENT, context, currentUserId); // reject and throw PermissionException if there is no intersection if (!allowAllGroups(context) && !StringUtils.equals(assignment.getAuthor(), currentUserId) && !CollectionUtils.containsAny(asgGroups, allowedGroups.stream().map(Group::getReference).collect(Collectors.toSet()))) { throw new PermissionException(currentUserId, SECURE_ACCESS_ASSIGNMENT, assignment.getId()); } } if (allowAddAssignment(assignment.getContext())) { // always return for users that can add assignment in the context return assignment; } else if (isAvailableOrSubmitted(assignment, currentUserId)) { return assignment; } throw new PermissionException(currentUserId, SECURE_ACCESS_ASSIGNMENT, assignment.getId()); } private boolean isAvailableOrSubmitted(Assignment assignment, String userId) { if (!assignment.getDeleted()) { // show not deleted, not draft, opened assignments // TODO do we need to use time zone Instant openTime = assignment.getOpenDate(); Instant visibleTime = assignment.getVisibleDate(); if ((openTime != null && Instant.now().isAfter(openTime)) || (visibleTime != null && Instant.now().isAfter(visibleTime)) && !assignment.getDraft()) { return true; } } else { try { if (assignment.getDeleted() && assignment .getTypeOfSubmission() != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION && getSubmission(assignment.getId(), userId) != null) { // and those deleted but not non-electronic assignments but the user has made submissions to them return true; } } catch (PermissionException e) { log.warn("User doesn't have permission to access submission, {}", e.getMessage()); } } return false; } private boolean isDraftAssignmentVisible(Assignment assignment) { return StringUtils.equals(assignment.getAuthor(), userDirectoryService.getCurrentUser().getId()) // the author can see it || permissionCheck(SECURE_SHARE_DRAFTS, siteService.siteReference(assignment.getContext()), null); // any role user with share draft permission } private boolean permissionCheck(String permission, String resource, String user) { boolean access = false; if (!StringUtils.isAnyBlank(resource, permission)) { if (StringUtils.isBlank(user)) { access = securityService.unlock(permission, resource); log.debug("checking permission [{}] in context [{}] for current user: {}", permission, resource, access); } else { access = securityService.unlock(user, permission, resource); log.debug("checking permission [{}] in context [{}] for user [{}]: {}", permission, resource, user, access); } } return access; } private boolean permissionCheckWithGroups(String permission, Assignment assignment) { if (GROUP == assignment.getTypeOfAccess()) { // check the permission in the groups for (String groupId : assignment.getGroups()) { if (securityService.unlock(permission, groupId)) { return true; } } // lastly if the user has permission asn.all.groups and has permission for the site if (allowAllGroups(assignment.getContext()) && securityService.unlock(permission, siteService.siteReference(assignment.getContext()))) { return true; } } else { return permissionCheck(permission, siteService.siteReference(assignment.getContext()), null); } return false; } // ///////////////////////////////////////////////////////////// // TODO // cleaning up the following entries in other tools should // probably happen as the result of posting an event in the // respective tools service and not be chained here // only if a rollback were needed would we want to include here // ///////////////////////////////////////////////////////////// private void removeAssociatedTaggingItem(Assignment assignment) { try { if (taggingManager.isTaggable()) { for (TaggingProvider provider : taggingManager.getProviders()) { provider.removeTags(assignmentActivityProducer.getActivity(assignment)); } } } catch (PermissionException pe) { log.warn("removeAssociatedTaggingItem: User does not have permission to remove tags for assignment: " + assignment.getId() + " via transferCopyEntities"); } } private void removeAssociatedGradebookItem(Assignment assignment) { String context = assignment.getContext(); String associatedGradebookAssignment = assignment.getProperties() .get(AssignmentServiceConstants.PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT); if (StringUtils.isNotBlank(associatedGradebookAssignment)) { try { boolean isExternalAssignmentDefined = gradebookExternalAssessmentService .isExternalAssignmentDefined(context, associatedGradebookAssignment); if (isExternalAssignmentDefined) { gradebookExternalAssessmentService.removeExternalAssessment(context, associatedGradebookAssignment); } } catch (GradebookNotFoundException gnfe) { // this may occur if no gradebook tool exists in the site log.debug("Attempted to remove associated gradebook item, {}", gnfe.getMessage()); } } } private Calendar getCalendar(String context) { Calendar calendar = null; String calendarId = serverConfigurationService.getString("calendar", null); if (calendarId == null) { calendarId = calendarService.calendarReference(context, siteService.MAIN_CONTAINER); try { calendar = calendarService.getCalendar(calendarId); } catch (IdUnusedException e) { log.warn("No calendar found for site: " + context); calendar = null; } catch (PermissionException e) { log.error("The current user does not have permission to access the calendar for context: {}", context, e); } catch (Exception ex) { log.error("Unknown exception occurred retrieving calendar for site: {}", context, ex); calendar = null; } } return calendar; } private void removeAssociatedCalendarItem(Calendar calendar, Assignment assignment) { Map<String, String> properties = assignment.getProperties(); String isThereEvent = properties.get(AssignmentConstants.NEW_ASSIGNMENT_DUE_DATE_SCHEDULED); if (isThereEvent != null && isThereEvent.equals(Boolean.TRUE.toString())) { // remove the associated calendar event if (calendar != null) { // already has calendar object // get the old event CalendarEvent event = null; String oldEventId = properties.get(ResourceProperties.PROP_ASSIGNMENT_DUEDATE_CALENDAR_EVENT_ID); if (oldEventId != null) { try { event = calendar.getEvent(oldEventId); } catch (Exception e) { log.warn("Could not get the calendar event with id = {}", oldEventId, e); } } // remove the event if it exists if (event != null) { try { calendar.removeEvent( calendar.getEditEvent(event.getId(), CalendarService.EVENT_REMOVE_CALENDAR)); properties.remove(AssignmentConstants.NEW_ASSIGNMENT_DUE_DATE_SCHEDULED); properties.remove(ResourceProperties.PROP_ASSIGNMENT_DUEDATE_CALENDAR_EVENT_ID); } catch (PermissionException ee) { log.warn("Not allowed to remove calendar event for assignment = {}", assignment.getId()); } catch (InUseException ee) { log.warn("Someone else is editing calendar event for assignment = {}", assignment.getId()); } catch (IdUnusedException ee) { log.warn("Calendar event are in use for assignment = {} and event = {}", assignment.getId(), event.getId()); } } } } } private AnnouncementChannel getAnnouncementChannel(String contextId) { AnnouncementChannel channel = null; String channelId = serverConfigurationService.getString(announcementService.ANNOUNCEMENT_CHANNEL_PROPERTY, null); if (channelId == null) { channelId = announcementService.channelReference(contextId, siteService.MAIN_CONTAINER); try { channel = announcementService.getAnnouncementChannel(channelId); } catch (IdUnusedException e) { log.warn("No announcement channel found with id = {}", channelId); channel = null; } catch (PermissionException e) { log.warn("Current user not authorized to delete announcement with id = {}", channelId, e); channel = null; } } return channel; } private void removeAssociatedAnnouncementItem(AnnouncementChannel channel, Assignment assignment) { Map<String, String> properties = assignment.getProperties(); if (channel != null) { String openDateAnnounced = StringUtils .trimToNull(properties.get(AssignmentConstants.NEW_ASSIGNMENT_OPEN_DATE_ANNOUNCED)); String openDateAnnouncementId = StringUtils.trimToNull( properties.get(ResourceProperties.PROP_ASSIGNMENT_OPENDATE_ANNOUNCEMENT_MESSAGE_ID)); if (openDateAnnounced != null && openDateAnnouncementId != null) { try { channel.removeMessage(openDateAnnouncementId); } catch (PermissionException e) { log.warn("User does not have permission", e); } } } } // TODO zipSubmissions and zipGroupSubmissions should be combined private void zipSubmissions(String assignmentReference, String assignmentTitle, Assignment.GradeType gradeType, Assignment.SubmissionType typeOfSubmission, Iterator submissions, OutputStream outputStream, StringBuilder exceptionMessage, boolean withStudentSubmissionText, boolean withStudentSubmissionAttachment, boolean withGradeFile, boolean withFeedbackText, boolean withFeedbackComment, boolean withFeedbackAttachment, boolean withoutFolders, String gradeFileFormat, boolean includeNotSubmitted, String siteId) { ZipOutputStream out = null; boolean isAdditionalNotesEnabled = false; Site st = null; try { st = siteService.getSite(siteId); isAdditionalNotesEnabled = candidateDetailProvider != null && candidateDetailProvider.isAdditionalNotesEnabled(st); } catch (IdUnusedException e) { log.warn("Could not find site {} - isAdditionalNotesEnabled set to false", siteId); } try { out = new ZipOutputStream(outputStream); // create the folder structure - named after the assignment's title final String root = escapeInvalidCharsEntry(Validator.escapeZipEntry(assignmentTitle)) + Entity.SEPARATOR; final SpreadsheetExporter.Type type = SpreadsheetExporter.Type.valueOf(gradeFileFormat.toUpperCase()); final SpreadsheetExporter sheet = SpreadsheetExporter.getInstance(type, assignmentTitle, gradeType.toString(), getCsvSeparator()); String submittedText = ""; if (!submissions.hasNext()) { exceptionMessage.append("There is no submission yet. "); } if (isAdditionalNotesEnabled) { sheet.addHeader(resourceLoader.getString("grades.id"), resourceLoader.getString("grades.eid"), resourceLoader.getString("grades.lastname"), resourceLoader.getString("grades.firstname"), resourceLoader.getString("grades.grade"), resourceLoader.getString("grades.submissionTime"), resourceLoader.getString("grades.late"), resourceLoader.getString("gen.notes")); } else { sheet.addHeader(resourceLoader.getString("grades.id"), resourceLoader.getString("grades.eid"), resourceLoader.getString("grades.lastname"), resourceLoader.getString("grades.firstname"), resourceLoader.getString("grades.grade"), resourceLoader.getString("grades.submissionTime"), resourceLoader.getString("grades.late")); } // allow add assignment members final List<User> allowAddSubmissionUsers = allowAddSubmissionUsers(assignmentReference); // Create the ZIP file String caughtException = null; String caughtStackTrace = null; final StringBuilder submittersAdditionalNotesHtml = new StringBuilder(); while (submissions.hasNext()) { final AssignmentSubmission s = (AssignmentSubmission) submissions.next(); boolean isAnon = assignmentUsesAnonymousGrading(s.getAssignment()); //SAK-29314 added a new value where it's by default submitted but is marked when the user submits if ((s.getSubmitted() && s.getUserSubmission()) || includeNotSubmitted) { // get the submitter who submitted the submission see if the user is still in site final Optional<AssignmentSubmissionSubmitter> assignmentSubmitter = s.getSubmitters().stream() .findAny(); try { User u = null; if (assignmentSubmitter.isPresent()) { u = userDirectoryService.getUser(assignmentSubmitter.get().getSubmitter()); } if (allowAddSubmissionUsers.contains(u)) { String submittersName = root; final User[] submitters = s.getSubmitters().stream().map(p -> { try { return userDirectoryService.getUser(p.getSubmitter()); } catch (UserNotDefinedException e) { log.warn("User not found {}, {}", p.getSubmitter(), e.getMessage()); } return null; }).filter(Objects::nonNull).toArray(User[]::new); String submittersString = ""; for (int i = 0; i < submitters.length; i++) { if (i > 0) { submittersString = submittersString.concat("; "); } String fullName = submitters[i].getSortName(); // in case the user doesn't have first name or last name if (!fullName.contains(",")) { fullName = fullName.concat(","); } submittersString = submittersString.concat(fullName); // add the eid to the end of it to guarantee folder name uniqness // if user Eid contains non ascii characters, the user internal id will be used final String userEid = submitters[i].getEid(); final String candidateEid = escapeInvalidCharsEntry(userEid); if (candidateEid.equals(userEid)) { submittersString = submittersString + "(" + candidateEid + ")"; } else { submittersString = submittersString + "(" + submitters[i].getId() + ")"; } submittersString = escapeInvalidCharsEntry(submittersString); // Work out if submission is late. final String latenessStatus = whenSubmissionMade(s); log.debug("latenessStatus: " + latenessStatus); final String anonTitle = resourceLoader.getString("grading.anonymous.title"); final String fullAnonId = s.getId() + " " + anonTitle; String[] params = new String[7]; if (isAdditionalNotesEnabled && candidateDetailProvider != null) { final List<String> notes = candidateDetailProvider .getAdditionalNotes(submitters[i], st).orElse(new ArrayList<String>()); if (!notes.isEmpty()) { params = new String[notes.size() + 7]; System.arraycopy(notes.toArray(new String[notes.size()]), 0, params, 7, notes.size()); } } // SAK-17606 if (!isAnon) { log.debug("Zip user: " + submitters[i].toString()); params[0] = submitters[i].getDisplayId(); params[1] = submitters[i].getEid(); params[2] = submitters[i].getLastName(); params[3] = submitters[i].getFirstName(); params[4] = this.getGradeForSubmitter(s, submitters[i].getId()); if (s.getDateSubmitted() != null) { params[5] = s.getDateSubmitted().toString(); // TODO may need to be formatted } else { params[5] = ""; } params[6] = latenessStatus; } else { params[0] = fullAnonId; params[1] = fullAnonId; params[2] = anonTitle; params[3] = anonTitle; params[4] = this.getGradeForSubmitter(s, submitters[i].getId()); if (s.getDateSubmitted() != null) { params[5] = s.getDateSubmitted().toString(); // TODO may need to be formatted } else { params[5] = ""; } params[6] = latenessStatus; } sheet.addRow(params); } if (StringUtils.trimToNull(submittersString) != null) { submittersName = submittersName.concat(StringUtils.trimToNull(submittersString)); submittedText = s.getSubmittedText(); // SAK-17606 if (isAnon) { submittersString = s.getId() + " " + resourceLoader.getString("grading.anonymous.title"); submittersName = root + submittersString; } if (!withoutFolders) { submittersName = submittersName.concat("/"); } else { submittersName = submittersName.concat("_"); } // record submission timestamp if (!withoutFolders && s.getSubmitted() && s.getDateSubmitted() != null) { final String zipEntryName = submittersName + "timestamp.txt"; final String textEntryString = s.getDateSubmitted().toString(); createTextZipEntry(out, zipEntryName, textEntryString); } // create the folder structure - named after the submitter's name if (typeOfSubmission != Assignment.SubmissionType.ATTACHMENT_ONLY_ASSIGNMENT_SUBMISSION && typeOfSubmission != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION) { // include student submission text if (withStudentSubmissionText) { // create the text file only when a text submission is allowed final StringBuilder submittersNameString = new StringBuilder( submittersName); //remove folder name if Download All is without user folders if (!withoutFolders) { submittersNameString.append(submittersString); } final String zipEntryName = submittersNameString .append("_submissionText" + AssignmentConstants.ZIP_SUBMITTED_TEXT_FILE_TYPE) .toString(); createTextZipEntry(out, zipEntryName, submittedText); } // include student submission feedback text if (withFeedbackText) { // create a feedbackText file into zip final String zipEntryName = submittersName + "feedbackText.html"; final String textEntryString = s.getFeedbackText(); createTextZipEntry(out, zipEntryName, textEntryString); } } if (typeOfSubmission != Assignment.SubmissionType.TEXT_ONLY_ASSIGNMENT_SUBMISSION && typeOfSubmission != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION && withStudentSubmissionAttachment) { // include student submission attachment //remove "/" that creates a folder if Download All is without user folders String sSubAttachmentFolder = submittersName + resourceLoader.getString("stuviewsubm.submissatt");//jh + "/"; if (!withoutFolders) { // create a attachment folder for the submission attachments sSubAttachmentFolder = submittersName + resourceLoader.getString("stuviewsubm.submissatt") + "/"; sSubAttachmentFolder = escapeInvalidCharsEntry(sSubAttachmentFolder); final ZipEntry sSubAttachmentFolderEntry = new ZipEntry( sSubAttachmentFolder); out.putNextEntry(sSubAttachmentFolderEntry); } else { sSubAttachmentFolder += "_"; //submittersName = submittersName.concat("_"); } // add all submission attachment into the submission attachment folder zipAttachments(out, submittersName, sSubAttachmentFolder, s.getAttachments()); out.closeEntry(); } if (withFeedbackComment) { // the comments.txt file to show instructor's comments final String zipEntryName = submittersName + "comments" + AssignmentConstants.ZIP_COMMENT_FILE_TYPE; final String textEntryString = formattedText .encodeUnicode(s.getFeedbackComment()); createTextZipEntry(out, zipEntryName, textEntryString); } if (withFeedbackAttachment) { // create an attachment folder for the feedback attachments String feedbackSubAttachmentFolder = submittersName + resourceLoader.getString("download.feedback.attachment"); if (!withoutFolders) { feedbackSubAttachmentFolder += "/"; final ZipEntry feedbackSubAttachmentFolderEntry = new ZipEntry( feedbackSubAttachmentFolder); out.putNextEntry(feedbackSubAttachmentFolderEntry); } else { submittersName = submittersName.concat("_"); } // add all feedback attachment folder zipAttachments(out, submittersName, feedbackSubAttachmentFolder, s.getFeedbackAttachments()); out.closeEntry(); } } // if if (isAdditionalNotesEnabled && candidateDetailProvider != null) { final List<String> notes = candidateDetailProvider.getAdditionalNotes(u, st) .orElse(new ArrayList<String>()); if (!notes.isEmpty()) { final StringBuilder noteList = new StringBuilder("<ul>"); for (String note : notes) { noteList.append("<li>" + StringEscapeUtils.escapeHtml4(note) + "</li>"); } noteList.append("</ul>"); submittersAdditionalNotesHtml .append("<tr><td style='padding-right:10px;padding-left:10px'>" + submittersString + "</td><td style='padding-right:10px'>" + noteList + "</td></tr>"); } } } else { log.warn( "Can't add submission: {} to zip, missing the submittee or they are no longer allowed to submit in the site", s.getId()); } } catch (Exception e) { caughtException = e.toString(); if (log.isDebugEnabled()) { caughtStackTrace = ExceptionUtils.getStackTrace(e); } break; } } // if the user is still in site } // while -- there is submission if (caughtException == null) { // continue if (withGradeFile) { final ZipEntry gradesCSVEntry = new ZipEntry(root + "grades." + sheet.getFileExtension()); out.putNextEntry(gradesCSVEntry); sheet.write(out); out.closeEntry(); } if (isAdditionalNotesEnabled) { final ZipEntry additionalEntry = new ZipEntry( root + resourceLoader.getString("assignment.additional.notes.file.title") + ".html"); out.putNextEntry(additionalEntry); String htmlString = emailUtil.htmlPreamble("additionalnotes"); htmlString += "<h1>" + resourceLoader.getString("assignment.additional.notes.export.title") + "</h1>"; htmlString += "<div>" + resourceLoader.getString("assignment.additional.notes.export.header") + "</div><br/>"; htmlString += "<table border=\"1\" style=\"border-collapse:collapse;\"><tr><th>" + resourceLoader.getString("gen.student") + "</th><th>" + resourceLoader.getString("gen.notes") + "</th>" + submittersAdditionalNotesHtml + "</table>"; htmlString += "<br/><div>" + resourceLoader.getString("assignment.additional.notes.export.footer") + "</div>"; htmlString += emailUtil.htmlEnd(); log.debug("Additional information html: " + htmlString); final byte[] wes = htmlString.getBytes(); out.write(wes); additionalEntry.setSize(wes.length); out.closeEntry(); } } else { // log the error exceptionMessage.append(" Exception " + caughtException + " for creating submission zip file for assignment " + "\"" + assignmentTitle + "\"\n"); if (log.isDebugEnabled()) { exceptionMessage.append(caughtStackTrace); } } } catch (IOException e) { exceptionMessage.append("IOException for creating submission zip file for assignment " + "\"" + assignmentTitle + "\" exception: " + e + "\n"); } finally { // Complete the ZIP file if (out != null) { try { out.finish(); out.flush(); } catch (IOException e) { // tried } try { out.close(); } catch (IOException e) { // tried } } } } // TODO zipSubmissions and zipGroupSubmissions should be combined protected void zipGroupSubmissions(String assignmentReference, String assignmentTitle, String gradeTypeString, Assignment.SubmissionType typeOfSubmission, Iterator submissions, OutputStream outputStream, StringBuilder exceptionMessage, boolean withStudentSubmissionText, boolean withStudentSubmissionAttachment, boolean withGradeFile, boolean withFeedbackText, boolean withFeedbackComment, boolean withFeedbackAttachment, String gradeFileFormat, boolean includeNotSubmitted) { ZipOutputStream out = null; try { out = new ZipOutputStream(outputStream); // create the folder structure - named after the assignment's title final String root = escapeInvalidCharsEntry(Validator.escapeZipEntry(assignmentTitle)) + Entity.SEPARATOR; final SpreadsheetExporter.Type type = SpreadsheetExporter.Type.valueOf(gradeFileFormat.toUpperCase()); final SpreadsheetExporter sheet = SpreadsheetExporter.getInstance(type, assignmentTitle, gradeTypeString, getCsvSeparator()); if (!submissions.hasNext()) { exceptionMessage.append("There is no submission yet. "); } // Write the header sheet.addHeader(resourceLoader.getString("group"), resourceLoader.getString("grades.eid"), resourceLoader.getString("grades.members"), resourceLoader.getString("grades.grade"), resourceLoader.getString("grades.submissionTime"), resourceLoader.getString("grades.late")); // allow add assignment members allowAddSubmissionUsers(assignmentReference); // Create the ZIP file String caughtException = null; String caughtStackTrace = null; while (submissions.hasNext()) { final AssignmentSubmission s = (AssignmentSubmission) submissions.next(); log.debug(this + " ZIPGROUP " + (s == null ? "null" : s.getId())); //SAK-29314 added a new value where it's by default submitted but is marked when the user submits if ((s.getSubmitted() && s.getUserSubmission()) || includeNotSubmitted) { try { final StringBuilder submittersName = new StringBuilder(root); final User[] submitters = s.getSubmitters().stream().map(p -> { try { return userDirectoryService.getUser(p.getSubmitter()); } catch (UserNotDefinedException e) { log.warn("User not found {}", p.getSubmitter()); return null; } }).filter(Objects::nonNull).toArray(User[]::new); final String groupTitle = siteService.getSite(s.getAssignment().getContext()) .getGroup(s.getGroupId()).getTitle(); final StringBuilder submittersString = new StringBuilder(); final StringBuilder submitters2String = new StringBuilder(); for (int i = 0; i < submitters.length; i++) { if (i > 0) { submittersString.append("; "); submitters2String.append("; "); } String fullName = submitters[i].getSortName(); // in case the user doesn't have first name or last name if (fullName.indexOf(",") == -1) { fullName = fullName.concat(","); } submittersString.append(fullName); submitters2String.append(submitters[i].getDisplayName()); // add the eid to the end of it to guarantee folder name uniqness submittersString.append("(" + submitters[i].getEid() + ")"); } final String latenessStatus = whenSubmissionMade(s); final String gradeDisplay = getGradeDisplay(s.getGrade(), s.getAssignment().getTypeOfGrade(), s.getAssignment().getScaleFactor()); //Adding the row sheet.addRow(groupTitle, s.getGroupId(), submitters2String.toString(), gradeDisplay, s.getDateSubmitted() != null ? s.getDateSubmitted().toString() : StringUtils.EMPTY, latenessStatus); if (StringUtils.trimToNull(groupTitle) != null) { submittersName.append(StringUtils.trimToNull(groupTitle)).append(" (") .append(s.getGroupId()).append(")"); final String submittedText = s.getSubmittedText(); submittersName.append("/"); // record submission timestamp if (s.getSubmitted() && s.getDateSubmitted() != null) { createTextZipEntry(out, submittersName + "timestamp.txt", s.getDateSubmitted().toString()); } // create the folder structure - named after the submitter's name if (typeOfSubmission != Assignment.SubmissionType.ATTACHMENT_ONLY_ASSIGNMENT_SUBMISSION && typeOfSubmission != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION) { // include student submission text if (withStudentSubmissionText) { // create the text file only when a text submission is allowed final String zipEntryName = submittersName + groupTitle + "_submissionText" + AssignmentConstants.ZIP_SUBMITTED_TEXT_FILE_TYPE; createTextZipEntry(out, zipEntryName, submittedText); } // include student submission feedback text if (withFeedbackText) { // create a feedbackText file into zip createTextZipEntry(out, submittersName + "feedbackText.html", s.getFeedbackText()); } } if (typeOfSubmission != Assignment.SubmissionType.TEXT_ONLY_ASSIGNMENT_SUBMISSION && typeOfSubmission != Assignment.SubmissionType.NON_ELECTRONIC_ASSIGNMENT_SUBMISSION && withStudentSubmissionAttachment) { // include student submission attachment // create a attachment folder for the submission attachments final String sSubAttachmentFolder = submittersName + resourceLoader.getString("stuviewsubm.submissatt") + "/"; final ZipEntry sSubAttachmentFolderEntry = new ZipEntry(sSubAttachmentFolder); out.putNextEntry(sSubAttachmentFolderEntry); // add all submission attachment into the submission attachment folder zipAttachments(out, submittersName.toString(), sSubAttachmentFolder, s.getAttachments()); out.closeEntry(); } if (withFeedbackComment) { // the comments.txt file to show instructor's comments final String zipEntryName = submittersName + "comments" + AssignmentConstants.ZIP_COMMENT_FILE_TYPE; final String textEntryString = formattedText.encodeUnicode(s.getFeedbackComment()); createTextZipEntry(out, zipEntryName, textEntryString); } if (withFeedbackAttachment) { // create an attachment folder for the feedback attachments final String feedbackSubAttachmentFolder = submittersName + resourceLoader.getString("download.feedback.attachment") + "/"; final ZipEntry feedbackSubAttachmentFolderEntry = new ZipEntry( feedbackSubAttachmentFolder); out.putNextEntry(feedbackSubAttachmentFolderEntry); // add all feedback attachment folder zipAttachments(out, submittersName.toString(), feedbackSubAttachmentFolder, s.getFeedbackAttachments()); out.closeEntry(); } if (!submittersString.toString().trim().isEmpty()) { // the comments.txt file to show instructor's comments final String zipEntryName = submittersName + "members" + AssignmentConstants.ZIP_COMMENT_FILE_TYPE; final String textEntryString = formattedText .encodeUnicode(submittersString.toString()); createTextZipEntry(out, zipEntryName, textEntryString); } } // if } catch (Exception e) { caughtException = e.toString(); if (log.isDebugEnabled()) { caughtStackTrace = ExceptionUtils.getStackTrace(e); } break; } } // if the user is still in site } // while -- there is submission if (caughtException == null) { // continue if (withGradeFile) { final ZipEntry gradesCSVEntry = new ZipEntry(root + "grades." + sheet.getFileExtension()); out.putNextEntry(gradesCSVEntry); sheet.write(out); out.closeEntry(); } } else { // log the error exceptionMessage.append(" Exception " + caughtException + " for creating submission zip file for assignment " + "\"" + assignmentTitle + "\"\n"); if (log.isDebugEnabled()) { exceptionMessage.append(caughtStackTrace); } } } catch (IOException e) { exceptionMessage.append("IOException for creating submission zip file for assignment " + "\"" + assignmentTitle + "\" exception: " + e + "\n"); } finally { // Complete the ZIP file if (out != null) { try { out.finish(); out.flush(); } catch (IOException e) { // tried } try { out.close(); } catch (IOException e) { // tried } } } } private void createTextZipEntry(ZipOutputStream out, final String zipEntryName, final String textEntryString) throws IOException { final ZipEntry textEntry = new ZipEntry(zipEntryName); out.putNextEntry(textEntry); if (textEntryString != null) { final byte[] text = textEntryString.getBytes(); out.write(text); textEntry.setSize(text.length); } out.closeEntry(); } // TODO refactor this private String whenSubmissionMade(AssignmentSubmission s) { Instant dueTime = s.getAssignment().getDueDate(); Instant submittedTime = s.getDateSubmitted(); String latenessStatus; if (submittedTime == null) { latenessStatus = resourceLoader.getString("grades.lateness.unknown"); } else if (dueTime != null && submittedTime.isAfter(dueTime)) { latenessStatus = resourceLoader.getString("grades.lateness.late"); } else { latenessStatus = resourceLoader.getString("grades.lateness.ontime"); } return latenessStatus; } // TODO refactor this private void zipAttachments(ZipOutputStream out, String submittersName, String sSubAttachmentFolder, Collection<String> attachments) { int attachedUrlCount = 0; InputStream content = null; Map<String, Integer> done = new HashMap<>(); for (String r : attachments) { try { String attachId = removeReferencePrefix(r); ContentResource resource = contentHostingService.getResource(attachId); String contentType = resource.getContentType(); ResourceProperties props = resource.getProperties(); String displayName = props.getPropertyFormatted(props.getNamePropDisplayName()); displayName = escapeInvalidCharsEntry(displayName); // for URL content type, encode a redirect to the body URL if (contentType.equalsIgnoreCase(ResourceProperties.TYPE_URL)) { displayName = "attached_URL_" + attachedUrlCount; attachedUrlCount++; } // buffered stream input content = resource.streamContent(); byte data[] = new byte[1024 * 10]; BufferedInputStream bContent = null; try { bContent = new BufferedInputStream(content, data.length); String candidateName = sSubAttachmentFolder + displayName; String realName = null; Integer already = done.get(candidateName); if (already == null) { realName = candidateName; done.put(candidateName, 1); } else { String fileName = FilenameUtils.removeExtension(candidateName); String fileExt = FilenameUtils.getExtension(candidateName); if (!"".equals(fileExt.trim())) { fileExt = "." + fileExt; } realName = fileName + "+" + already + fileExt; done.put(candidateName, already + 1); } ZipEntry attachmentEntry = new ZipEntry(realName); out.putNextEntry(attachmentEntry); int bCount = -1; while ((bCount = bContent.read(data, 0, data.length)) != -1) { out.write(data, 0, bCount); } try { out.closeEntry(); // The zip entry need to be closed } catch (IOException ioException) { log.warn(":zipAttachments: problem closing zip entry " + ioException); } } catch (IllegalArgumentException iException) { log.warn(":zipAttachments: problem creating BufferedInputStream with content and length " + data.length + iException); } finally { if (bContent != null) { try { bContent.close(); // The BufferedInputStream needs to be closed } catch (IOException ioException) { log.warn(":zipAttachments: problem closing FileChannel " + ioException); } } } } catch (PermissionException e) { log.warn(" zipAttachments--PermissionException submittersName=" + submittersName + " attachment reference=" + r); } catch (IdUnusedException e) { log.warn(" zipAttachments--IdUnusedException submittersName=" + submittersName + " attachment reference=" + r); } catch (TypeException e) { log.warn(" zipAttachments--TypeException: submittersName=" + submittersName + " attachment reference=" + r); } catch (IOException e) { log.warn(" zipAttachments--IOException: Problem in creating the attachment file: submittersName=" + submittersName + " attachment reference=" + r + " error " + e); } catch (ServerOverloadException e) { log.warn(" zipAttachments--ServerOverloadException: submittersName=" + submittersName + " attachment reference=" + r); } finally { if (content != null) { try { content.close(); // The input stream needs to be closed } catch (IOException ioException) { log.warn(":zipAttachments: problem closing Inputstream content " + ioException); } } } } // for } @Transactional public void resetAssignment(Assignment assignment) { assignmentRepository.resetAssignment(assignment); } @Override @Transactional public void postReviewableSubmissionAttachments(AssignmentSubmission submission) { try { Optional<AssignmentSubmissionSubmitter> submitter = submission.getSubmitters().stream() .filter(AssignmentSubmissionSubmitter::getSubmittee).findFirst(); if (submitter.isPresent()) { Assignment assignment = submission.getAssignment(); String assignmentRef = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); List<ContentResource> resources = new ArrayList<>(); for (String attachment : submission.getAttachments()) { Reference attachmentRef = entityManager.newReference(attachment); try { ContentResource resource = contentHostingService.getResource(attachmentRef.getId()); if (contentReviewService.isAcceptableContent(resource)) { resources.add(resource); } } catch (TypeException e) { log.warn("Could not retrieve content resource: {}, {}", assignmentRef, e.getMessage()); } } try { contentReviewService.queueContent(submitter.get().getSubmitter(), assignment.getContext(), assignmentRef, resources); } catch (QueueException e) { log.warn("Could not queue submission: {} for review, {}", submission.getId(), e.getMessage()); } } } catch (IdUnusedException | PermissionException e) { log.warn("Could not locate submission: {}, {}", submission.getId(), e.getMessage()); } } @Override public String[] myToolIds() { return new String[] { "sakai.assignment", "sakai.assignment.grades" }; } @Override public Optional<List<String>> getTransferOptions() { return Optional.of(Arrays.asList(new String[] { EntityTransferrer.PUBLISH_OPTION })); } @Override @Transactional public void updateEntityReferences(String toContext, Map<String, String> transversalMap) { if (transversalMap != null && !transversalMap.isEmpty()) { Collection<Assignment> assignments = getAssignmentsForContext(toContext); for (Assignment assignment : assignments) { try { String msgBody = assignment.getInstructions(); StringBuffer msgBodyPreMigrate = new StringBuffer(msgBody); msgBody = linkMigrationHelper.migrateAllLinks(transversalMap.entrySet(), msgBody); // SecurityAdvisor securityAdvisor = new MySecurityAdvisor(sessionManager.getCurrentSessionUserId(), // new ArrayList<String>(Arrays.asList(SECURE_UPDATE_ASSIGNMENT_CONTENT)), // AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference()); try { if (!msgBody.equals(msgBodyPreMigrate.toString())) { // add permission to update assignment content // securityService.pushAdvisor(securityAdvisor); assignment.setInstructions(msgBody); updateAssignment(assignment); } } catch (Exception e) { // exception log.warn("UpdateEntityReference: cannot get assignment content for {}, {}", assignment.getId(), e.getMessage()); } finally { // remove advisor // securityService.popAdvisor(securityAdvisor); } } catch (Exception ee) { log.warn("UpdateEntityReference: remove Assignment and all references for {}, {}", assignment.getId(), ee.getMessage()); } } } } @Override @Transactional public Map<String, String> transferCopyEntities(String fromContext, String toContext, List<String> ids, List<String> transferOptions) { Map<String, String> transversalMap = new HashMap<>(); Collection<Assignment> assignments = getAssignmentsForContext(fromContext); for (Assignment oAssignment : assignments) { String oAssignmentId = oAssignment.getId(); String nAssignmentId = null; if (ids == null || ids.isEmpty() || ids.contains(oAssignmentId)) { try { Assignment nAssignment = addAssignment(toContext); nAssignmentId = nAssignment.getId(); nAssignment.setTitle(oAssignment.getTitle()); // replace all occurrence of old context with new context inside instruction text if (StringUtils.isNotBlank(oAssignment.getInstructions())) { nAssignment .setInstructions(oAssignment.getInstructions().replaceAll(fromContext, toContext)); } nAssignment.setTypeOfGrade(oAssignment.getTypeOfGrade()); nAssignment.setTypeOfSubmission(oAssignment.getTypeOfSubmission()); // User supplied publish option takes precedence, then property, then source. if (transferOptions != null && transferOptions.contains(EntityTransferrer.PUBLISH_OPTION)) { nAssignment.setDraft(false); } else if (serverConfigurationService.getBoolean("import.importAsDraft", true)) { nAssignment.setDraft(true); } else { nAssignment.setDraft(oAssignment.getDraft()); } nAssignment.setCloseDate(oAssignment.getCloseDate()); nAssignment.setDropDeadDate(oAssignment.getDropDeadDate()); nAssignment.setDueDate(oAssignment.getDueDate()); nAssignment.setOpenDate(oAssignment.getOpenDate()); nAssignment.setHideDueDate(oAssignment.getHideDueDate()); nAssignment.setPosition(oAssignment.getPosition()); nAssignment.setAllowAttachments(oAssignment.getAllowAttachments()); nAssignment.setHonorPledge(oAssignment.getHonorPledge()); nAssignment.setIndividuallyGraded(oAssignment.getIndividuallyGraded()); nAssignment.setMaxGradePoint(oAssignment.getMaxGradePoint()); nAssignment.setScaleFactor(oAssignment.getScaleFactor()); nAssignment.setReleaseGrades(oAssignment.getReleaseGrades()); // group assignment if (oAssignment.getTypeOfAccess() == GROUP) { nAssignment.setTypeOfAccess(GROUP); Site oSite = siteService.getSite(oAssignment.getContext()); Site nSite = siteService.getSite(nAssignment.getContext()); boolean siteChanged = false; Collection<Group> nGroups = nSite.getGroups(); for (String groupId : oAssignment.getGroups()) { Group oGroup = oSite.getGroup(groupId); Optional<Group> existingGroup = nGroups.stream() .filter(g -> StringUtils.equals(g.getTitle(), oGroup.getTitle())).findAny(); Group nGroup; if (existingGroup.isPresent()) { // found a matching group nGroup = existingGroup.get(); } else { // create group nGroup = nSite.addGroup(); nGroup.setTitle(oGroup.getTitle()); nGroup.setDescription(oGroup.getDescription()); nGroup.getProperties().addProperty("group_prop_wsetup_created", Boolean.TRUE.toString()); siteChanged = true; } nAssignment.getGroups().add(nGroup.getReference()); } if (siteChanged) siteService.save(nSite); nAssignment.setIsGroup(oAssignment.getIsGroup()); } // review service nAssignment.setContentReview(oAssignment.getContentReview()); // attachments Set<String> oAttachments = oAssignment.getAttachments(); List<Reference> nAttachments = entityManager.newReferenceList(); for (String oAttachment : oAttachments) { Reference oReference = entityManager.newReference(oAttachment); String oAttachmentId = oReference.getId(); // transfer attachment, replace the context string if necessary and add new attachment String nReference = transferAttachment(fromContext, toContext, oAttachmentId); nAssignment.getAttachments().add(nReference); } // peer review nAssignment.setAllowPeerAssessment(oAssignment.getAllowPeerAssessment()); nAssignment.setPeerAssessmentAnonEval(oAssignment.getPeerAssessmentAnonEval()); nAssignment.setPeerAssessmentInstructions(oAssignment.getPeerAssessmentInstructions()); nAssignment.setPeerAssessmentNumberReviews(oAssignment.getPeerAssessmentNumberReviews()); nAssignment.setPeerAssessmentStudentReview(oAssignment.getPeerAssessmentStudentReview()); nAssignment.setPeerAssessmentPeriodDate(oAssignment.getPeerAssessmentPeriodDate()); if (nAssignment.getPeerAssessmentPeriodDate() == null && nAssignment.getCloseDate() != null) { // set the peer period time to be 10 mins after accept until date Instant tenMinutesAfterCloseDate = Instant .from(nAssignment.getCloseDate().plus(Duration.ofMinutes(10))); nAssignment.setPeerAssessmentPeriodDate(tenMinutesAfterCloseDate); } // properties Map<String, String> nProperties = nAssignment.getProperties(); nProperties.putAll(oAssignment.getProperties()); // remove the link btw assignment and announcement item. One can announce the open date afterwards nProperties.remove(ResourceProperties.NEW_ASSIGNMENT_CHECK_AUTO_ANNOUNCE); nProperties.remove(AssignmentConstants.NEW_ASSIGNMENT_OPEN_DATE_ANNOUNCED); nProperties.remove(ResourceProperties.PROP_ASSIGNMENT_OPENDATE_ANNOUNCEMENT_MESSAGE_ID); // remove the link btw assignment and calendar item. One can add the due date to calendar afterwards nProperties.remove(ResourceProperties.NEW_ASSIGNMENT_CHECK_ADD_DUE_DATE); nProperties.remove(AssignmentConstants.NEW_ASSIGNMENT_DUE_DATE_SCHEDULED); nProperties.remove(ResourceProperties.PROP_ASSIGNMENT_DUEDATE_CALENDAR_EVENT_ID); if (!nAssignment.getDraft()) { Map<String, String> oProperties = oAssignment.getProperties(); String fromCalendarEventId = oProperties .get(ResourceProperties.PROP_ASSIGNMENT_DUEDATE_CALENDAR_EVENT_ID); if (fromCalendarEventId != null) { String fromCalendarId = calendarService.calendarReference(oAssignment.getContext(), SiteService.MAIN_CONTAINER); Calendar fromCalendar = calendarService.getCalendar(fromCalendarId); CalendarEvent fromEvent = fromCalendar.getEvent(fromCalendarEventId); String toCalendarId = calendarService.calendarReference(nAssignment.getContext(), SiteService.MAIN_CONTAINER); Calendar toCalendar = null; try { toCalendar = calendarService.getCalendar(toCalendarId); } catch (IdUnusedException iue) { calendarService.commitCalendar(calendarService.addCalendar(toCalendarId)); toCalendar = calendarService.getCalendar(toCalendarId); } String fromDisplayName = fromEvent.getDisplayName(); CalendarEvent toCalendarEvent = toCalendar.addEvent(fromEvent.getRange(), fromEvent.getDisplayName(), fromEvent.getDescription(), fromEvent.getType(), fromEvent.getLocation(), fromEvent.getAccess(), fromEvent.getGroups(), fromEvent.getAttachments()); nProperties.put(ResourceProperties.PROP_ASSIGNMENT_DUEDATE_CALENDAR_EVENT_ID, toCalendarEvent.getId()); nProperties.put(AssignmentConstants.NEW_ASSIGNMENT_DUE_DATE_SCHEDULED, Boolean.TRUE.toString()); nProperties.put(ResourceProperties.NEW_ASSIGNMENT_CHECK_ADD_DUE_DATE, Boolean.TRUE.toString()); } String openDateAnnounced = StringUtils.trimToNull( oProperties.get(AssignmentConstants.NEW_ASSIGNMENT_OPEN_DATE_ANNOUNCED)); String fromAnnouncementId = StringUtils.trimToNull(oProperties .get(ResourceProperties.PROP_ASSIGNMENT_OPENDATE_ANNOUNCEMENT_MESSAGE_ID)); AnnouncementChannel fromChannel = getAnnouncementChannel(oAssignment.getContext()); if (fromChannel != null && fromAnnouncementId != null) { AnnouncementMessage fromAnnouncement = fromChannel .getAnnouncementMessage(fromAnnouncementId); AnnouncementChannel toChannel = getAnnouncementChannel(nAssignment.getContext()); if (toChannel == null) { // Create the announcement channel String toChannelId = announcementService.channelReference(nAssignment.getContext(), siteService.MAIN_CONTAINER); announcementService .commitChannel(announcementService.addAnnouncementChannel(toChannelId)); toChannel = getAnnouncementChannel(nAssignment.getContext()); } AnnouncementMessage toAnnouncement = toChannel.addAnnouncementMessage( fromAnnouncement.getAnnouncementHeader().getSubject(), fromAnnouncement.getAnnouncementHeader().getDraft(), fromAnnouncement.getAnnouncementHeader().getAttachments(), fromAnnouncement.getBody()); nProperties.put(AssignmentConstants.NEW_ASSIGNMENT_OPEN_DATE_ANNOUNCED, Boolean.TRUE.toString()); nProperties.put(ResourceProperties.PROP_ASSIGNMENT_OPENDATE_ANNOUNCEMENT_MESSAGE_ID, toAnnouncement.getId()); nProperties.put(ResourceProperties.NEW_ASSIGNMENT_CHECK_AUTO_ANNOUNCE, Boolean.TRUE.toString()); } } // gradebook-integration link String associatedGradebookAssignment = nProperties .get(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT); if (StringUtils.isBlank(associatedGradebookAssignment)) { // if the association property is empty then set gradebook integration to not integrated nProperties.put(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, GRADEBOOK_INTEGRATION_NO); } else { // see if the old assignment's associated gradebook item is an internal gradebook entry or externally defined boolean isExternalAssignmentDefined = gradebookExternalAssessmentService .isExternalAssignmentDefined(oAssignment.getContext(), associatedGradebookAssignment); if (isExternalAssignmentDefined) { if (!nAssignment.getDraft()) { String gbUid = nAssignment.getContext(); if (!gradebookFrameworkService.isGradebookDefined(gbUid)) { gradebookFrameworkService.addGradebook(gbUid, gbUid); } // This assignment has been published, make sure the associated gb item is available org.sakaiproject.service.gradebook.shared.Assignment gbAssignment = gradebookService .getAssignmentByNameOrId(nAssignment.getContext(), associatedGradebookAssignment); if (gbAssignment == null) { // The associated gb item hasn't been created here yet. gbAssignment = gradebookService.getExternalAssignment(oAssignment.getContext(), associatedGradebookAssignment); Optional<Long> categoryId = createCategoryForGbAssignmentIfNecessary( gbAssignment, oAssignment.getContext(), nAssignment.getContext()); String assignmentRef = AssignmentReferenceReckoner.reckoner() .assignment(nAssignment).reckon().getReference(); gradebookExternalAssessmentService.addExternalAssessment( nAssignment.getContext(), assignmentRef, null, nAssignment.getTitle(), nAssignment.getMaxGradePoint() / (double) nAssignment.getScaleFactor(), Date.from(nAssignment.getDueDate()), this.getToolTitle(), null, false, categoryId.isPresent() ? categoryId.get() : null); nProperties.put(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT, assignmentRef); } } else { // if this is an external defined (came from assignment) // mark the link as "add to gradebook" for the new imported assignment, since the assignment is still of draft state // later when user posts the assignment, the corresponding assignment will be created in gradebook. nProperties.remove(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT); nProperties.put(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, GRADEBOOK_INTEGRATION_ADD); } } else { // If this is an internal gradebook item then it should be associated with the assignment try { org.sakaiproject.service.gradebook.shared.Assignment gbAssignment = gradebookService .getAssignmentByNameOrId(nAssignment.getContext(), associatedGradebookAssignment); if (gbAssignment == null) { if (!nAssignment.getDraft()) { // The target gb item doesn't exist and we're in publish mode, so copy it over. gbAssignment = gradebookService.getAssignmentByNameOrId( oAssignment.getContext(), associatedGradebookAssignment); gbAssignment.setId(null); Optional<Long> categoryId = createCategoryForGbAssignmentIfNecessary( gbAssignment, oAssignment.getContext(), nAssignment.getContext()); if (categoryId.isPresent()) { gbAssignment.setCategoryId(categoryId.get()); } gradebookService.addAssignment(nAssignment.getContext(), gbAssignment); nProperties.put(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT, gbAssignment.getName()); } else { nProperties.put(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT, AssignmentReferenceReckoner.reckoner().assignment(nAssignment) .reckon().getReference()); nProperties.put(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, GRADEBOOK_INTEGRATION_ADD); } } else { // migrate to gradebook assignment id (vs title) associatedGradebookAssignment = gbAssignment.getId().toString(); nProperties.put(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, GRADEBOOK_INTEGRATION_ASSOCIATE); nProperties.put(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT, associatedGradebookAssignment); } } catch (AssessmentNotFoundException anfe) { log.info( "While importing assignment {} the associated gradebook item {} was missing, " + "switching assignment linkage to added by assignments", nAssignmentId, associatedGradebookAssignment); } } } updateAssignment(nAssignment); transversalMap.put("assignment/" + oAssignmentId, "assignment/" + nAssignmentId); log.info("Old assignment id: {} - new assignment id: {}", oAssignmentId, nAssignmentId); try { if (taggingManager.isTaggable()) { for (TaggingProvider provider : taggingManager.getProviders()) { provider.transferCopyTags(assignmentActivityProducer.getActivity(oAssignment), assignmentActivityProducer.getActivity(nAssignment)); } } } catch (PermissionException pe) { log.error("{} oAssignmentId={} nAssignmentId={}", pe.toString(), oAssignmentId, nAssignmentId); } // Import supplementary items if they are present in the assignment to be imported // Model Answer AssignmentModelAnswerItem oModelAnswerItem = assignmentSupplementItemService .getModelAnswer(oAssignmentId); if (oModelAnswerItem != null) { AssignmentModelAnswerItem nModelAnswerItem = assignmentSupplementItemService .newModelAnswer(); assignmentSupplementItemService.saveModelAnswer(nModelAnswerItem); nModelAnswerItem.setAssignmentId(nAssignmentId); nModelAnswerItem.setText(oModelAnswerItem.getText()); nModelAnswerItem.setShowTo(oModelAnswerItem.getShowTo()); Set<AssignmentSupplementItemAttachment> oModelAnswerItemAttachments = oModelAnswerItem .getAttachmentSet(); Set<AssignmentSupplementItemAttachment> nModelAnswerItemAttachments = new HashSet<>(); for (AssignmentSupplementItemAttachment oAttachment : oModelAnswerItemAttachments) { AssignmentSupplementItemAttachment nAttachment = assignmentSupplementItemService .newAttachment(); // New attachment creation String nAttachmentId = transferAttachment(fromContext, toContext, removeReferencePrefix(oAttachment.getAttachmentId())); if (StringUtils.isNotEmpty(nAttachmentId)) { nAttachment.setAssignmentSupplementItemWithAttachment(nModelAnswerItem); nAttachment.setAttachmentId(nAttachmentId); assignmentSupplementItemService.saveAttachment(nAttachment); nModelAnswerItemAttachments.add(nAttachment); } } nModelAnswerItem.setAttachmentSet(nModelAnswerItemAttachments); assignmentSupplementItemService.saveModelAnswer(nModelAnswerItem); } // Private Note AssignmentNoteItem oNoteItem = assignmentSupplementItemService.getNoteItem(oAssignmentId); if (oNoteItem != null) { AssignmentNoteItem nNoteItem = assignmentSupplementItemService.newNoteItem(); //assignmentSupplementItemService.saveNoteItem(nNoteItem); nNoteItem.setAssignmentId(nAssignment.getId()); nNoteItem.setNote(oNoteItem.getNote()); nNoteItem.setShareWith(oNoteItem.getShareWith()); nNoteItem.setCreatorId(userDirectoryService.getCurrentUser().getId()); assignmentSupplementItemService.saveNoteItem(nNoteItem); } // All Purpose AssignmentAllPurposeItem oAllPurposeItem = assignmentSupplementItemService .getAllPurposeItem(oAssignmentId); if (oAllPurposeItem != null) { AssignmentAllPurposeItem nAllPurposeItem = assignmentSupplementItemService .newAllPurposeItem(); assignmentSupplementItemService.saveAllPurposeItem(nAllPurposeItem); nAllPurposeItem.setAssignmentId(nAssignment.getId()); nAllPurposeItem.setTitle(oAllPurposeItem.getTitle()); nAllPurposeItem.setText(oAllPurposeItem.getText()); nAllPurposeItem.setHide(oAllPurposeItem.getHide()); nAllPurposeItem.setReleaseDate(null); nAllPurposeItem.setRetractDate(null); Set<AssignmentSupplementItemAttachment> oAllPurposeItemAttachments = oAllPurposeItem .getAttachmentSet(); Set<AssignmentSupplementItemAttachment> nAllPurposeItemAttachments = new HashSet<>(); for (AssignmentSupplementItemAttachment oAttachment : oAllPurposeItemAttachments) { AssignmentSupplementItemAttachment nAttachment = assignmentSupplementItemService .newAttachment(); // New attachment creation String nAttachId = transferAttachment(fromContext, toContext, removeReferencePrefix(oAttachment.getAttachmentId())); if (StringUtils.isNotEmpty(nAttachId)) { nAttachment.setAssignmentSupplementItemWithAttachment(nAllPurposeItem); nAttachment.setAttachmentId(nAttachId); assignmentSupplementItemService.saveAttachment(nAttachment); nAllPurposeItemAttachments.add(nAttachment); } } nAllPurposeItem.setAttachmentSet(nAllPurposeItemAttachments); assignmentSupplementItemService.cleanAllPurposeItemAccess(nAllPurposeItem); Set<AssignmentAllPurposeItemAccess> accessSet = new HashSet<>(); AssignmentAllPurposeItemAccess access = assignmentSupplementItemService .newAllPurposeItemAccess(); access.setAccess(userDirectoryService.getCurrentUser().getId()); access.setAssignmentAllPurposeItem(nAllPurposeItem); assignmentSupplementItemService.saveAllPurposeItemAccess(access); accessSet.add(access); nAllPurposeItem.setAccessSet(accessSet); assignmentSupplementItemService.saveAllPurposeItem(nAllPurposeItem); } } catch (Exception e) { log.error("{} oAssignmentId={} nAssignmentId={}", e.toString(), oAssignmentId, nAssignmentId); } } } return transversalMap; } @Override @Transactional public Map<String, String> transferCopyEntities(String fromContext, String toContext, List<String> ids, List<String> transferOptions, boolean cleanup) { Map<String, String> transversalMap = new HashMap<>(); try { if (cleanup) { Collection<Assignment> assignments = getAssignmentsForContext(toContext); for (Assignment assignment : assignments) { String assignmentId = assignment.getId(); try { // remove this assignment with all its associated items deleteAssignmentAndAllReferences(assignment); } catch (Exception e) { log.warn("Remove assignment and all references for {}, {}", assignmentId, e.getMessage()); } } } transversalMap.putAll(transferCopyEntities(fromContext, toContext, ids, transferOptions)); } catch (Exception e) { log.info("End removing Assignmentt data {}", e.getMessage()); } return transversalMap; } private String transferAttachment(String fromContext, String toContext, String oAttachmentId) { String reference = ""; String nAttachmentId = oAttachmentId.replaceAll(fromContext, toContext); try { ContentResource attachment = contentHostingService.getResource(nAttachmentId); reference = attachment.getReference(); } catch (IdUnusedException iue) { try { ContentResource oAttachment = contentHostingService.getResource(oAttachmentId); try (InputStream content = new ByteArrayInputStream(oAttachment.getContent())) { if (contentHostingService.isAttachmentResource(nAttachmentId)) { // add the new resource into attachment collection area ContentResource attachment = contentHostingService.addAttachmentResource( Validator.escapeResourceName(oAttachment.getProperties() .getProperty(ResourceProperties.PROP_DISPLAY_NAME)), toContext, toolManager.getTool("sakai.assignment.grades").getTitle(), oAttachment.getContentType(), content, oAttachment.getProperties()); reference = attachment.getReference(); } else { // add the new resource into resource area ContentResource attachment = contentHostingService.addResource( Validator.escapeResourceName(oAttachment.getProperties() .getProperty(ResourceProperties.PROP_DISPLAY_NAME)), toContext, 1, oAttachment.getContentType(), content, oAttachment.getProperties(), Collections.emptyList(), false, null, null, NotificationService.NOTI_NONE); reference = attachment.getReference(); } } catch (Exception e) { // if the new resource cannot be added log.warn("Cannot add new attachment with id = {}, {}", nAttachmentId, e.getMessage()); } } catch (Exception e) { // if cannot find the original attachment, do nothing. log.warn("Cannot get the original attachment with id = {}, {}", oAttachmentId, e.getMessage()); } } catch (Exception e) { log.warn("Could not get the new attachment with id = {}, {}", nAttachmentId, e.getMessage()); } return reference; } private LRS_Statement getStatementForAssignmentGraded(String reference, Assignment a, AssignmentSubmission s, User studentUser) { LRS_Actor instructor = learningResourceStoreService.getActor(sessionManager.getCurrentSessionUserId()); LRS_Verb verb = new LRS_Verb(SAKAI_VERB.scored); LRS_Object lrsObject = new LRS_Object(serverConfigurationService.getPortalUrl() + reference, "received-grade-assignment"); Map<String, String> nameMap = new HashMap<>(); nameMap.put("en-US", "User received a grade"); lrsObject.setActivityName(nameMap); Map<String, String> descMap = new HashMap<>(); String resubmissionNumber = StringUtils .defaultString(s.getProperties().get(AssignmentConstants.ALLOW_RESUBMIT_NUMBER), "0"); descMap.put("en-US", "User received a grade for their assginment: " + a.getTitle() + "; Submission #: " + resubmissionNumber); lrsObject.setDescription(descMap); LRS_Actor student = learningResourceStoreService.getActor(studentUser.getId()); student.setName(studentUser.getDisplayName()); return new LRS_Statement(student, verb, lrsObject, getLRS_Result(a, s, true), null); } private LRS_Result getLRS_Result(Assignment a, AssignmentSubmission s, boolean completed) { LRS_Result result = null; String decSeparator = formattedText.getDecimalSeparator(); // gradeDisplay ready to conversion to Float 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.setCompletion(completed); } else { result = new LRS_Result(completed); result.setGrade(getGradeDisplay(s.getGrade(), a.getTypeOfGrade(), a.getScaleFactor())); } return result; } private LRS_Statement getStatementForUnsubmittedAssignmentGraded(String reference, Assignment a, AssignmentSubmission s, User studentUser) { LRS_Actor instructor = learningResourceStoreService.getActor(sessionManager.getCurrentSessionUserId()); LRS_Verb verb = new LRS_Verb(SAKAI_VERB.scored); LRS_Object lrsObject = new LRS_Object(serverConfigurationService.getAccessUrl() + reference, "received-grade-unsubmitted-assignment"); Map<String, String> nameMap = new HashMap<>(); nameMap.put("en-US", "User received a grade"); lrsObject.setActivityName(nameMap); Map<String, String> descMap = new HashMap<>(); descMap.put("en-US", "User received a grade for an unsubmitted assginment: " + a.getTitle()); lrsObject.setDescription(descMap); LRS_Actor student = learningResourceStoreService.getActor(studentUser.getId()); student.setName(studentUser.getDisplayName()); return new LRS_Statement(student, verb, lrsObject, getLRS_Result(a, s, false), null); } private LRS_Statement getStatementForSubmitAssignment(String reference, String accessUrl, String assignmentName) { LRS_Actor actor = learningResourceStoreService.getActor(sessionManager.getCurrentSessionUserId()); LRS_Verb verb = new LRS_Verb(SAKAI_VERB.attempted); LRS_Object lrsObject = new LRS_Object(accessUrl + reference, "submit-assignment"); HashMap<String, String> nameMap = new HashMap<String, String>(); nameMap.put("en-US", "User submitted an assignment"); lrsObject.setActivityName(nameMap); // Add description HashMap<String, String> descMap = new HashMap<String, String>(); descMap.put("en-US", "User submitted an assignment: " + assignmentName); lrsObject.setDescription(descMap); return new LRS_Statement(actor, verb, lrsObject); } private void sendGradeReleaseNotification(AssignmentSubmission submission) { Set<User> filteredUsers; Assignment assignment = submission.getAssignment(); Map<String, String> assignmentProperties = assignment.getProperties(); String siteId = assignment.getContext(); String resubmitNumber = submission.getProperties().get(AssignmentConstants.ALLOW_RESUBMIT_NUMBER); boolean released = BooleanUtils.toBoolean(submission.getGradeReleased()); Set<String> submitterIds = submission.getSubmitters().stream() .map(AssignmentSubmissionSubmitter::getSubmitter).collect(Collectors.toSet()); try { Set<String> siteUsers = siteService.getSite(siteId).getUsers(); filteredUsers = submitterIds.stream().filter(siteUsers::contains).map(id -> { try { return userDirectoryService.getUser(id); } catch (UserNotDefinedException e) { log.warn("Could not find user with id = {}, {}", id, e.getMessage()); } return null; }).filter(Objects::nonNull).collect(Collectors.toSet()); } catch (IdUnusedException e) { log.warn("Site ({}) not found.", siteId); return; } if (released && StringUtils.equals(AssignmentConstants.ASSIGNMENT_RELEASEGRADE_NOTIFICATION_EACH, assignmentProperties.get(AssignmentConstants.ASSIGNMENT_RELEASEGRADE_NOTIFICATION_VALUE))) { // send email to every submitters if (!filteredUsers.isEmpty()) { // send the message immidiately emailService.sendToUsers(filteredUsers, emailUtil.getHeaders(null, "releasegrade"), emailUtil.getNotificationMessage(submission, "releasegrade")); } } if (StringUtils.isNotBlank(resubmitNumber) && StringUtils.equals(AssignmentConstants.ASSIGNMENT_RELEASERESUBMISSION_NOTIFICATION_EACH, assignmentProperties.get(AssignmentConstants.ASSIGNMENT_RELEASEGRADE_NOTIFICATION_VALUE))) { // send email to every submitters if (!filteredUsers.isEmpty()) { // send the message immidiately emailService.sendToUsers(filteredUsers, emailUtil.getHeaders(null, "releaseresumbission"), emailUtil.getNotificationMessage(submission, "releaseresumbission")); } } } private void notificationToInstructors(AssignmentSubmission submission, Assignment assignment) { String notiOption = assignment.getProperties() .get(AssignmentConstants.ASSIGNMENT_INSTRUCTOR_NOTIFICATIONS_VALUE); if (notiOption != null && !notiOption.equals(AssignmentConstants.ASSIGNMENT_INSTRUCTOR_NOTIFICATIONS_NONE)) { // need to send notification email String context = assignment.getContext(); String assignmentReference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); // compare the list of users with the receive.notifications and list of users who can actually grade this assignment List<User> receivers = allowReceiveSubmissionNotificationUsers(context); List allowGradeAssignmentUsers = allowGradeAssignmentUsers(assignmentReference); receivers.retainAll(allowGradeAssignmentUsers); String messageBody = emailUtil.getNotificationMessage(submission, "submission"); if (notiOption.equals(AssignmentConstants.ASSIGNMENT_INSTRUCTOR_NOTIFICATIONS_EACH)) { // send the message immediately emailService.sendToUsers(receivers, emailUtil.getHeaders(null, "submission"), messageBody); } else if (notiOption.equals(AssignmentConstants.ASSIGNMENT_INSTRUCTOR_NOTIFICATIONS_DIGEST)) { // just send plain/text version for now String digestMsgBody = emailUtil.getPlainTextNotificationMessage(submission, "submission"); // digest the message to each user for (User user : receivers) { digestService.digest(user.getId(), emailUtil.getSubject("submission"), digestMsgBody); } } } } private void notificationToStudent(AssignmentSubmission submission) { if (serverConfigurationService.getBoolean("assignment.submission.confirmation.email", true)) { Set<String> submitterIds = submission.getSubmitters().stream() .map(AssignmentSubmissionSubmitter::getSubmitter).collect(Collectors.toSet()); Set<User> users = submitterIds.stream().map(id -> { try { return userDirectoryService.getUser(id); } catch (UserNotDefinedException e) { log.warn("Could not find user with id = {}, {}", id, e.getMessage()); } return null; }).filter(Objects::nonNull).collect(Collectors.toSet()); emailService.sendToUsers(users, emailUtil.getHeaders(null, "submission"), emailUtil.getNotificationMessage(submission, "submission")); } } @Override public String getUsersLocalDateTimeString(Instant date) { if (date == null) return ""; ZoneId zone = userTimeService.getLocalTimeZone().toZoneId(); DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT) .withZone(zone).withLocale(resourceLoader.getLocale()); return df.format(date); } private String removeReferencePrefix(String referenceId) { if (referenceId.startsWith(REF_PREFIX)) { referenceId = referenceId.replaceFirst(REF_PREFIX, ""); } return referenceId; } @Override @Transactional public List<ContentReviewResult> getContentReviewResults(AssignmentSubmission s) { ArrayList<ContentReviewResult> reviewResults = new ArrayList<ContentReviewResult>(); //get all the attachments for this submission and populate the reviewResults List<ContentResource> contentResources = getAllAcceptableAttachments(s); for (ContentResource cr : contentResources) { ContentReviewResult reviewResult = new ContentReviewResult(); reviewResult.setContentResource(cr); ContentReviewItem cri = contentReviewService.getContentReviewItemByContentId(cr.getId()); if (cri == null) { log.warn("Retrieved null ContentReviewItem for content " + cr.getId()); continue; } reviewResult.setContentReviewItem(cri); AssignmentReferenceReckoner.AssignmentReference referenceReckoner = AssignmentReferenceReckoner .reckoner().assignment(s.getAssignment()).reckon(); reviewResult.setReviewReport(getReviewReport(cr, referenceReckoner.getReference())); String iconUrl = getReviewIconCssClass(reviewResult); reviewResult.setReviewIconCssClass(iconUrl); reviewResult.setReviewError(getReviewError(reviewResult)); if ("true".equals(reviewResult.isInline())) { reviewResults.add(0, reviewResult); } else { reviewResults.add(reviewResult); } } return reviewResults; } @Override public boolean isContentReviewVisibleForSubmission(AssignmentSubmission submission) { if (submission == null) { throw new IllegalArgumentException( "isContentReviewVisibleForSubmission invoked with submission = null"); } Assignment assignment = submission.getAssignment(); String assignmentReference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon() .getReference(); boolean hasInstructorPermission = allowGradeSubmission(assignmentReference); boolean hasStudentPermission = false; // If we have instructor permission, we can short circuit past student checks // Student checks: ensure the assignment is configured to allow students to view reports, and that the user is permitted to get the specified submission if (!hasInstructorPermission && Boolean.valueOf(assignment.getProperties().get("s_view_report"))) { String submissionReference = AssignmentReferenceReckoner.reckoner().submission(submission).reckon() .getReference(); hasStudentPermission = allowGetSubmission(submissionReference); } // Content Review results should be visible iff the user has permission and the submission is not a draft return (hasInstructorPermission || hasStudentPermission) && submission.getSubmitted() && submission.getDateSubmitted() != null; } /** * Gets all attachments in the submission that are acceptable to the content review service */ private List<ContentResource> getAllAcceptableAttachments(AssignmentSubmission s) { List<ContentResource> attachments = new ArrayList<>(); for (String attachment : s.getAttachments()) { Reference attachmentRef = entityManager.newReference(attachment); try { ContentResource resource = contentHostingService.getResource(attachmentRef.getId()); if (contentReviewService.isAcceptableContent(resource)) { attachments.add(resource); } } catch (Exception e) { log.warn(":getAllAcceptableAttachments() {} ", e.getMessage()); } } return attachments; } private String getReviewIconCssClass(ContentReviewResult reviewResult) { if (reviewResult == null) { log.debug("{} getReviewIconCssClass(ContentResource, int) called with reviewResult == null", reviewResult.getContentResource().getId()); return null; } Long status = reviewResult.getStatus(); String reviewReport = reviewResult.getReviewReport(); String iconCssClass = null; if (!"Error".equals(reviewReport)) { iconCssClass = contentReviewService.getIconCssClassforScore(reviewResult.getReviewScore(), reviewResult.getContentResource().getId()); } else if (ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_AWAITING_REPORT_CODE.equals(status) || ContentReviewConstants.CONTENT_REVIEW_NOT_SUBMITTED_CODE.equals(status)) { iconCssClass = "contentReviewIconPending"; } else { iconCssClass = "contentReviewIconWarn"; } return iconCssClass; } private String getReviewReport(ContentResource cr, String assignmentReference) { if (cr == null) { log.debug("getReviewReport(ContentResource) called with cr == null"/*, this.getId()*/); return "Error"; } try { String contentId = cr.getId(); if (allowGradeSubmission(assignmentReference)) { return contentReviewService.getReviewReportInstructor(contentId, assignmentReference, userDirectoryService.getCurrentUser().getId()); } else { return contentReviewService.getReviewReportStudent(contentId, assignmentReference, userDirectoryService.getCurrentUser().getId()); } } catch (Exception e) { log.warn(":getReviewReport(ContentResource) {}", e.getMessage()); return "Error"; } } private String getReviewError(ContentReviewResult reviewResult) { if (reviewResult == null) { log.debug("getReviewReport(ContentReviewResult) called with reviewResult == null"); return null; } if (reviewResult.getStatus() == null) { log.debug("getReviewReport(ContentReviewResult) called with reviewResult.getStatus() == null"); return null; } Long status = reviewResult.getStatus(); //This should use getLocalizedReviewErrorMesage(contentId) to get a i18n message of the error String errorMessage = null; boolean exposeError = false; if (status.equals(ContentReviewConstants.CONTENT_REVIEW_REPORT_ERROR_NO_RETRY_CODE)) { errorMessage = resourceLoader.getString("content_review.error.REPORT_ERROR_NO_RETRY_CODE"); } else if (status.equals(ContentReviewConstants.CONTENT_REVIEW_REPORT_ERROR_RETRY_CODE)) { errorMessage = resourceLoader.getString("content_review.error.REPORT_ERROR_RETRY_CODE"); } else if (status.equals(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_NO_RETRY_CODE)) { errorMessage = resourceLoader.getString("content_review.error.SUBMISSION_ERROR_NO_RETRY_CODE"); exposeError = true; } else if (status.equals(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_CODE)) { errorMessage = resourceLoader.getString("content_review.error.SUBMISSION_ERROR_RETRY_CODE"); exposeError = true; } else if (status.equals(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_RETRY_EXCEEDED_CODE)) { errorMessage = resourceLoader.getString("content_review.error.SUBMISSION_ERROR_RETRY_EXCEEDED_CODE"); } else if (status.equals(ContentReviewConstants.CONTENT_REVIEW_SUBMISSION_ERROR_USER_DETAILS_CODE)) { errorMessage = resourceLoader.getString("content_review.error.SUBMISSION_ERROR_USER_DETAILS_CODE"); } else if (ContentReviewConstants.CONTENT_REVIEW_SUBMITTED_AWAITING_REPORT_CODE.equals(status)) { errorMessage = resourceLoader.getString("content_review.pending.info"); } else if (ContentReviewConstants.CONTENT_REVIEW_NOT_SUBMITTED_CODE.equals(status)) { errorMessage = resourceLoader.getFormattedMessage("content_review.notYetSubmitted", new Object[] { contentReviewService.getServiceName() }); } if (errorMessage == null) { errorMessage = resourceLoader.getString("content_review.error"); } // Expose the underlying CRS error to the UI if (exposeError && exposeContentReviewErrorsToUI) { errorMessage += " " + resourceLoader.getFormattedMessage("content_review.errorFromSource", contentReviewService.getServiceName(), reviewResult.getContentReviewItem().getLastError()); } return errorMessage; } private Optional<Long> createCategoryForGbAssignmentIfNecessary( org.sakaiproject.service.gradebook.shared.Assignment gbAssignment, String fromGradebookId, String toGradebookId) { String categoryName = gbAssignment.getCategoryName(); if (!StringUtils.isBlank(categoryName)) { List<CategoryDefinition> toCategoryDefinitions = gradebookService.getCategoryDefinitions(toGradebookId); if (toCategoryDefinitions == null) { toCategoryDefinitions = new ArrayList<>(); } if (!toCategoryDefinitions.stream().anyMatch(cd -> cd.getName().equals(categoryName))) { // The category doesn't exist yet CategoryDefinition fromCategoryDefinition = gradebookService.getCategoryDefinitions(fromGradebookId) .stream().filter(cd -> cd.getName().equals(categoryName)).findAny().get(); CategoryDefinition toCategoryDefinition = new CategoryDefinition(); toCategoryDefinition.setName(fromCategoryDefinition.getName()); toCategoryDefinition.setAssignmentList( Arrays.asList(new org.sakaiproject.service.gradebook.shared.Assignment[] { gbAssignment })); toCategoryDefinition.setExtraCredit(fromCategoryDefinition.getExtraCredit()); toCategoryDefinition.setWeight(fromCategoryDefinition.getWeight()); toCategoryDefinition.setDropHighest(fromCategoryDefinition.getDropHighest()); toCategoryDefinition.setDropLowest(fromCategoryDefinition.getDropLowest()); toCategoryDefinition.setKeepHighest(fromCategoryDefinition.getKeepHighest()); GradebookInformation toGbInformation = gradebookService.getGradebookInformation(toGradebookId); GradebookInformation fromGbInformation = gradebookService.getGradebookInformation(fromGradebookId); toGbInformation.setCategoryType(fromGbInformation.getCategoryType()); List<CategoryDefinition> categories = toGbInformation.getCategories(); categories.add(toCategoryDefinition); gradebookService.updateGradebookSettings(toGradebookId, toGbInformation); } // A new category may have been added in the previous block. Pull them again, just to be sure. This will // ensure that any upstream caching is refreshed, too. Optional<CategoryDefinition> optional = gradebookService.getCategoryDefinitions(toGradebookId).stream() .filter(cd -> cd.getName().equals(categoryName)).findAny(); if (optional.isPresent()) { return Optional.of(optional.get().getId()); } else { log.warn("Created new gb category, but couldn't find it after creation. Returning empty ..."); return Optional.empty(); } } else { return Optional.empty(); } } }