Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ This is a boolean value, whose default is false, which controls whether or not s

`certification.extraUserProperties.enable = true`

# Conversion (Course end date)

When upgrading an existing installation to a version that supports the fixed course end date certificate variable,
apply [`conversion/add-course-end-date.sql`](conversion/add-course-end-date.sql) before starting Sakai if automatic
database updates are disabled.

# Conversion (Users of versions older than 12.0)
Due to the tool refactor, some tables were renamed and some classes were refactored, a conversion script is required to make it work in the 12.x version and newer.

Expand All @@ -44,4 +50,3 @@ The tool id has been changed for consistency:
```
UPDATE sakai_site_tool SET registration = 'sakai.certification' WHERE registration = 'com.rsmart.certification';
```

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.sakaiproject.certification.api;

import java.time.LocalDate;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -51,6 +52,7 @@ public class CertificateDefinition {
protected String description;
protected String siteId;
protected String expiryOffset;
protected LocalDate courseEndDate;
protected Date createDate;
/**
* The status of a CertificateDefinition is one of:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2003-2026 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.certification.api;

import java.time.LocalDate;
import java.util.Map;

/**
* Cross-field constraints for certificate definitions.
*/
public final class CertificateDefinitionConstraints {

private static final String COURSE_END_DATE_VARIABLE = "${" + VariableResolver.CERT_ENDDATE + "}";
private static final String ESCAPED_COURSE_END_DATE_VARIABLE = COURSE_END_DATE_VARIABLE.substring(1);

private CertificateDefinitionConstraints() {
}

public static boolean isCourseEndDateConfigurationValid(LocalDate courseEndDate,
Map<String, String> fieldValues) {
return courseEndDate != null || !usesCourseEndDateVariable(fieldValues);
}

public static boolean usesCourseEndDateVariable(Map<String, String> fieldValues) {
return fieldValues != null
&& (fieldValues.containsValue(COURSE_END_DATE_VARIABLE)
|| fieldValues.containsValue(ESCAPED_COURSE_END_DATE_VARIABLE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.sakaiproject.certification.api;

import java.io.InputStream;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -51,8 +52,11 @@ public interface CertificateService {
* @param cd
* @return
* @throws IdUnusedException
* @throws IncompleteCertificateDefinitionException if the course end date variable is mapped without a course
* end date
*/
public CertificateDefinition updateCertificateDefinition (CertificateDefinition cd) throws IdUnusedException;
public CertificateDefinition updateCertificateDefinition(CertificateDefinition cd)
throws IdUnusedException, IncompleteCertificateDefinitionException;

public void setDocumentTemplateService (DocumentTemplateService dts);

Expand Down Expand Up @@ -111,6 +115,27 @@ public CertificateDefinition createCertificateDefinition(String name, String des
String mimeType, InputStream template)
throws IdUsedException, UnsupportedTemplateTypeException, DocumentTemplateException;

/**
* Creates a new certificate definition with an optional date-only course end date.
*
* @param name the name of the certificate
* @param description a description of the certificate
* @param siteId the containing site
* @param progressHidden specifies whether site members can view their progress towards earning this certificate
* @param courseEndDate the course convening end date, or null if it is not configured
* @param fileName the filename associated with the template file
* @param mimeType the mimetype for the template file
* @param template an input stream containing the contents of the template file
* @return the new certificate definition
* @throws IdUsedException if the certificate name is already used in the site
* @throws UnsupportedTemplateTypeException if the template type is unsupported
* @throws DocumentTemplateException if the template cannot be stored
*/
public CertificateDefinition createCertificateDefinition(String name, String description, String siteId,
Boolean progressHidden, LocalDate courseEndDate,
String fileName, String mimeType, InputStream template)
throws IdUsedException, UnsupportedTemplateTypeException, DocumentTemplateException;

/**
* Populates the DocumentTemplate object for this CertificateDefinition.
*
Expand Down Expand Up @@ -158,9 +183,11 @@ public InputStream getTemplateFileInputStream(String resourceId)
* @param certificateDefinitionId
* @param fieldValues
* @throws IdUnusedException
* @throws IncompleteCertificateDefinitionException if the course end date variable is mapped without a course
* end date
*/
public void setFieldValues(String certificateDefinitionId, Map<String, String> fieldValues)
throws IdUnusedException;
throws IdUnusedException, IncompleteCertificateDefinitionException;

/**
* This sets the CertificateDefinitionStatus to ACTIVE or INACTIVE depending on the value of the boolean 'active'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public interface VariableResolver {
public static final String LAST_NAME = "recipient.lastname";
public static final String CERT_EXPIREDATE = "cert.expiredate";
public static final String CERT_AWARDDATE = "cert.date";
public static final String CERT_ENDDATE = "cert.enddate";

public Set<String> getVariableLabels();

Expand Down
1 change: 1 addition & 0 deletions conversion/add-course-end-date.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE certificate_definition ADD course_end_date DATE;
5 changes: 5 additions & 0 deletions impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ variable.firstname=first name of the recipient
variable.lastname=last name of the recipient
variable.expiration=expiration date
variable.issuedate=date of award
variable.courseEndDate=course end date
variable.unassigned=unassigned

report.table.header.duedate=Due Date for {0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@

public abstract class AbstractVariableResolver implements VariableResolver {

private final ResourceLoader messages = new ResourceLoader("org.sakaiproject.certification.Messages");
private final ResourceLoader messages;
private final HashMap<String, String> descriptions = new HashMap<>();

protected AbstractVariableResolver() {
this(new ResourceLoader("org.sakaiproject.certification.Messages"));
}

protected AbstractVariableResolver(ResourceLoader messages) {
this.messages = messages;
}

public void addVariable (String variable, String description) {
descriptions.put(variable, description);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,46 @@

package org.sakaiproject.certification.impl;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

import org.sakaiproject.certification.api.CertificateDefinition;
import org.sakaiproject.certification.api.VariableResolutionException;
import org.sakaiproject.util.ResourceLoader;

public class AwardVariableResolver extends AbstractVariableResolver {

private static final String MESSAGE_NAMEOFCERT = "variable.nameOfCert";
private static final String MESSAGE_COURSE_END_DATE = "variable.courseEndDate";
private static final String MESSAGE_UNASSIGNED = "variable.unassigned";

public AwardVariableResolver() {
this(new ResourceLoader("org.sakaiproject.certification.Messages"));
}

AwardVariableResolver(ResourceLoader messages) {
super(messages);
String name = getMessages().getString(MESSAGE_NAMEOFCERT);
String courseEndDate = getMessages().getString(MESSAGE_COURSE_END_DATE);
String unassigned = getMessages().getString(MESSAGE_UNASSIGNED);
addVariable(CERT_NAME, name);
addVariable(CERT_ENDDATE, courseEndDate);
addVariable (UNASSIGNED, unassigned);
}

public String getValue(CertificateDefinition certDef, String varLabel, String userId, boolean useCaching) throws VariableResolutionException {
if (CERT_NAME.equals(varLabel)) {
return certDef.getName();
} else if (CERT_ENDDATE.equals(varLabel)) {
LocalDate courseEndDate = certDef.getCourseEndDate();
if (courseEndDate == null) {
return "";
}

DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
.withLocale(getMessages().getLocale());
return dateFormatter.format(courseEndDate);
} else if (UNASSIGNED.equals(varLabel)) {
return "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.File;
import java.io.InputStream;
import java.text.DateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
Expand Down Expand Up @@ -53,6 +54,7 @@
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.certification.api.CertificateDefinition;
import org.sakaiproject.certification.api.CertificateDefinitionConstraints;
import org.sakaiproject.certification.api.CertificateDefinitionStatus;
import org.sakaiproject.certification.api.CertificateService;
import org.sakaiproject.certification.api.DocumentTemplate;
Expand Down Expand Up @@ -220,7 +222,10 @@ public Object doInHibernate(Session session) throws HibernateException {
deleteTemplateFile(cd.getDocumentTemplate().getResourceId());
}

public CertificateDefinition updateCertificateDefinition(final CertificateDefinition cd) throws IdUnusedException {
public CertificateDefinition updateCertificateDefinition(final CertificateDefinition cd)
throws IdUnusedException, IncompleteCertificateDefinitionException {
validateCourseEndDateConfiguration(cd.getCourseEndDate(), cd.getFieldValues());

CertificateDefinition retVal = null;
if (cd instanceof CertificateDefinition) {
retVal = (CertificateDefinition) cd;
Expand All @@ -234,6 +239,8 @@ public Object doInHibernate(Session session) {
CertificateDefinition cdhi = (CertificateDefinition) q.list().get(0);
cdhi.setName(cd.getName());
cdhi.setDescription(cd.getDescription());
cdhi.setCourseEndDate(cd.getCourseEndDate());
cdhi.setFieldValues(copyFieldValues(cd.getFieldValues()));
cdhi.setProgressHidden(cd.getProgressHidden());
session.update(cdhi);
return cdhi;
Expand All @@ -250,6 +257,14 @@ public CertificateDefinition createCertificateDefinition (final String name, fin
final String siteId, final Boolean progressHidden, final String fileName,
final String mimeType, final InputStream template)
throws IdUsedException, UnsupportedTemplateTypeException, DocumentTemplateException {
return createCertificateDefinition(name, description, siteId, progressHidden, null, fileName, mimeType, template);
}

public CertificateDefinition createCertificateDefinition (final String name, final String description,
final String siteId, final Boolean progressHidden,
final LocalDate courseEndDate, final String fileName,
final String mimeType, final InputStream template)
throws IdUsedException, UnsupportedTemplateTypeException, DocumentTemplateException {
CertificateDefinition cd = null;
try {
cd = (CertificateDefinition) getHibernateTemplate().execute(new HibernateCallback() {
Expand All @@ -261,6 +276,7 @@ public Object doInHibernate(Session session) throws HibernateException {
certificateDefinition.setDescription(description);
certificateDefinition.setName(name);
certificateDefinition.setSiteId(siteId);
certificateDefinition.setCourseEndDate(courseEndDate);
certificateDefinition.setProgressHidden(progressHidden);
certificateDefinition.setStatus(CertificateDefinitionStatus.UNPUBLISHED);
session.save(certificateDefinition);
Expand Down Expand Up @@ -537,14 +553,17 @@ private Object doSecureCertificateService(SecureCertificateServiceCallback callb
}
}

public void setFieldValues(String certificateDefinitionId, Map<String, String> fieldValues) throws IdUnusedException {
public void setFieldValues(String certificateDefinitionId, Map<String, String> fieldValues)
throws IdUnusedException, IncompleteCertificateDefinitionException {
CertificateDefinition cd = (CertificateDefinition)getCertificateDefinition(certificateDefinitionId);
cd.setFieldValues(fieldValues);
validateCourseEndDateConfiguration(cd.getCourseEndDate(), fieldValues);
cd.setFieldValues(copyFieldValues(fieldValues));
getHibernateTemplate().update(cd);
}

public void activateCertificateDefinition(String certificateDefinitionId, boolean active) throws IncompleteCertificateDefinitionException, IdUnusedException {
CertificateDefinition cd = (CertificateDefinition)getCertificateDefinition(certificateDefinitionId);
validateCourseEndDateConfiguration(cd.getCourseEndDate(), cd.getFieldValues());
if (cd.getDocumentTemplate() == null || cd.getName() == null || cd.getAwardCriteria() == null || cd.getFieldValues() == null)
{
throw new IncompleteCertificateDefinitionException ("incomplete certificate definition");
Expand All @@ -554,6 +573,18 @@ public void activateCertificateDefinition(String certificateDefinitionId, boolea
getHibernateTemplate().update(cd);
}

private void validateCourseEndDateConfiguration(LocalDate courseEndDate, Map<String, String> fieldValues)
throws IncompleteCertificateDefinitionException {
if (!CertificateDefinitionConstraints.isCourseEndDateConfigurationValid(courseEndDate, fieldValues)) {
throw new IncompleteCertificateDefinitionException(
"course end date is required when the course end date variable is mapped");
}
}

private Map<String, String> copyFieldValues(Map<String, String> fieldValues) {
return fieldValues == null ? null : new HashMap<>(fieldValues);
}

private void setCriteriaFactoryOnCriteria(CertificateDefinition certDef) {
Set<Criterion> criteria = certDef.getAwardCriteria();
if (criteria != null) {
Expand Down
Loading
Loading