diff --git a/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java b/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java index 6742075ec81a..103b92ed75ac 100644 --- a/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java +++ b/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentConstants.java @@ -257,6 +257,9 @@ public final class AssignmentConstants { public static final String NEW_ASSIGNMENT_REVIEW_SERVICE_EXCLUDE_VALUE = "exclude_value"; public static final String SUBMISSION_REVIEW_SERVICE_EULA_AGREEMENT = "review_service_eula_agreement"; public static final String SUBMISSION_REVIEW_CHECK_SERVICE_EULA_AGREEMENT = "review_check_service_eula_agreement"; + public static final String NEW_ASSIGNMENT_TAG_CREATOR = "tag_creator"; + public static final String NEW_ASSIGNMENT_TAG_GROUPS = "tag_groups"; + public static final String SHOW_TAGS_STUDENT = "show_tags_student"; private AssignmentConstants() { throw new RuntimeException(this.getClass().getCanonicalName() + " is not to be instantiated"); diff --git a/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentService.java b/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentService.java index 425d0d3a2d22..5663c4ad371a 100644 --- a/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentService.java +++ b/assignment/api/src/java/org/sakaiproject/assignment/api/AssignmentService.java @@ -891,5 +891,7 @@ public String getDeepLinkWithPermissions(String context, String assignmentId, bo public String getContentReviewServiceName(); public String getAssignmentModifier(String modifier); + + public boolean allowAddTags(String context); } diff --git a/assignment/api/src/resources/assignment.properties b/assignment/api/src/resources/assignment.properties index 12f4763c9c95..701396aa8421 100644 --- a/assignment/api/src/resources/assignment.properties +++ b/assignment/api/src/resources/assignment.properties @@ -1275,3 +1275,13 @@ autoevaluation=Autoevaluation: instructor_grading=Instructor Grading: reviewrubric=This is a self-report assignment. You can check your self-report before grading. reviewrubricreport=This is your self-report. + +#S2U-32 +tags_header=Tags +tags_groups=Add tags for selected groups automatically. +tags_instructor=Add tag for assignment creator automatically. +tags_note=Note: they will not appear in the select component when editing +tags_search=Search using tags +tags_option_students=Show tags to students +tags_option_students_default=Default (hide tags to students) +tags_option_students_yes=Show tags to students and allow them to use the filter component diff --git a/assignment/api/src/resources/assignment_ca.properties b/assignment/api/src/resources/assignment_ca.properties index 6c48da7f279e..0cf3d2dae9d5 100644 --- a/assignment/api/src/resources/assignment_ca.properties +++ b/assignment/api/src/resources/assignment_ca.properties @@ -1273,3 +1273,13 @@ autoevaluation=Autoevaluaci\u00f3: instructor_grading=Correcci\u00f3 del profesor: reviewrubric=Aquesta tasca \u00E9s autoavaluable. Pot revisar la autoavaluaci\u00F3 abans d'enviar-la. reviewrubricreport=Aquesta \u00E9s la teva autoavaluaci\u00F3. + +#S2U-32 +tags_header=Etiquetes +tags_groups=Afegir autom\u00e0ticament etiquetes per als grups seleccionats. +tags_instructor=Afegir autom\u00e0ticament etiqueta per a l'autor. +tags_note=Nota: no apareixeran al selector al editar +tags_search=Cercar usant etiquetes +tags_option_students=Mostrar etiquetes als estudiants +tags_option_students_default=Predeterminat (ocultar etiquetes als estudiants). +tags_option_students_yes=Mostrar etiquetes als estudiantes i permetre'ls usar el filtre. diff --git a/assignment/api/src/resources/assignment_es.properties b/assignment/api/src/resources/assignment_es.properties index 5fbb4f0a269c..e349eaad0db9 100644 --- a/assignment/api/src/resources/assignment_es.properties +++ b/assignment/api/src/resources/assignment_es.properties @@ -1266,3 +1266,13 @@ autoevaluation=Autoevaluaci\u00f3n: instructor_grading=Correcci\u00f3n del profesor: reviewrubric=Esta tarea es autoevaluable. Puedes revisar tu autoevaluaci\u00f3n antes de enviarla. reviewrubricreport=Esta es tu autoevaluaci\u00f3n. + +#S2U-32 +tags_header=Etiquetas +tags_groups=A\u00f1adir autom\u00e1ticamente etiquetas para los grupos seleccionados. +tags_instructor=A\u00f1adir autom\u00e1ticamente etiquetas para el autor. +tags_note=Nota: no aparecer\u00e1n en el selector al editar +tags_search=Buscar usando etiquetas +tags_option_students=Mostrar etiquetas a los estudiantes. +tags_option_students_default=Por defecto (ocultar etiquetas a los estudiantes). +tags_option_students_yes=Mostrar etiquetas a los estudiantes y permitirles el uso del filtro. diff --git a/assignment/api/src/resources/assignment_eu.properties b/assignment/api/src/resources/assignment_eu.properties index 20b614ee0bf9..be49951522af 100644 --- a/assignment/api/src/resources/assignment_eu.properties +++ b/assignment/api/src/resources/assignment_eu.properties @@ -1278,3 +1278,13 @@ autoevaluation=Autoebaluazioa: instructor_grading=Irakaslearen zuzenketa: reviewrubric=Zeregin hau autoebaluagarria da. Zure ebaluazioa berrikusi dezakezu bidali baino lehen. reviewrubricreport=Hau da zure ebaluazioa. + +#S2U-32 +tags_header=Etiketak +tags_groups=Etiketak gehitu automatikoki hautatutako taldeentzat. +tags_instructor=Etiketak gehitu automatikoki egilearentzat. +tags_note=Oharra: editatzean ez dira hautagailuan agertuko +tags_search=Bilaketa egin etiketak erabiliz +tags_option_students=Erakutsi etiketak ikasleei. +tags_option_students_default=Lehenetsia (ezkutatu etiketak ikasleei). +tags_option_students_yes= Erakutsi etiketak ikasleei eta baimendu erabiltzea iragazkia diff --git a/assignment/impl/pom.xml b/assignment/impl/pom.xml index a51629255b0d..895787b05b6b 100644 --- a/assignment/impl/pom.xml +++ b/assignment/impl/pom.xml @@ -80,6 +80,10 @@ org.sakaiproject.search search-api + + org.sakaiproject.tags + tags-api + commons-codec commons-codec diff --git a/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java b/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java index c509c180a591..cbbc4c547c2f 100644 --- a/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java +++ b/assignment/impl/src/java/org/sakaiproject/assignment/impl/AssignmentServiceImpl.java @@ -157,6 +157,7 @@ import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.taggable.api.TaggingManager; import org.sakaiproject.taggable.api.TaggingProvider; +import org.sakaiproject.tags.api.TagService; import org.sakaiproject.tasks.api.Priorities; import org.sakaiproject.tasks.api.Task; import org.sakaiproject.tasks.api.TaskService; @@ -241,6 +242,7 @@ public class AssignmentServiceImpl implements AssignmentService, EntityTransferr @Resource private UserMessagingService userMessagingService; @Setter private UserTimeService userTimeService; @Setter private TimeSheetService timeSheetService; + @Setter private TagService tagService; private boolean allowSubmitByInstructor; private boolean exposeContentReviewErrorsToUI; @@ -1114,6 +1116,12 @@ public Assignment addDuplicateAssignment(String context, String assignmentId) th log.error("Error while trying to duplicate Rubrics: {} ", e.getMessage()); } + // copy tags + if (serverConfigurationService.getBoolean("tagservice.enable.integrations", true)) { + List tagIds = tagService.getTagAssociationIds(assignment.getContext(), assignmentId); + tagService.updateTagAssociations(assignment.getContext(), assignment.getId(), tagIds, true); + } + String reference = AssignmentReferenceReckoner.reckoner().assignment(assignment).reckon().getReference(); // event for tracking eventTrackingService.post(eventTrackingService.newEvent(AssignmentConstants.EVENT_ADD_ASSIGNMENT, reference, true)); @@ -5090,4 +5098,9 @@ public String getAssignmentModifier(String modifier) { } } + @Override + public boolean allowAddTags(String context) { + String resourceString = AssignmentReferenceReckoner.reckoner().context(context).reckon().getReference(); + return permissionCheck(TagService.TAGSERVICE_MANAGE_PERMISSION, resourceString, null); + } } diff --git a/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java b/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java index 9cbc230f0f49..e1b7070628ed 100644 --- a/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java +++ b/assignment/impl/src/test/org/sakaiproject/assignment/impl/AssignmentTestConfiguration.java @@ -57,6 +57,7 @@ import org.sakaiproject.search.api.SearchService; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.springframework.orm.hibernate.AdditionalHibernateMappings; +import org.sakaiproject.tags.api.TagService; import org.sakaiproject.tasks.api.TaskService; import org.sakaiproject.taggable.api.TaggingManager; import org.sakaiproject.time.api.TimeService; @@ -329,4 +330,9 @@ public UserMessagingService userMessagingService() { public TimeSheetService timeSheetService() { return mock(TimeSheetService.class); } + + @Bean(name = "org.sakaiproject.tags.api.TagService") + public TagService tagService() { + return mock(TagService.class); + } } diff --git a/assignment/impl/src/webapp/WEB-INF/components.xml b/assignment/impl/src/webapp/WEB-INF/components.xml index ce39eb032a0c..1b9e56c3c58a 100644 --- a/assignment/impl/src/webapp/WEB-INF/components.xml +++ b/assignment/impl/src/webapp/WEB-INF/components.xml @@ -54,6 +54,7 @@ + org.sakaiproject.rubrics rubrics-api + + org.sakaiproject.tags + tags-api + org.sakaiproject.edu-services.scoringservice diff --git a/assignment/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java b/assignment/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java index f36775ce4056..520b833db656 100644 --- a/assignment/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java +++ b/assignment/tool/src/java/org/sakaiproject/assignment/tool/AssignmentAction.java @@ -36,7 +36,10 @@ import static org.sakaiproject.assignment.api.AssignmentConstants.GRADE_SUBMISSION_GRADE; import static org.sakaiproject.assignment.api.AssignmentConstants.GRADE_SUBMISSION_SUBMISSION_ID; import static org.sakaiproject.assignment.api.AssignmentConstants.NEW_ASSIGNMENT_ADD_TO_GRADEBOOK; +import static org.sakaiproject.assignment.api.AssignmentConstants.NEW_ASSIGNMENT_TAG_CREATOR; +import static org.sakaiproject.assignment.api.AssignmentConstants.NEW_ASSIGNMENT_TAG_GROUPS; import static org.sakaiproject.assignment.api.AssignmentConstants.PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT; +import static org.sakaiproject.assignment.api.AssignmentConstants.SHOW_TAGS_STUDENT; import static org.sakaiproject.assignment.api.AssignmentConstants.STATE_CONTEXT_STRING; import static org.sakaiproject.assignment.api.AssignmentConstants.UNGRADED_GRADE_STRING; import static org.sakaiproject.assignment.api.AssignmentConstants.UNGRADED_GRADE_TYPE_STRING; @@ -232,6 +235,8 @@ import org.sakaiproject.taggable.api.TaggingHelperInfo; import org.sakaiproject.taggable.api.TaggingManager; import org.sakaiproject.taggable.api.TaggingProvider; +import org.sakaiproject.tags.api.Tag; +import org.sakaiproject.tags.api.TagService; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.time.api.UserTimeService; import org.sakaiproject.tool.api.Session; @@ -689,6 +694,8 @@ public class AssignmentAction extends PagedResourceActionII { private static final String NEW_ASSIGNMENT_DUE_DATE_SCHEDULED = "new_assignment_due_date_scheduled"; private static final String NEW_ASSIGNMENT_OPEN_DATE_ANNOUNCED = "new_assignment_open_date_announced"; private static final String NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE = "new_assignment_check_add_honor_pledge"; + private static final String NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS = "new_assignment_check_add_group_tags"; + private static final String NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS = "new_assignment_check_add_instructor_tags"; private static final String NEW_ASSIGNMENT_CHECK_HIDE_DUE_DATE = "new_assignment_check_hide_due_date"; private static final String NEW_ASSIGNMENT_FOCUS = "new_assignment_focus"; private static final String NEW_ASSIGNMENT_DESCRIPTION_EMPTY = "new_assignment_description_empty"; @@ -1073,6 +1080,7 @@ public class AssignmentAction extends PagedResourceActionII { private static final String INVOKE_BY_LINK = "link"; private static final String INVOKE_BY_PORTAL = "portal"; private static final String SUBMISSIONS_SEARCH_ONLY = "submissions_search_only"; + private static final String TAG_SELECTOR = "tag_selector"; /*************** search related *******************/ private static final String STATE_SEARCH = "state_search"; private static final String FORM_SEARCH = "form_search"; @@ -1167,6 +1175,7 @@ private enum AssignmentFilter { private UserTimeService userTimeService; private RangeAndGroupsDelegate rangeAndGroups; private LTIService ltiService; + private TagService tagService; /** * sort by assignment timesheet @@ -1210,6 +1219,7 @@ public AssignmentAction() { userDirectoryService = ComponentManager.get(UserDirectoryService.class); userTimeService = ComponentManager.get(UserTimeService.class); ltiService = ComponentManager.get(LTIService.class); + tagService = ComponentManager.get(TagService.class); rangeAndGroups = new RangeAndGroupsDelegate(assignmentService, rb, siteService, securityService, formattedText); } @@ -2879,6 +2889,28 @@ private String build_list_assignments_context(VelocityPortlet portlet, Context c context.put("rubricMap", rubricMap); + if (serverConfigurationService.getBoolean("tagservice.enable.integrations", true)) { + Set allTaggedGroupsAndInstructorsSet = new HashSet<>(); + Map> tagsMap = new HashMap<>(); + assignments.forEach(a -> { + List tagLabels = tagService.getAssociatedTagsForItem(a.getContext(), a.getId()).stream().map(Tag::getTagLabel).collect(Collectors.toList()); + List instructorAndGroupTags = addInstructorAndGroupTags(a); + tagLabels.addAll(instructorAndGroupTags); + allTaggedGroupsAndInstructorsSet.addAll(instructorAndGroupTags); + tagsMap.put(a.getId(), tagLabels); + }); + context.put("tagsEnabled", Boolean.TRUE); + context.put("tagTool", TagService.TOOL_ASSIGNMENTS); + context.put("allowAddTags", assignmentService.allowAddTags(contextString)); + context.put("tagsMap", tagsMap); + context.put("siteId", contextString); + List allTaggedGroupsAndInstructors = new ArrayList<>(allTaggedGroupsAndInstructorsSet); + Collections.sort(allTaggedGroupsAndInstructors, String.CASE_INSENSITIVE_ORDER); + context.put("allTaggedGroupsAndInstructors", String.join(",", allTaggedGroupsAndInstructors)); + context.put(SHOW_TAGS_STUDENT, state.getAttribute(SHOW_TAGS_STUDENT)); + context.put(TAG_SELECTOR, state.getAttribute(TAG_SELECTOR)); + } + // allow get assignment context.put("allowGetAssignment", assignmentService.allowGetAssignment(contextString)); @@ -2963,6 +2995,37 @@ private String build_list_assignments_context(VelocityPortlet portlet, Context c } // build_list_assignments_context + private List addInstructorAndGroupTags(Assignment a) { + List tagLabels = new ArrayList<>(); + if (a.getProperties().get(NEW_ASSIGNMENT_TAG_CREATOR) != null && Boolean.parseBoolean(a.getProperties().get(NEW_ASSIGNMENT_TAG_CREATOR))) { + try { + String author = userDirectoryService.getUser(a.getAuthor()).getDisplayName(); + tagLabels.add(author); + } catch (Exception e) { + log.warn(this + ":build_list_assignments_context cannot get user " + e.getMessage() + " user id=" + a.getAuthor()); + } + if (!a.getAuthor().equals(a.getModifier())){ + try { + String modifier = userDirectoryService.getUser(a.getModifier()).getDisplayName(); + tagLabels.add(modifier); + } catch (Exception e) { + log.warn(this + ":build_list_assignments_context cannot get user " + e.getMessage() + " user id=" + a.getModifier()); + } + } + } + if (a.getProperties().get(NEW_ASSIGNMENT_TAG_GROUPS) != null && Boolean.parseBoolean(a.getProperties().get(NEW_ASSIGNMENT_TAG_GROUPS))) { + try { + Site site = siteService.getSite(a.getContext()); + List asnGroups = a.getGroups().stream().map(id -> site.getGroup(id)).filter(Objects::nonNull).collect(Collectors.toList()); + List groupTitles = asnGroups.stream().map(Group::getTitle).collect(Collectors.toList()); + tagLabels.addAll(groupTitles); + } catch (Exception e) { + log.warn(this + ":build_list_assignments_context cannot get site " + e.getMessage() + " site id=" + a.getContext()); + } + } + return tagLabels; + } + private List getSortedAsnGroupTitles(Assignment asn, Site site, AssignmentComparator groupComparator) { List asnGroups = asn.getGroups().stream().map(id -> site.getGroup(id)).filter(Objects::nonNull).collect(Collectors.toList()); asnGroups.sort(groupComparator); @@ -3170,6 +3233,9 @@ protected void setAssignmentFormContext(SessionState state, Context context) { } context.put("name_CheckAddHonorPledge", NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE); + context.put("name_CheckAddInstructorTags", NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS); + context.put("name_CheckAddGroupTags", NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS); + // SAK-17606 context.put("name_CheckAnonymousGrading", NEW_ASSIGNMENT_CHECK_ANONYMOUS_GRADING); @@ -3341,6 +3407,9 @@ protected void setAssignmentFormContext(SessionState state, Context context) { context.put("value_CheckAddHonorPledge", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE)); + context.put("value_CheckAddInstructorTags", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS)); + context.put("value_CheckAddGroupTags", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS)); + // put resubmission option into context assignment_resubmission_option_into_context(context, state); assignment_extension_option_into_context(context, state); @@ -3478,6 +3547,12 @@ protected void setAssignmentFormContext(SessionState state, Context context) { // get attachment for all purpose object putSupplementItemAttachmentStateIntoContext(state, context, ALLPURPOSE_ATTACHMENTS); + if (serverConfigurationService.getBoolean("tagservice.enable.integrations", true)) { + context.put("tagsEnabled", Boolean.TRUE); + context.put("tagTool", TagService.TOOL_ASSIGNMENTS); + context.put("allowAddTags", assignmentService.allowAddTags(contextString)); + } + // put role information into context HashMap roleUsers = new HashMap(); try { @@ -3737,6 +3812,8 @@ private String build_instructor_preview_assignment_context(VelocityPortlet portl context.put("value_opendate_notification_low", AssignmentConstants.ASSIGNMENT_OPENDATE_NOTIFICATION_LOW); context.put("value_opendate_notification_high", AssignmentConstants.ASSIGNMENT_OPENDATE_NOTIFICATION_HIGH); context.put("value_CheckAddHonorPledge", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE)); + context.put("value_CheckAddInstructorTags", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS)); + context.put("value_CheckAddGroupTags", state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS)); context.put("value_NEW_ASSIGNMENT_CHECK_ADD_ESTIMATE", state.getAttribute(ResourceProperties.NEW_ASSIGNMENT_CHECK_ADD_ESTIMATE)); context.put("value_NEW_ASSIGNMENT_CHECK_ADD_ESTIMATE_REQUIRED", state.getAttribute(ResourceProperties.NEW_ASSIGNMENT_CHECK_ADD_ESTIMATE_REQUIRED)); @@ -5775,6 +5852,21 @@ private String build_instructor_download_upload_all(VelocityPortlet portlet, Con return template + TEMPLATE_INSTRUCTOR_UPLOAD_ALL; } // build_instructor_upload_all + public void doSearchTags(RunData data) { + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + + resetPaging(state); + state.setAttribute(STATE_MODE, MODE_LIST_ASSIGNMENTS); + state.setAttribute(SORTED_BY, SORTED_BY_DEFAULT); + state.setAttribute(SORTED_ASC, Boolean.TRUE.toString()); + + if (data.getParameters().getString(TAG_SELECTOR) != null && StringUtils.trimToNull(data.getParameters().getString(TAG_SELECTOR)) != null) { + state.setAttribute(TAG_SELECTOR, data.getParameters().getString(TAG_SELECTOR)); + } else { + state.removeAttribute(TAG_SELECTOR); + } + } // doSearchTags + /** * Filter the assignments list by group */ @@ -7626,6 +7718,8 @@ private void setNewAssignmentParameters(RunData data, boolean validify) { } } + state.setAttribute(TAG_SELECTOR, params.getString(TAG_SELECTOR)); + //Peer Assessment boolean peerAssessment = false; if (gradeType == SCORE_GRADE_TYPE && params.getBoolean(NEW_ASSIGNMENT_USE_PEER_ASSESSMENT)) { @@ -7846,6 +7940,11 @@ private void setNewAssignmentParameters(RunData data, boolean validify) { // set the honor pledge to be "no honor pledge" state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE, hp); + Boolean ait = params.getBoolean(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS); + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS, ait); + Boolean agt = params.getBoolean(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS); + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS, agt); + List attachments = (List) state.getAttribute(ATTACHMENTS); if (attachments == null || attachments.isEmpty()) { // read from vm file @@ -8524,6 +8623,9 @@ private void post_save_assignment(RunData data, String postOrSave) { Boolean checkAddHonorPledge = (Boolean) state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE); + Boolean checkAddInstructorTags = state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS) != null ? (Boolean) state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS) : null; + Boolean checkAddGroupTags = state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS) != null ? (Boolean) state.getAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS) : null; + String addtoGradebook = GRADEBOOK_INTEGRATION_NO; if (((Boolean) state.getAttribute(NEW_ASSIGNMENT_GRADE_ASSIGNMENT))) { addtoGradebook = StringUtils.isNotBlank((String) state.getAttribute(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK)) ? (String) state.getAttribute(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK) : GRADEBOOK_INTEGRATION_NO; @@ -8662,6 +8764,11 @@ private void post_save_assignment(RunData data, String postOrSave) { aProperties.put(AssignmentConstants.ASSIGNMENT_RELEASERESUBMISSION_NOTIFICATION_VALUE, (String) state.getAttribute(AssignmentConstants.ASSIGNMENT_RELEASERESUBMISSION_NOTIFICATION_VALUE)); } + if (serverConfigurationService.getBoolean("tagservice.enable.integrations", true) && assignmentService.allowAddTags(siteId)) { + aProperties.put(NEW_ASSIGNMENT_TAG_CREATOR, Boolean.toString(checkAddInstructorTags)); + aProperties.put(NEW_ASSIGNMENT_TAG_GROUPS, Boolean.toString(checkAddGroupTags)); + } + // persist the Assignment changes commitAssignment(state, post, a, assignmentReference, title, submissionType, useReviewService, allowStudentViewReport, gradeType, gradePoints, description, checkAddHonorPledge, attachments, section, rangeAndGroupSettings.range, @@ -8676,6 +8783,14 @@ private void post_save_assignment(RunData data, String postOrSave) { rubricsService.saveRubricAssociation(AssignmentConstants.TOOL_ID, a.getId(), rubricParams); } + if (serverConfigurationService.getBoolean("tagservice.enable.integrations", true) && assignmentService.allowAddTags(siteId)) { + List tagIds = new ArrayList<>(); + if (state.getAttribute(TAG_SELECTOR) != null && StringUtils.trimToNull((String) state.getAttribute(TAG_SELECTOR)) != null) { + tagIds.addAll(Arrays.asList(((String) state.getAttribute(TAG_SELECTOR)).split(","))); + } + tagService.updateTagAssociations(a.getContext(), a.getId(), tagIds, true); + } + if (post) { // we need to update the submission if (switchingGradeType || bool_change_resubmit_option) { @@ -10128,6 +10243,13 @@ public void doEdit_assignment(RunData data) { state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE, a.getHonorPledge()); + if(properties.get(NEW_ASSIGNMENT_TAG_CREATOR) != null) { + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS, Boolean.valueOf(properties.get(NEW_ASSIGNMENT_TAG_CREATOR).toString())); + } + if(properties.get(NEW_ASSIGNMENT_TAG_GROUPS) != null) { + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS, Boolean.valueOf(properties.get(NEW_ASSIGNMENT_TAG_GROUPS).toString())); + } + state.setAttribute(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, properties.get(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK)); state.setAttribute(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT, properties.get(PROP_ASSIGNMENT_ASSOCIATE_GRADEBOOK_ASSIGNMENT)); @@ -11947,17 +12069,20 @@ protected void initState(SessionState state, VelocityPortlet portlet, JetspeedRu state.setAttribute(STATE_USER, userDirectoryService.getCurrentUser()); } - if (state.getAttribute(SUBMISSIONS_SEARCH_ONLY) == null) { - String propValue = null; + if (state.getAttribute(SUBMISSIONS_SEARCH_ONLY) == null || state.getAttribute(SHOW_TAGS_STUDENT) == null) { + String propValueSubmissions = null; + String propValueTags = null; // save the option into tool configuration try { Site site = siteService.getSite(siteId); ToolConfiguration tc = site.getToolForCommonId(AssignmentConstants.TOOL_ID); - propValue = tc.getPlacementConfig().getProperty(SUBMISSIONS_SEARCH_ONLY); + propValueSubmissions = tc.getPlacementConfig().getProperty(SUBMISSIONS_SEARCH_ONLY); + propValueTags = tc.getPlacementConfig().getProperty(SHOW_TAGS_STUDENT); } catch (IdUnusedException e) { log.warn(this + ":init() Cannot find site with id " + siteId); } - state.setAttribute(SUBMISSIONS_SEARCH_ONLY, propValue == null ? Boolean.FALSE : Boolean.valueOf(propValue)); + state.setAttribute(SUBMISSIONS_SEARCH_ONLY, propValueSubmissions == null ? Boolean.FALSE : Boolean.valueOf(propValueSubmissions)); + state.setAttribute(SHOW_TAGS_STUDENT, propValueTags == null ? Boolean.FALSE : Boolean.valueOf(propValueTags)); } /** The calendar tool */ @@ -12323,6 +12448,9 @@ private void initializeAssignment(SessionState state) { // make the honor pledge not include as the default state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE, Boolean.FALSE); + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS, Boolean.FALSE); + state.setAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS, Boolean.FALSE); + state.setAttribute(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK, GRADEBOOK_INTEGRATION_NO); state.setAttribute(NEW_ASSIGNMENT_ATTACHMENT, entityManager.newReferenceList()); @@ -12428,6 +12556,8 @@ private void resetAssignment(SessionState state) { state.removeAttribute(ResourceProperties.NEW_ASSIGNMENT_CHECK_AUTO_ANNOUNCE); state.removeAttribute(AssignmentConstants.ASSIGNMENT_OPENDATE_NOTIFICATION); state.removeAttribute(NEW_ASSIGNMENT_CHECK_ADD_HONOR_PLEDGE); + state.removeAttribute(NEW_ASSIGNMENT_CHECK_ADD_INSTRUCTOR_TAGS); + state.removeAttribute(NEW_ASSIGNMENT_CHECK_ADD_GROUP_TAGS); state.removeAttribute(NEW_ASSIGNMENT_CHECK_HIDE_DUE_DATE); state.removeAttribute(NEW_ASSIGNMENT_ADD_TO_GRADEBOOK); state.removeAttribute(NEW_ASSIGNMENT_ATTACHMENT); @@ -12505,6 +12635,8 @@ private void resetAssignment(SessionState state) { state.removeAttribute(NEW_ASSIGNMENT_PREVIOUSLY_ASSOCIATED); state.removeAttribute(RUBRIC_ASSOCIATION); + + state.removeAttribute(TAG_SELECTOR); } // resetAssignment /** @@ -12867,6 +12999,16 @@ protected int sizeResources(SessionState state) { returnResources = filterAssignments(returnResources, (AssignmentFilter) state.getAttribute(FILTER_OPTION)); + //Filter assignments by tags + List selectedTags = state.getAttribute(TAG_SELECTOR) != null ? Arrays.asList(((String) state.getAttribute(TAG_SELECTOR)).split(",")) : new ArrayList<>(); + if (CollectionUtils.isNotEmpty(selectedTags)) { + returnResources = ((List)returnResources).stream().filter(a -> { + List tagIds = tagService.getTagAssociationIds(a.getContext(), a.getId()); + tagIds.addAll(addInstructorAndGroupTags(a)); + return (tagIds.containsAll(selectedTags)); + }).collect(Collectors.toList()); + } + state.setAttribute(HAS_MULTIPLE_ASSIGNMENTS, returnResources.size() > 1); break; case MODE_INSTRUCTOR_REORDER_ASSIGNMENT: @@ -14985,6 +15127,8 @@ protected void doOptions(RunData data) { ToolConfiguration tc = site.getToolForCommonId(AssignmentConstants.TOOL_ID); String optionValue = tc.getPlacementConfig().getProperty(SUBMISSIONS_SEARCH_ONLY); state.setAttribute(SUBMISSIONS_SEARCH_ONLY, optionValue == null ? Boolean.FALSE : Boolean.valueOf(optionValue)); + String optionTagsValue = tc.getPlacementConfig().getProperty(SHOW_TAGS_STUDENT); + state.setAttribute(SHOW_TAGS_STUDENT, optionTagsValue == null ? Boolean.FALSE : Boolean.valueOf(optionTagsValue)); } catch (IdUnusedException e) { log.warn(this + ":doOptions Cannot find site with id " + siteId); } @@ -15012,17 +15156,20 @@ protected String build_options_context(VelocityPortlet portlet, Context context, context.put("context", siteId); Boolean submissionsSearchOnly = (Boolean) state.getAttribute(SUBMISSIONS_SEARCH_ONLY); + Boolean showTagsStudents = (Boolean) state.getAttribute(SHOW_TAGS_STUDENT); - if (submissionsSearchOnly == null) { + if (submissionsSearchOnly == null || showTagsStudents == null) { try { Site site = siteService.getSite(siteId); ToolConfiguration tc = site.getToolForCommonId(AssignmentConstants.TOOL_ID); submissionsSearchOnly = BooleanUtils.toBoolean(tc.getPlacementConfig().getProperty(SUBMISSIONS_SEARCH_ONLY)); + showTagsStudents = BooleanUtils.toBoolean(tc.getPlacementConfig().getProperty(SHOW_TAGS_STUDENT)); } catch (Exception e) { } } context.put(SUBMISSIONS_SEARCH_ONLY, submissionsSearchOnly); + context.put(SHOW_TAGS_STUDENT, showTagsStudents); String template = (String) getContext(data).get("template"); return template + TEMPLATE_OPTIONS; @@ -15046,6 +15193,8 @@ public void doUpdate_options(RunData data) { // only show those submissions matching search criteria boolean submissionsSearchOnly = params.getBoolean(SUBMISSIONS_SEARCH_ONLY); state.setAttribute(SUBMISSIONS_SEARCH_ONLY, submissionsSearchOnly); + boolean showTagsStudents = params.getBoolean(SHOW_TAGS_STUDENT); + state.setAttribute(SHOW_TAGS_STUDENT, showTagsStudents); // save the option into tool configuration try { @@ -15053,11 +15202,16 @@ public void doUpdate_options(RunData data) { Site site = siteService.getSite(siteId); ToolConfiguration tc = site.getToolForCommonId(AssignmentConstants.TOOL_ID); String currentSetting = tc.getPlacementConfig().getProperty(SUBMISSIONS_SEARCH_ONLY); + String currentSettingTags = tc.getPlacementConfig().getProperty(SHOW_TAGS_STUDENT); if (currentSetting == null || !currentSetting.equals(Boolean.toString(submissionsSearchOnly))) { changed = true; // save the change tc.getPlacementConfig().setProperty(SUBMISSIONS_SEARCH_ONLY, Boolean.toString(submissionsSearchOnly)); } + if (currentSettingTags == null || !currentSettingTags.equals(Boolean.toString(showTagsStudents))) { + changed = true; + tc.getPlacementConfig().setProperty(SHOW_TAGS_STUDENT, Boolean.toString(showTagsStudents)); + } if (changed) { siteService.save(site); } diff --git a/assignment/tool/src/webapp/js/assignments.js b/assignment/tool/src/webapp/js/assignments.js index 57e653decafb..c34097b3d89d 100755 --- a/assignment/tool/src/webapp/js/assignments.js +++ b/assignment/tool/src/webapp/js/assignments.js @@ -1112,3 +1112,18 @@ ASN.grab = function (selectedItem) { $(li).removeClass("grabbing_cursor"); $(li).addClass("grab_cursor"); } + +ASN.clearShadowTags = function () { + const tagSelector = document.getElementById('tag_selector'); + const tagSelectorVue = document.querySelector("sakai-tag-selector").shadowRoot; + if (tagSelector && tagSelectorVue) { + tagSelector.value = ''; + tagSelectorVue.querySelectorAll('input[name="tag[]"').forEach((elem) => elem.remove()); + } +} + +ASN.checkIframeTags = function () { + if (inIframe()) { + document.getElementById('tagSection').style.display = 'none'; + } +} diff --git a/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_new_edit_assignment.vm b/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_new_edit_assignment.vm index de1b281578cf..c13b85483271 100644 --- a/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_new_edit_assignment.vm +++ b/assignment/tool/src/webapp/vm/assignment/chef_assignments_instructor_new_edit_assignment.vm @@ -31,6 +31,12 @@ $('#selectedGroups').change(function(){ ASN_INE.validateGroupSelection(); }); + ASN.checkIframeTags(); + + // Initialize tag selector + #if ($!tagsEnabled && $!allowAddTags) + window.syncTagSelectorInput("tag-selector", "tag_selector"); + #end }); @@ -45,6 +51,28 @@ +
+ #if ($!view.equals('lisofass1') && $!tagsEnabled && ($!allowAddTags || $!show_tags_student) && ($!assignments.hasNext() || $!tag_selector)) +
+ + + +
+ +
+
+ + #if ($!tag_selector) + + #end + +
+
+ #end +
+ #if ($allowAddAssignment) #if ($!view.equals('stuvie'))
$tlang.getString("stulistassig.selanass1")
@@ -76,27 +104,25 @@ #end - #if ($hasAssignments) -
-
-
- - - $tlang.getString("gen.listnavselect") - - -
-
-
-
- #pagerPanel("pager1") +
+
+
+ + + $tlang.getString("gen.listnavselect") + + +
- #end +
+
+ #pagerPanel("pager1") +
#if (!$!hasAssignments) @@ -261,6 +287,11 @@ #end + #if ($!tagsEnabled && ($!allowAddTags || $!show_tags_student)) + + $tlang.getString('tags_header') + + #end #if ($!allowRemoveAssignment && $!view.equals('lisofass1')) $tlang.getString("gen.remove.q") @@ -653,6 +684,15 @@ #end #end + #if ($!tagsEnabled && ($!allowAddTags || $!show_tags_student)) + + #if ($tagsMap.containsKey($assignment.getId())) + #foreach($tag in $tagsMap.get($assignment.getId())) + $tag + #end + #end + + #end #if ($!allowRemoveAssignment && $!view.equals('lisofass1')) @@ -753,6 +793,9 @@ #end + #if ($!tagsEnabled && $!show_tags_student) + + #end #if ($!allowRemoveAssignment && $!view.equals('lisofass1')) @@ -823,6 +866,9 @@ #end + #if ($!tagsEnabled && $!show_tags_student) + + #end #if ($!allowRemoveAssignment && $!view.equals('lisofass1')) diff --git a/assignment/tool/src/webapp/vm/assignment/chef_assignments_options.vm b/assignment/tool/src/webapp/vm/assignment/chef_assignments_options.vm index 1da75e3a1718..e2f16e1f3042 100644 --- a/assignment/tool/src/webapp/vm/assignment/chef_assignments_options.vm +++ b/assignment/tool/src/webapp/vm/assignment/chef_assignments_options.vm @@ -29,6 +29,21 @@ +
+

$tlang.getString("tags_option_students")

+
+ +
+
+ +
+

diff --git a/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties b/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties index 74820f2514e1..bcf662a8405e 100644 --- a/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties +++ b/config/configuration/bundles/src/bundle/org/sakaiproject/config/bundle/default.sakai.properties @@ -5530,6 +5530,9 @@ # The email address that will receive the confirmation mails from the import jobs (by default uses the defined in portal.error.email) #tags.import_job_email=youremail@yourdomain.ext +# S2U-32 and S2U-28 Allow the use of tags in some tools: assignments, private messages (default is true) +# tagservice.enable.integrations=false + # ############################### #SAMIGO SEARCH AND TAGS # ############################### diff --git a/library/src/skins/default/src/sass/base/_extendables.scss b/library/src/skins/default/src/sass/base/_extendables.scss index 04a3257204de..6103b87b8a4b 100644 --- a/library/src/skins/default/src/sass/base/_extendables.scss +++ b/library/src/skins/default/src/sass/base/_extendables.scss @@ -624,7 +624,10 @@ div.wicket-modal div.w_blue a.w_close { .text-info, .bg-info { color: var(--infoBanner-color); - background-color: var(--infoBanner-bgcolor); + background-color: var(--infoBanner-bgcolor) !important; + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; } .text-warning, .bg-warning { diff --git a/library/src/skins/default/src/sass/modules/tool/assignments/_assignments.scss b/library/src/skins/default/src/sass/modules/tool/assignments/_assignments.scss index cb3a220e686c..ed41825692a7 100644 --- a/library/src/skins/default/src/sass/modules/tool/assignments/_assignments.scss +++ b/library/src/skins/default/src/sass/modules/tool/assignments/_assignments.scss @@ -170,6 +170,9 @@ line-height: 1; padding: 0.5em 0 0.5em 2.5em; } + .tag-header{ + max-width:250px + } } .toggleAnchor { @@ -710,8 +713,4 @@ margin-right: 7px; } } - - input { - margin: 2px; - } } diff --git a/library/src/skins/default/src/sass/modules/tool/messages/_messages.scss b/library/src/skins/default/src/sass/modules/tool/messages/_messages.scss index 45e3bd85d40e..9440682da4cc 100644 --- a/library/src/skins/default/src/sass/modules/tool/messages/_messages.scss +++ b/library/src/skins/default/src/sass/modules/tool/messages/_messages.scss @@ -114,6 +114,12 @@ td.priority{ width: 7%; } + td.taglist{ + width: 17%; + list-style-type: none; + padding: 0; + max-width: 250px; + } &\:pvtmsgs_filter { input[type=search] { @@ -221,4 +227,7 @@ .ToggleBulkDisabled { color: var(--sakai-text-color-disabled); } + .DisableTags { + pointer-events: none; + } } diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties index ffc0fd8e33ec..0e2c0ff5b61b 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties @@ -995,3 +995,8 @@ pvt_read_receipt_text=Receive a notification when the message is opened by the r pvt_read_receipt_email={0}({1}) has read the message with the subject: `{2}` on {3}. \n\nTo see this message, click in this url and see the message of the site: {4} pvt_email_href={1}<\a> pvt_read_receipt_email_subject={0} has been read + +#S2U-28 +pvt_tags_header=Tags +pvt_tags_save=Save Tags +pvt_enter_search_tags=Please select tags for search. diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties index fa266fb22c25..265ffbac4bfe 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_ca.properties @@ -988,3 +988,8 @@ pvt_read_receipt_text=Rebre una notificaci\u00f3 quan el missatge sigui obert pe pvt_read_receipt_email={0}({1}) ha llegit el missatge amb l`assumpte `{2}` el {3}. \n\nPer veure aquest missatge, feu clic en aquest URL i vegeu el missatge del lloc: {4} pvt_read_receipt_email_subject={0} ha estat llegit + +#S2U-28 +pvt_tags_header=Etiquetes +pvt_tags_save=Desar Etiquetes +pvt_enter_search_tags=Introdu\u00efu etiquetes per a la cerca. diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties index 66153fb56f22..254747dc061a 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_es.properties @@ -973,3 +973,8 @@ pvt_read_receipt_text=Recibir una notificaci\u00f3n cuando el mensaje sea abiert pvt_read_receipt_email={0}({1}) ha le\u00eddo el mensaje con asunto `{2}` el {3}. \n\nPara ver el mensaje que envi\u00f3, haga clic en este enlace y acceda al mensaje del sitio: {4} pvt_read_receipt_email_subject={0} ha sido le\u00eddo + +#S2U-28 +pvt_tags_header=Etiquetas +pvt_tags_save=Guardar Etiquetas +pvt_enter_search_tags=Introduzca etiquetas de b\u00fasqueda. diff --git a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties index b0fcbcd54f05..3184612c3d4c 100644 --- a/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties +++ b/msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages_eu.properties @@ -990,3 +990,8 @@ pvt_read_receipt_text=Jaso jakinarazpen bat hartzaileak mezua irekitzen duenean pvt_read_receipt_email={0}({1})-ek `{2}` mezua irakurri du {3}an. \n\nMezu hau ikusteko, egin klik url honetan eta ikusi gunearen mezua: {4} pvt_read_receipt_email_subject={0} irakurri da + +#S2U-28 +pvt_tags_header=Etiketak +pvt_tags_save=Gorde etiketak +pvt_enter_search_tags=Sartu bilatzeko testua. diff --git a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java index f5929222f14c..2f46011a5100 100644 --- a/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java +++ b/msgcntr/messageforums-api/src/java/org/sakaiproject/api/app/messageforums/ui/PrivateMessageManager.java @@ -125,8 +125,8 @@ public List getMessagesByTypeByContext(final String typeUuid, final String conte * @param asEmail * @param readReceipt */ - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt); - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients, boolean readReceipt); + public Long sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt); + public Long sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients, boolean readReceipt); /** diff --git a/msgcntr/messageforums-app/pom.xml b/msgcntr/messageforums-app/pom.xml index da162b2a2110..4aff6a77efdc 100644 --- a/msgcntr/messageforums-app/pom.xml +++ b/msgcntr/messageforums-app/pom.xml @@ -19,6 +19,10 @@ org.sakaiproject.calendar sakai-calendar-api + + org.sakaiproject.tags + tags-api + javax.servlet javax.servlet-api diff --git a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java index c98d6f863ed5..f0524a1a1b55 100644 --- a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java +++ b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java @@ -70,6 +70,8 @@ import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; +import org.sakaiproject.tags.api.Tag; +import org.sakaiproject.tags.api.TagService; import org.sakaiproject.time.api.UserTimeService; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.api.SessionManager; @@ -108,6 +110,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -165,6 +168,7 @@ public class PrivateMessagesTool { private static final String CONFIRM_MSG_DELETE = "pvt_confirm_msg_delete"; private static final String ENTER_SEARCH_TEXT = "pvt_enter_search_text"; + private static final String ENTER_SEARCH_TAGS = "pvt_enter_search_tags"; private static final String MOVE_MSG_ERROR = "pvt_move_msg_error"; private static final String NO_MARKED_READ_MESSAGE = "pvt_no_message_mark_read"; private static final String NO_MARKED_DELETE_MESSAGE = "pvt_no_message_mark_delete"; @@ -187,6 +191,7 @@ public class PrivateMessagesTool { private static final String HIDDEN_SEARCH_TO_ISO_DATE = "searchToDateISO8601"; private Boolean fromPermissions = false; + private Boolean fromPreview = false; /** *Dependency Injected @@ -243,7 +248,10 @@ public class PrivateMessagesTool { @Setter @ManagedProperty(value="#{Components[\"org.sakaiproject.util.api.FormattedText\"]}") private FormattedText formattedText; - + @Setter + @ManagedProperty(value="#{Components[\"org.sakaiproject.tags.api.TagService\"]}") + private TagService tagService; + /** Navigation for JSP */ public static final String MAIN_PG="main"; public static final String DISPLAY_MESSAGES_PG="pvtMsg"; @@ -309,8 +317,17 @@ public class PrivateMessagesTool { @Getter @Setter private String msgNavMode="privateMessages" ; - @Getter @Setter - private PrivateMessageDecoratedBean detailMsg ; + @Getter + private PrivateMessageDecoratedBean detailMsg; + public void setDetailMsg(PrivateMessageDecoratedBean detailMsg) { + this.detailMsg = detailMsg; + if (detailMsg == null || (!fromPreview && !detailMsg.getIsPreview() && !detailMsg.getIsPreviewReply() && !detailMsg.getIsPreviewReplyAll() && !detailMsg.getIsPreviewForward())) { + this.selectedTags = ""; + fromPreview = false; + } else if (detailMsg.getIsPreview() || detailMsg.getIsPreviewReply() || detailMsg.getIsPreviewReplyAll() || detailMsg.getIsPreviewForward()) { + fromPreview = true; + } + } private boolean viewChanged = false; @Getter @Setter @@ -410,6 +427,8 @@ public class PrivateMessagesTool { @Getter @Setter private PrivateMessage replyingMessage; + @Getter @Setter + private String selectedTags; //=====================need to be modified to support internationalization - by huxt /** The configuration mode, received, sent,delete, case etc ... */ public static final String STATE_PVTMSG_MODE = "pvtmsg.mode"; @@ -745,10 +764,14 @@ private String getSiteTitle(){ return ""; } - private String getSiteId() { + public String getSiteId() { return toolManager.getCurrentPlacement().getContext(); } - + + public String getTagTool() { + return TagService.TOOL_PRIVATE_MESSAGES; + } + private String getContextSiteId() { return "/site/" + toolManager.getCurrentPlacement().getContext(); @@ -1050,6 +1073,8 @@ public String processDisplayForum() multiDeleteSuccess = false; if (searchPvtMsgs != null) searchPvtMsgs.clear(); + this.selectedTags = ""; + this.fromPreview = false; return DISPLAY_MESSAGES_PG; } @@ -1140,13 +1165,13 @@ public String processPvtMsgTopic() } public String calculateColumnClass() { - String columnClasses = "check,attach,reply,specialLink,date,dateScheduler,created,addressee,priority"; + String columnClasses = "check,attach,reply,specialLink,date,dateScheduler,created,addressee,priority,taglist hidden-xs"; if (selectedTopic.getTopic().getTitle().equals("pvt_received")) { - columnClasses = "check,attach,reply,specialLink,date,created,priority"; + columnClasses = "check,attach,reply,specialLink,date,created,priority,taglist hidden-xs"; } else if (selectedTopic.getTopic().getTitle().equals("pvt_sent")) { - columnClasses = "check,attach,reply,specialLink,date,dateScheduler,addressee,priority"; + columnClasses = "check,attach,reply,specialLink,date,dateScheduler,addressee,priority,taglist hidden-xs"; } else if (selectedTopic.getTopic().getTitle().equals("pvt_drafts") || selectedTopic.getTopic().getTitle().equals("pvt_deleted") || selectedTopic.getTopic().getTitle().equals("pvt_scheduler")) { - columnClasses = "check,attach,reply,specialLink,date,dateScheduler,created,priority"; + columnClasses = "check,attach,reply,specialLink,date,dateScheduler,created,priority,taglist hidden-xs"; } return columnClasses; } @@ -1649,7 +1674,14 @@ public String processPvtMsgDeleteConfirm() { */ return SELECTED_MESSAGE_PG ; } - + + public String processPvtMsgSaveTags() { + log.debug("processPvtMsgDeleteConfirm() " + currentMsgUuid); + + manageTagAssociation(Long.valueOf(currentMsgUuid)); + return SELECTED_MESSAGE_PG; + } + /** * called from Single delete Page - * called when 'delete' button pressed second time @@ -1722,6 +1754,8 @@ public void resetComposeContents() this.setOpenDate(""); this.setSchedulerSendDateString(""); this.setBooleanReadReceipt(false); + this.setSelectedTags(""); + this.fromPreview = false; } public String processPvtMsgPreview(){ @@ -1817,7 +1851,8 @@ public String processPvtMsgSend() { return processPvtMsgComposeCancel(); } else { PrivateMessageSchedulerService.removeScheduledReminder(pMsg.getId()); - prtMsgManager.sendPrivateMessage(pMsg, recipients, isSendEmail, booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(pMsg, recipients, isSendEmail, booleanReadReceipt); + manageTagAssociation(msgId); } // if you are sending a reply Message replying = pMsg.getInReplyTo(); @@ -1951,7 +1986,8 @@ public String processPvtMsgSaveDraft() { List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(dMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(dMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + manageTagAssociation(msgId); //reset contents resetComposeContents(); @@ -2435,6 +2471,7 @@ private String processPvtMsgReplySentAction(PrivateMessage rrepMsg){ Map recipients = getRecipients(); + Long msgId; if(booleanSchedulerSend && !rrepMsg.getDraft()) { rrepMsg.setScheduledDate(openDate); schedulerMessage(rrepMsg, isSendEmail()); @@ -2444,11 +2481,13 @@ private String processPvtMsgReplySentAction(PrivateMessage rrepMsg){ List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + msgId = prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); + msgId = prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); } + manageTagAssociation(msgId); + if(!rrepMsg.getDraft()){ prtMsgManager.markMessageAsRepliedForUser(getReplyingMessage()); incrementSynopticToolInfo(recipients.keySet(), false); @@ -2832,9 +2871,11 @@ private void processPvtMsgForwardSendHelper(PrivateMessage rrepMsg){ List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + manageTagAssociation(msgId); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(rrepMsg, recipients, isSendEmail(), booleanReadReceipt); + manageTagAssociation(msgId); } if(!rrepMsg.getDraft()){ @@ -3124,9 +3165,11 @@ private PrivateMessage processPvtMsgReplyAllSendHelper(boolean preview, Boolean List draftRecipients = drDelegate.getDraftRecipients(getSelectedComposeToList(), courseMemberMap); List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); - prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(rrepMsg, getRecipients(), isSendEmail(), draftRecipients, draftBccRecipients, booleanReadReceipt); + manageTagAssociation(msgId); } else { - prtMsgManager.sendPrivateMessage(rrepMsg, returnSet, isSendEmail(), booleanReadReceipt); + Long msgId = prtMsgManager.sendPrivateMessage(rrepMsg, returnSet, isSendEmail(), booleanReadReceipt); + manageTagAssociation(msgId); } if(!rrepMsg.getDraft()){ @@ -3151,8 +3194,14 @@ private PrivateMessage processPvtMsgReplyAllSendHelper(boolean preview, Boolean return rrepMsg; } - - + private void manageTagAssociation(Long msgId) { + log.debug("msgId " + msgId + " - selectedTags " + selectedTags); + if (msgId != null && ServerConfigurationService.getBoolean("tagservice.enable.integrations", true) && isInstructor() && selectedTags != null) { + List tagIds = Arrays.asList(selectedTags.split(",")); + tagService.updateTagAssociations(getUserId(), String.valueOf(msgId), tagIds, false); + selectedTags = String.join(",", tagService.getTagAssociationIds(getUserId(), String.valueOf(msgId))); + } + } private boolean containedInList(User user,List list){ @@ -3504,6 +3553,15 @@ public boolean isEmailPermit() { return prtMsgManager.isEmailPermit(); } + public boolean isCanUseTags() { + boolean tagServiceEnabled = ServerConfigurationService.getBoolean(TagService.TAGSERVICE_ENABLED_INTEGRATION_PROP, TagService.TAGSERVICE_ENABLED_INTEGRATION_DEFAULT); + boolean manageTagsAllowed = securityService.unlock(userDirectoryService.getCurrentUser(), TagService.TAGSERVICE_MANAGE_PERMISSION, getContextSiteId()); + + log.debug("IsTagServiceEnabled:{}|HasCurrentUserTagPermission:{}", tagServiceEnabled, manageTagsAllowed); + + return tagServiceEnabled && manageTagsAllowed; + } + public String processPvtMsgOrganize() { log.debug("processPvtMsgOrganize()"); @@ -4047,6 +4105,10 @@ public String processSearch() setErrorMessage(getResourceBundleString(ENTER_SEARCH_TEXT)); } + if(searchOnTags && StringUtils.isEmpty(selectedTags)) { + setErrorMessage(getResourceBundleString(ENTER_SEARCH_TAGS)); + } + if(searchToDate != null){ searchToDate = Date.from(searchToDate.toInstant().plus(23, ChronoUnit.HOURS).plus(59, ChronoUnit.MINUTES).plusSeconds(59)); } @@ -4055,6 +4117,14 @@ public String processSearch() getSearchText(), getSearchFromDate(), getSearchToDate(), getSelectedSearchLabel(), searchOnSubject, searchOnAuthor, searchOnBody, searchOnLabel, searchOnDate) ; + List selectedTagsList = selectedTags != null ? Arrays.asList(selectedTags.split(",")) : new ArrayList<>(); + if(searchOnTags && CollectionUtils.isNotEmpty(selectedTagsList)) { + tempPvtMsgLs = ((List)tempPvtMsgLs).stream().filter(pm -> { + List tagIds = tagService.getTagAssociationIds(getUserId(), String.valueOf(pm.getId())); + return (tagIds.containsAll(selectedTagsList)); + }).collect(Collectors.toList()); + } + newls= createDecoratedDisplay(tempPvtMsgLs); //set threaded view as false in search @@ -4086,10 +4156,12 @@ public String processClearSearch() searchOnLabel= false ; searchOnAuthor=false; searchOnDate=false; + searchOnTags=false; searchFromDate=null; searchToDate=null; searchFromDateString=null; searchToDateString=null; + selectedTags = ""; return DISPLAY_MESSAGES_PG; } @@ -4105,6 +4177,8 @@ public String processClearSearch() @Getter @Setter public boolean searchOnDate=false; @Getter @Setter + public boolean searchOnTags=false; + @Getter @Setter public Date searchFromDate; @Getter @Setter public Date searchToDate; @@ -4157,6 +4231,9 @@ public List createDecoratedDisplay(List msg) } dbean.setSendToStringDecorated(createDecoratedSentToDisplay(dbean)); + List tagLabels = tagService.getAssociatedTagsForItem(getUserId(), String.valueOf(element.getId())).stream().map(Tag::getTagLabel).collect(Collectors.toList()); + dbean.setTagList(tagLabels); + decLs.add(dbean) ; } //reset selectAll flag, for future use @@ -4491,6 +4568,8 @@ public boolean isDetailMessagePublishableToFaq() { public void setMsgNavMode(String msgNavMode) { this.msgNavMode = msgNavMode; + this.selectedTags = ""; + this.fromPreview = false; } /** @@ -4626,6 +4705,8 @@ private void setFromMainOrHp() { fromMainOrHp = fromPage; } + this.selectedTags = ""; + this.fromPreview = false; } @SuppressWarnings("unchecked") @@ -4911,6 +4992,7 @@ private void schedulerMessage(PrivateMessage pMsg, boolean asEmail) { List draftBccRecipients = drDelegate.getDraftRecipients(getSelectedComposeBccList(), courseMemberMap); prtMsgManager.sendProgamMessage(pMsg, draftRecipients, draftBccRecipients, asEmail); PrivateMessageSchedulerService.scheduleDueDateReminder(pMsg.getId()); + manageTagAssociation(pMsg.getId()); } public void setOpenDate(String openDateStr){ diff --git a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/PrivateMessageDecoratedBean.java b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/PrivateMessageDecoratedBean.java index dd0a2d8ab218..7992f364b541 100644 --- a/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/PrivateMessageDecoratedBean.java +++ b/msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/PrivateMessageDecoratedBean.java @@ -59,6 +59,7 @@ public void setIsSelected(boolean isSelected) { private PrivateMessageDecoratedBean previewReplyTmpMsg; private boolean isReplied = false; + private List tagList = new ArrayList<>(); public PrivateMessage getMsg() { @@ -233,6 +234,13 @@ public void setReplied(boolean isReplied) { this.isReplied = isReplied; } + public List getTagList() { + return tagList; + } + + public void setTagList(List tagList) { + this.tagList = tagList; + } } diff --git a/msgcntr/messageforums-app/src/webapp/js/messages.js b/msgcntr/messageforums-app/src/webapp/js/messages.js index cf0ceb6679d2..7d9aee34c15e 100644 --- a/msgcntr/messageforums-app/src/webapp/js/messages.js +++ b/msgcntr/messageforums-app/src/webapp/js/messages.js @@ -92,6 +92,13 @@ function addTagSelector(obj) { } } +function initTagSelector(view) { + const tagsInputId = view + ":tag_selector"; + const tagSelectorId = "tag-selector"; + + window.syncTagSelectorInput(tagSelectorId, tagsInputId); +} + $(document).ready(function(){ $("#prefs_pvt_form\\:pvtmsgs\\:checkAll, #prefs_pvt_form\\:threaded_pvtmsgs\\:checkAll").click(function () { @@ -111,6 +118,10 @@ $(document).ready(function(){ $('#prefs_pvt_form\\:pvt_selected_label').toggleClass('showed'); }); + $('#prefs_pvt_form\\:search_by_tags').change( function(){ + $('#prefs_pvt_form\\:pvt_selected_tags').toggleClass('showed'); + }); + $('#prefs_pvt_form\\:advanced_search_button').mousedown( function(){ var searchByDateCheckbox = $('#prefs_pvt_form\\:search_by_date'); if(searchByDateCheckbox.is(":checked") && !$('#prefs_pvt_form\\:pvt_beg_date').hasClass('showed')){ @@ -121,6 +132,11 @@ $(document).ready(function(){ if(searchByLabelCheckbox.is(":checked") && !$('#prefs_pvt_form\\:pvt_selected_label').hasClass('showed')){ $('#prefs_pvt_form\\:pvt_selected_label').toggleClass('showed'); } + + var searchByTagsCheckbox = $('#prefs_pvt_form\\:search_by_tags'); + if(searchByTagsCheckbox.is(":checked") && !$('#prefs_pvt_form\\:pvt_selected_tags').hasClass('showed')){ + $('#prefs_pvt_form\\:pvt_selected_tags').toggleClass('showed'); + } }); }); diff --git a/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp b/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp index 4ce90d92ac57..54f556d2ec75 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/compose.jsp @@ -69,6 +69,10 @@ var menuLinkSpan = menuLink.closest('span'); menuLinkSpan.addClass('current'); menuLinkSpan.html(menuLink.text()); + + + initTagSelector("compose"); + }); <%@ include file="/jsp/privateMsg/pvtMenu.jsp" %> @@ -370,6 +374,20 @@ --%> + +

+ + + + diff --git a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/msgHeader.jsp b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/msgHeader.jsp index e30e7b9eb88d..d0c130c1b63c 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/msgHeader.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/msgHeader.jsp @@ -78,6 +78,11 @@ + + + + + @@ -103,7 +108,25 @@ - + + + + + +
+
+ + +
+
+
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsg.jsp b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsg.jsp index 68fc7e6246df..7469593bb908 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsg.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsg.jsp @@ -5,7 +5,7 @@ <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://sakaiproject.org/jsf2/sakai" prefix="sakai" %> <%@ taglib uri="http://sakaiproject.org/jsf/messageforums" prefix="mf" %> - +<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> @@ -61,7 +61,8 @@ {"bSortable": true, "bSearchable": true}, - {"bSortable": true, "bSearchable": true} + {"bSortable": true, "bSearchable": true}, + ], "language": { "search": , @@ -96,7 +97,8 @@ {"bSortable": true, "bSearchable": true}, - {"bSortable": true, "bSearchable": true} + {"bSortable": true, "bSearchable": true}, + ], "language": { "search": , @@ -116,6 +118,10 @@ } }); } + + + initTagSelector("prefs_pvt_form"); + }); @@ -236,6 +242,16 @@ + + + + + + + + + + @@ -333,6 +349,16 @@ + + + + + + + + + + <%-- Added if user clicks Check All --%> diff --git a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgDetail.jsp b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgDetail.jsp index eb0d4466f591..a000b6681055 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgDetail.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgDetail.jsp @@ -43,6 +43,10 @@ }); $(this).prop('href', 'javascript:;'); }); + + + initTagSelector("pvtMsgDetail"); + }); <%@ include file="/jsp/privateMsg/pvtMenu.jsp" %> @@ -234,6 +238,28 @@ + +

+ + + + tabindex="-1" + + selected-temp='' + collection-id='' + item-id='' + site-id='' + tool='' + add-new="true" + > +
+ + + + +
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgEx.jsp b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgEx.jsp index 723108e2b913..b7c94b539917 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgEx.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/privateMsg/pvtMsgEx.jsp @@ -2,6 +2,7 @@ <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://sakaiproject.org/jsf2/sakai" prefix="sakai" %> <%@ taglib uri="http://sakaiproject.org/jsf/messageforums" prefix="mf" %> +<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> @@ -22,6 +23,10 @@ menuLinkSpan.addClass('current'); menuLinkSpan.html(menuLink.text()); }); + + + initTagSelector("prefs_pvt_form"); + @@ -135,6 +140,16 @@ + + + + + + + + + +
@@ -233,6 +248,16 @@ + + + + + + + + + +
diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp index 542fea65e39c..688895351fea 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgForward.jsp @@ -72,6 +72,10 @@ var menuLinkSpan = menuLink.closest('span'); menuLinkSpan.addClass('current'); menuLinkSpan.html(menuLink.text()); + + + initTagSelector("pvtMsgForward"); + }); @@ -355,8 +359,21 @@ - - + + +

+ + +
+ diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp index 1b5942f5d943..353febd2d391 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReply.jsp @@ -71,6 +71,9 @@ menuLinkSpan.addClass('current'); menuLinkSpan.html(menuLink.text()); + + initTagSelector("pvtMsgReply") + }); @@ -385,8 +388,21 @@ - - + + +

+ + +
+ diff --git a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp index 6351c7a8bca7..107a6c2b4bdd 100644 --- a/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp +++ b/msgcntr/messageforums-app/src/webapp/jsp/pvtMsgReplyAll.jsp @@ -73,6 +73,10 @@ var menuLinkSpan = menuLink.closest('span'); menuLinkSpan.addClass('current'); menuLinkSpan.html(menuLink.text()); + + + initTagSelector("pvtMsgForward"); + }); @@ -387,6 +391,19 @@ + +

+ + +
diff --git a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java index 3eddbd05cfc4..48ef871d2b82 100644 --- a/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java +++ b/msgcntr/messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/ui/PrivateMessageManagerImpl.java @@ -1231,12 +1231,12 @@ private String getSystemAndReplyEmail(String defaultEmail, User currentUser, Mes /** * @see org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager#sendPrivateMessage(org.sakaiproject.api.app.messageforums.PrivateMessage, java.util.Set, boolean, boolean) */ - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt) { - sendPrivateMessage(message, recipients, asEmail, Collections.emptyList(), Collections.emptyList(), readReceipt); + public Long sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, boolean readReceipt) { + return sendPrivateMessage(message, recipients, asEmail, Collections.emptyList(), Collections.emptyList(), readReceipt); } @Override - public void sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, + public Long sendPrivateMessage(PrivateMessage message, Map recipients, boolean asEmail, List draftRecipients, List draftBccRecipients, boolean readReceipt) { try @@ -1254,7 +1254,7 @@ public void sendPrivateMessage(PrivateMessage message, Map recipi /** for no just return out throw new IllegalArgumentException("Empty recipient list"); **/ - return; + return null; } String contextId=""; @@ -1282,7 +1282,7 @@ public void sendPrivateMessage(PrivateMessage message, Map recipi messageManager.deleteDraftRecipientsByMessageId(savedMessage.getId()); messageManager.saveDraftRecipients(savedMessage.getId(), allDraftRecipients); - return; + return savedMessage.getId(); } //build the message body @@ -1362,12 +1362,14 @@ public void sendPrivateMessage(PrivateMessage message, Map recipi bodyString, null, replyEmail.toArray(new InternetAddress[replyEmail.size()]), additionalHeaders); } - + return savedMessage.getId(); } catch (MessagingException e) { log.warn("PrivateMessageManagerImpl.sendPrivateMessage: exception: " + e.getMessage(), e); } + + return null; } public boolean isEmailForwardDisabled(){ diff --git a/pack/pom.xml b/pack/pom.xml index a07ce8ed10b1..0d03d56b52eb 100644 --- a/pack/pom.xml +++ b/pack/pom.xml @@ -40,6 +40,7 @@ false + test @@ -51,6 +52,7 @@ skip-tests true + none diff --git a/pom.xml b/pom.xml index fee56bbf6e40..5cb3cad4a628 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,7 @@ userauditservice usermembership velocity + vuecomponents web webapi webcomponents diff --git a/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeStandardHead.vm b/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeStandardHead.vm index 85a1582bc475..ba75ba7a406f 100644 --- a/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeStandardHead.vm +++ b/portal/portal-render-engine-impl/impl/src/webapp/vm/morpheus/includeStandardHead.vm @@ -91,6 +91,7 @@ + diff --git a/vuecomponents/tool/src/main/frontend/src/components/tag-selector.vue b/vuecomponents/tool/src/main/frontend/src/components/tag-selector.vue new file mode 100644 index 000000000000..1c125e5eb151 --- /dev/null +++ b/vuecomponents/tool/src/main/frontend/src/components/tag-selector.vue @@ -0,0 +1,230 @@ + + + + + + diff --git a/vuecomponents/tool/src/main/frontend/src/mixins/i18n-mixin.js b/vuecomponents/tool/src/main/frontend/src/mixins/i18n-mixin.js new file mode 100644 index 000000000000..18415bcc10f3 --- /dev/null +++ b/vuecomponents/tool/src/main/frontend/src/mixins/i18n-mixin.js @@ -0,0 +1,22 @@ +//For usage information, go the tutorial at vuecomponents/docs/i18n.md +import { loadProperties } from "../../../../../../../webcomponents/tool/src/main/frontend/js/sakai-i18n.js"; + +export default { + methods: { + getI18nProps(componentName) { + loadProperties(componentName) + .then((response) => { + this.i18n = response; + }) + .catch((reason) => { + console.error("I18n strings could not be retrieved -", reason); + }); + }, + }, + created() { + this.getI18nProps(this.i18nProps || this.$options.name); + }, + data() { + return { i18n: {} }; + }, +}; diff --git a/vuecomponents/tool/src/main/webapp/WEB-INF/applicationContext.xml b/vuecomponents/tool/src/main/webapp/WEB-INF/applicationContext.xml new file mode 100644 index 000000000000..9e99d9be9086 --- /dev/null +++ b/vuecomponents/tool/src/main/webapp/WEB-INF/applicationContext.xml @@ -0,0 +1,5 @@ + + + diff --git a/vuecomponents/tool/src/main/webapp/WEB-INF/web.xml b/vuecomponents/tool/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000000..2c196675e702 --- /dev/null +++ b/vuecomponents/tool/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,25 @@ + + + vuecomponents + Sakai Vue Components + + + + sakai.request + org.sakaiproject.util.RequestFilter + + + sakai.request + /* + REQUEST + FORWARD + INCLUDE + + + + org.sakaiproject.util.SakaiContextLoaderListener + + diff --git a/webapi/pom.xml b/webapi/pom.xml index 9ff6af769e56..8f999c6fd01a 100644 --- a/webapi/pom.xml +++ b/webapi/pom.xml @@ -92,6 +92,10 @@ sakai-calendar-util 24-SNAPSHOT
+ + org.sakaiproject.tags + tags-api + org.sakaiproject.conversations sakai-conversations-api diff --git a/webapi/src/main/java/org/sakaiproject/webapi/controllers/TagsController.java b/webapi/src/main/java/org/sakaiproject/webapi/controllers/TagsController.java new file mode 100644 index 000000000000..ddabc297f619 --- /dev/null +++ b/webapi/src/main/java/org/sakaiproject/webapi/controllers/TagsController.java @@ -0,0 +1,76 @@ +/****************************************************************************** + * Copyright 2023 sakaiproject.org 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/ECL-2.0 + * + * 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.webapi.controllers; + +import org.apache.commons.lang3.StringUtils; +import org.sakaiproject.assignment.api.AssignmentConstants; +import org.sakaiproject.authz.api.SecurityService; +import org.sakaiproject.site.api.Site; +import org.sakaiproject.site.api.SiteService; +import org.sakaiproject.site.api.ToolConfiguration; +import org.sakaiproject.tags.api.Tag; +import org.sakaiproject.tags.api.TagService; +import org.sakaiproject.webapi.exception.ForbiddenAccessException; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +public class TagsController extends AbstractSakaiApiController { + + @Resource + private TagService tagService; + @Resource + private SecurityService securityService; + + @GetMapping(value = "/sites/{siteId}/tools/{tool}/tags/{collectionId}/items/{itemId}", produces = MediaType.APPLICATION_JSON_VALUE) + public Iterable getTagsForItem(@PathVariable String siteId, @PathVariable String tool, @PathVariable String collectionId, @PathVariable String itemId) { + checkSakaiSession(); + checkAccess(siteId, tool); + + return tagService.getAssociatedTagsForItem(collectionId, itemId); + } + + @GetMapping(value = "/sites/{siteId}/tools/{tool}/tags/{collectionId}", produces = MediaType.APPLICATION_JSON_VALUE) + public Iterable getTagsForCollection(@PathVariable String siteId, @PathVariable String tool, @PathVariable String collectionId) { + checkSakaiSession(); + checkAccess(siteId, tool); + + return tagService.getTags().getAllInCollection(collectionId); + } + + private void checkAccess(String siteId, String tool) { + if (StringUtils.isNotEmpty(siteId) && StringUtils.isNotEmpty(tool)) { + if (securityService.unlock(TagService.TAGSERVICE_MANAGE_PERMISSION, "/site/" + siteId)) { + return; + } + if (tool.equals(TagService.TOOL_ASSIGNMENTS)) { + Site site = checkSite(siteId); + ToolConfiguration tc = site.getToolForCommonId(AssignmentConstants.TOOL_ID); + String optionTagsValue = tc.getPlacementConfig().getProperty(AssignmentConstants.SHOW_TAGS_STUDENT); + if (Boolean.TRUE.equals(optionTagsValue) && securityService.unlock(SiteService.SITE_VISIT, "/site/" + siteId)) { + return; + } + } + } + throw new ForbiddenAccessException(); + } + +} diff --git a/webapi/src/main/java/org/sakaiproject/webapi/exception/ForbiddenAccessException.java b/webapi/src/main/java/org/sakaiproject/webapi/exception/ForbiddenAccessException.java new file mode 100644 index 000000000000..9ad23c5af645 --- /dev/null +++ b/webapi/src/main/java/org/sakaiproject/webapi/exception/ForbiddenAccessException.java @@ -0,0 +1,36 @@ +/****************************************************************************** + * Copyright 2023 sakaiproject.org 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/ECL-2.0 + * + * 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.webapi.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Forbidden Access") +public class ForbiddenAccessException extends RuntimeException { + public ForbiddenAccessException() { + super(); + } + + public ForbiddenAccessException(Throwable cause) { + super(cause); + } + + public ForbiddenAccessException(String message, Throwable cause) { + super(message, cause); + } + + public ForbiddenAccessException(String message) { + super(message); + } +}