SEBSERV-75 allow moodle courses and quizzes to be used as exams
This commit is contained in:
parent
640211649c
commit
dbaffff5c3
6 changed files with 208 additions and 42 deletions
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
Loading…
Reference in a new issue