SEBSERV-75 allow moodle courses and quizzes to be used as exams

This commit is contained in:
anhefti 2020-08-04 11:40:21 +02:00
parent 640211649c
commit dbaffff5c3
6 changed files with 208 additions and 42 deletions

View file

@ -47,6 +47,7 @@ public class MoodleCourseAccess extends CourseAccess {
private static final Logger log = LoggerFactory.getLogger(MoodleCourseAccess.class);
private static final String MOODLE_QUIZ_START_URL_PATH = "mod/quiz/view.php?id=";
private static final String MOODLE_COURSE_START_URL_PATH = "course/view.php?id=";
private static final String MOODLE_COURSE_API_FUNCTION_NAME = "core_course_get_courses";
private static final String MOODLE_USER_PROFILE_API_FUNCTION_NAME = "core_user_get_users_by_field";
private static final String MOODLE_QUIZ_API_FUNCTION_NAME = "mod_quiz_get_quizzes_by_courses";
@ -55,6 +56,7 @@ public class MoodleCourseAccess extends CourseAccess {
private final JSONMapper jsonMapper;
private final LmsSetup lmsSetup;
private final MoodleRestTemplateFactory moodleRestTemplateFactory;
private final boolean includeCourses;
private MoodleAPIRestTemplate restTemplate;
@ -62,12 +64,14 @@ public class MoodleCourseAccess extends CourseAccess {
final JSONMapper jsonMapper,
final LmsSetup lmsSetup,
final MoodleRestTemplateFactory moodleRestTemplateFactory,
final AsyncService asyncService) {
final AsyncService asyncService,
final boolean includeCourses) {
super(asyncService);
this.jsonMapper = jsonMapper;
this.lmsSetup = lmsSetup;
this.moodleRestTemplateFactory = moodleRestTemplateFactory;
this.includeCourses = includeCourses;
}
@Override
@ -222,7 +226,7 @@ public class MoodleCourseAccess extends CourseAccess {
static Map<String, String> additionalAttrs = new HashMap<>();
private static List<QuizData> quizDataOf(
private List<QuizData> quizDataOf(
final LmsSetup lmsSetup,
final CourseData courseData,
final String uriPrefix) {
@ -234,13 +238,13 @@ public class MoodleCourseAccess extends CourseAccess {
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_DISPLAY_NAME, courseData.display_name);
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_SUMMARY, courseData.summary);
return courseData.quizzes
final List<QuizData> courseAndQuiz = courseData.quizzes
.stream()
.map(courseQuizData -> {
final String startURI = uriPrefix + courseQuizData.id;
final String startURI = uriPrefix + courseQuizData.course_module;
additionalAttrs.put(QuizData.ATTR_ADDITIONAL_TIME_LIMIT, String.valueOf(courseQuizData.time_limit));
return new QuizData(
courseQuizData.id,
courseData.id + ":" + courseQuizData.id,
lmsSetup.getInstitutionId(),
lmsSetup.id,
lmsSetup.getLmsType(),
@ -252,6 +256,22 @@ public class MoodleCourseAccess extends CourseAccess {
additionalAttrs);
})
.collect(Collectors.toList());
if (this.includeCourses) {
courseAndQuiz.add(new QuizData(
courseData.id,
lmsSetup.getInstitutionId(),
lmsSetup.id,
lmsSetup.getLmsType(),
courseData.display_name,
courseData.full_name,
Utils.toDateTimeUTCUnix(courseData.start_date),
Utils.toDateTimeUTCUnix(courseData.end_date),
lmsSetup.lmsApiUrl + MOODLE_COURSE_START_URL_PATH + courseData.id,
additionalAttrs));
}
return courseAndQuiz;
}
private Result<MoodleAPIRestTemplate> getRestTemplate() {

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle;
import java.util.ArrayList;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.LinkedMultiValueMap;
@ -27,11 +28,27 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.moodle.MoodleRestT
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123
*
* Response (JSON):
* {"courseId"="123", "configKeys"=["key1","key2","key3",...], "browserKeys"=["bkey1", "bkey2", "bkey3",...]}
*
* <pre>
* {
* "courseId": "123",
* "quizId": "456",
* "configKeys": [
* "key1",
* "key2",
* "key3"
* ],
* "browserKeys": [
* "bkey1",
* "bkey2",
* "bkey3"
* ]
* }
* </pre>
*
* Set keys:
* POST:
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction&courseId=123&configKey[0]=key1&configKey[1]=key2&browserKey[0]=bkey1&browserKey[1]=bkey2
* http://yourmoodle.org/webservice/rest/server.php?wstoken={token}&moodlewsrestformat=json&wsfunction=seb_restriction_update&courseId=123&configKey[0]=key1&configKey[1]=key2&browserKey[0]=bkey1&browserKey[1]=bkey2
*
* Delete all key (and remove restrictions):
* POST:
@ -41,8 +58,11 @@ public class MoodleCourseRestriction {
private static final Logger log = LoggerFactory.getLogger(MoodleCourseRestriction.class);
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION = "seb_restriction";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_CREATE = "seb_restriction_create";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_UPDATE = "seb_restriction_update";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE = "seb_restriction_delete";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID = "courseId";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID = "quizId";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY = "configKey";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY = "browserKey";
@ -64,10 +84,29 @@ public class MoodleCourseRestriction {
return LmsSetupTestResult.ofQuizAccessAPIError("not available yet");
}
Result<MoodleSEBRestriction> getSEBRestriction(final String courseId) {
Result<MoodleSEBRestriction> getSEBRestriction(
final String externalId) {
return Result.tryCatch(() -> {
final String[] courseQuizId = StringUtils.split(externalId, ":");
if (courseQuizId.length > 1) {
// we only have the course id (this is a course)
return getSEBRestriction(courseQuizId[0], null)
.getOrThrow();
} else {
// we have the course id and the quiz is (this is a quiz)
return getSEBRestriction(courseQuizId[0], courseQuizId[1])
.getOrThrow();
}
});
}
Result<MoodleSEBRestriction> getSEBRestriction(
final String courseId,
final String quizId) {
if (log.isDebugEnabled()) {
log.debug("GET SEB Client restriction on course: {}", courseId);
log.debug("GET SEB Client restriction on course: {} quiz: {}", courseId, quizId);
}
return Result.tryCatch(() -> {
@ -77,6 +116,9 @@ public class MoodleCourseRestriction {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
if (quizId != null) {
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID, quizId);
}
final String resultJSON = template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION,
@ -92,43 +134,103 @@ public class MoodleCourseRestriction {
});
}
Result<Boolean> putSEBRestriction(
final String courseId,
Result<MoodleSEBRestriction> createSEBRestriction(
final String externalId,
final MoodleSEBRestriction restriction) {
if (log.isDebugEnabled()) {
log.debug("PUT SEB Client restriction on course: {} : {}", courseId, restriction);
}
return Result.tryCatch(() -> {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY,
new ArrayList<>(restriction.configKeys));
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY,
new ArrayList<>(restriction.browserExamKeys));
template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION,
queryParams,
queryAttributes);
return true;
final String[] courseQuizId = StringUtils.split(externalId, ":");
if (courseQuizId.length > 1) {
// we only have the course id (this is a course)
return createSEBRestriction(courseQuizId[0], null, restriction)
.getOrThrow();
} else {
// we have the course id and the quiz is (this is a quiz)
return createSEBRestriction(courseQuizId[0], courseQuizId[1], restriction)
.getOrThrow();
}
});
}
Result<Boolean> deleteSEBRestriction(final String courseId) {
Result<MoodleSEBRestriction> createSEBRestriction(
final String courseId,
final String quizId,
final MoodleSEBRestriction restriction) {
if (log.isDebugEnabled()) {
log.debug("DELETE SEB Client restriction on course: {}", courseId);
log.debug("POST SEB Client restriction on course: {} quiz: restriction : {}",
courseId,
quizId,
restriction);
}
return postSEBRestriction(
courseId,
quizId,
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_CREATE,
restriction);
}
Result<MoodleSEBRestriction> updateSEBRestriction(
final String externalId,
final MoodleSEBRestriction restriction) {
return Result.tryCatch(() -> {
final String[] courseQuizId = StringUtils.split(externalId, ":");
if (courseQuizId.length > 1) {
// we only have the course id (this is a course)
return updateSEBRestriction(courseQuizId[0], null, restriction)
.getOrThrow();
} else {
// we have the course id and the quiz is (this is a quiz)
return updateSEBRestriction(courseQuizId[0], courseQuizId[1], restriction)
.getOrThrow();
}
});
}
Result<MoodleSEBRestriction> updateSEBRestriction(
final String courseId,
final String quizId,
final MoodleSEBRestriction restriction) {
if (log.isDebugEnabled()) {
log.debug("POST SEB Client restriction on course: {} quiz: restriction : {}",
courseId,
quizId,
restriction);
}
return postSEBRestriction(
courseId,
quizId,
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_UPDATE,
restriction);
}
Result<Boolean> deleteSEBRestriction(
final String externalId) {
return Result.tryCatch(() -> {
final String[] courseQuizId = StringUtils.split(externalId, ":");
if (courseQuizId.length > 1) {
// we only have the course id (this is a course)
return deleteSEBRestriction(courseQuizId[0], null)
.getOrThrow();
} else {
// we have the course id and the quiz is (this is a quiz)
return deleteSEBRestriction(courseQuizId[0], courseQuizId[1])
.getOrThrow();
}
});
}
Result<Boolean> deleteSEBRestriction(
final String courseId,
final String quizId) {
if (log.isDebugEnabled()) {
log.debug("DELETE SEB Client restriction on course: {} quizId {}", courseId, quizId);
}
return Result.tryCatch(() -> {
@ -137,6 +239,7 @@ public class MoodleCourseRestriction {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID, quizId);
template.callMoodleAPIFunction(
MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE,
@ -161,4 +264,40 @@ public class MoodleCourseRestriction {
return Result.of(this.restTemplate);
}
private Result<MoodleSEBRestriction> postSEBRestriction(
final String courseId,
final String quizId,
final String function,
final MoodleSEBRestriction restriction) {
return Result.tryCatch(() -> {
final MoodleAPIRestTemplate template = getRestTemplate()
.getOrThrow();
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID, courseId);
queryParams.add(MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID, quizId);
final MultiValueMap<String, String> queryAttributes = new LinkedMultiValueMap<>();
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_CONFIG_KEY,
new ArrayList<>(restriction.configKeys));
queryAttributes.addAll(
MOODLE_DEFAULT_COURSE_RESTRICTION_BROWSER_KEY,
new ArrayList<>(restriction.browserExamKeys));
final String resultJSON = template.callMoodleAPIFunction(
function,
queryParams,
queryAttributes);
final MoodleSEBRestriction restrictiondata = this.jsonMapper.readValue(
resultJSON,
new TypeReference<MoodleSEBRestriction>() {
});
return restrictiondata;
});
}
}

View file

@ -115,7 +115,7 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
}
return this.moodleCourseRestriction
.putSEBRestriction(
.updateSEBRestriction(
externalExamId,
MoodleSEBRestriction.from(sebRestrictionData))
.map(result -> sebRestrictionData);
@ -127,7 +127,8 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
log.debug("Release SEB Client restriction for Exam: {}", exam);
}
return this.moodleCourseRestriction.deleteSEBRestriction(exam.externalId)
return this.moodleCourseRestriction
.deleteSEBRestriction(exam.externalId)
.map(result -> exam);
}

View file

@ -34,13 +34,15 @@ public class MoodleLmsAPITemplateFactory {
private final ClientCredentialService clientCredentialService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final String[] alternativeTokenRequestPaths;
private final boolean includeCourses;
protected MoodleLmsAPITemplateFactory(
final JSONMapper jsonMapper,
final AsyncService asyncService,
final ClientCredentialService clientCredentialService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths) {
@Value("${sebserver.webservice.lms.moodle.api.token.request.paths:}") final String alternativeTokenRequestPaths,
@Value("${sebserver.webservice.lms.moodle.api.includeCourses:false}") final boolean includeCourses) {
this.jsonMapper = jsonMapper;
this.asyncService = asyncService;
@ -49,6 +51,7 @@ public class MoodleLmsAPITemplateFactory {
this.alternativeTokenRequestPaths = (alternativeTokenRequestPaths != null)
? StringUtils.split(alternativeTokenRequestPaths, Constants.LIST_SEPARATOR)
: null;
this.includeCourses = includeCourses;
}
public Result<MoodleLmsAPITemplate> create(
@ -71,7 +74,8 @@ public class MoodleLmsAPITemplateFactory {
this.jsonMapper,
lmsSetup,
moodleRestTemplateFactory,
this.asyncService);
this.asyncService,
this.includeCourses);
final MoodleCourseRestriction moodleCourseRestriction = new MoodleCourseRestriction(
this.jsonMapper,

View file

@ -49,6 +49,7 @@ sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths=
sebserver.webservice.lms.moodle.api.includeCourses=true
sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
# NOTE: This is a temporary work-around for SEB Restriction API within Open edX SEB integration plugin to

View file

@ -51,4 +51,5 @@ sebserver.webservice.api.pagination.maxPageSize=500
# comma separated list of known possible OpenEdX API access token request endpoints
sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
sebserver.webservice.lms.moodle.api.includeCourses=false
sebserver.webservice.lms.address.alias=