Improvements on bbb-lti (#6277)

* Fix listing multiple playbacks
* Fix localization
* Consider X-Forwarded-Proto header to determine which endpoint to use
* Allow custom context different than /lti
* Add pt_BR strings
This commit is contained in:
Felipe Cecagno 2019-02-04 15:49:30 -02:00 committed by Jesus Federico
parent 4fa0b0fa2c
commit 58761e1a74
8 changed files with 114 additions and 10 deletions

View File

@ -33,6 +33,7 @@ RUN cd /source \
&& sed -i "s|\(<lticp:name>\)[^<]*|\1$vendor_name|g" grails-app/controllers/org/bigbluebutton/ToolController.groovy \
&& sed -i "s|\(<lticp:description>\)[^<]*|\1$vendor_description|g" grails-app/controllers/org/bigbluebutton/ToolController.groovy \
&& sed -i "s|\(<lticp:url>\)[^<]*|\1$vendor_url|g" grails-app/controllers/org/bigbluebutton/ToolController.groovy \
&& sed -i "s|BigBlueButton|$title|g" grails-app/i18n/*.properties \
&& grails war
FROM tomcat:7-jre8
@ -44,9 +45,8 @@ RUN rm -r webapps/*
COPY --from=builder /source/target/lti-*.war webapps/lti.war
RUN unzip -q webapps/lti.war -d webapps/lti \
&& rm webapps/lti.war
COPY docker-entrypoint.sh /usr/local/bin/
ENV LTI_CONTEXT_PATH lti
CMD ["docker-entrypoint.sh"]

View File

@ -1,7 +1,13 @@
#!/bin/bash -xe
export JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -DbigbluebuttonSalt=$BIGBLUEBUTTON_SHARED_SECRET -DbigbluebuttonURL=$BIGBLUEBUTTON_URL -DltiEndPoint=$LTI_ENDPOINT -DltiConsumers=$LTI_CONSUMERS -DltiAllRecordedByDefault=$RECORDED_BY_DEFAULT"
export JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -DbigbluebuttonSalt=$BIGBLUEBUTTON_SHARED_SECRET -DbigbluebuttonURL=$BIGBLUEBUTTON_URL -DltiEndPoint=$LTI_ENDPOINT -DltiConsumers=$LTI_CONSUMERS -DltiAllRecordedByDefault=$RECORDED_BY_DEFAULT -DltiCanvasPlacements=$LTI_CANVAS_PLACEMENTS -DltiCanvasPlacementName=$LTI_CANVAS_PLACEMENT_NAME"
sed -i "s|^securerandom\.source=.*|securerandom.source=file:/dev/./urandom|g" $JAVA_HOME/lib/security/java.security
if [ -f webapps/lti.war ]; then
mkdir -p webapps/$LTI_CONTEXT_PATH
unzip -q webapps/lti.war -d webapps/$LTI_CONTEXT_PATH
rm webapps/lti.war
fi
catalina.sh run

View File

@ -44,6 +44,10 @@ ltiRestrictedAccess=true
# Sets all the meetings to be recorded by default
# Format: [<false>|true]
ltiAllRecordedByDefault=false
# The list of placements enabled on lti service.
# Format: assignment_menu,assignment_selection,assignments_menu,collaboration,course_navigation,course_home_sub_navigation,course_settings_sub_navigation
ltiCanvasPlacements=
ltiCanvasPlacementName=BigBlueButton
#----------------------------------------------------
# Inject configuration values into BigbluebuttonSrvice beans
@ -57,3 +61,5 @@ beans.ltiService.consumers=${ltiConsumers}
beans.ltiService.mode=${ltiMode}
beans.ltiService.restrictedAccess=${ltiRestrictedAccess}
beans.ltiService.recordedByDefault=${ltiAllRecordedByDefault}
beans.ltiService.canvasPlacements=${ltiCanvasPlacements}
beans.ltiService.canvasPlacementName=${ltiCanvasPlacementName}

View File

@ -60,7 +60,9 @@ class ToolController {
return
}
// On post request proceed with the launch.
def endPoint = ltiService.getScheme(request) + "://" + ltiService.endPoint + "/" + grailsApplication.metadata['app.name'] + "/" + params.get("controller") + (params.get("format") != null ? "." + params.get("format") : "")
def schemeHeader = request.getHeader("X-Forwarded-Proto")
def scheme = schemeHeader == null ? ltiService.getScheme(request) : schemeHeader
def endPoint = scheme + "://" + ltiService.endPoint + "/" + grailsApplication.metadata['app.name'] + "/" + params.get("controller") + (params.get("format") != null ? "." + params.get("format") : "")
log.info "endPoint: " + endPoint
ArrayList<String> missingParams = new ArrayList<String>()
@ -177,7 +179,7 @@ class ToolController {
private void setLocalization(Map<String, String> params){
String locale = params.get(Parameter.LAUNCH_LOCALE)
locale = (locale == null || locale.equals("")?"en":locale)
String[] localeCodes = locale.split("_")
String[] localeCodes = locale.split("[_-]")
// Localize the default welcome message
session['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE'] = new Locale(localeCodes[0])
if (localeCodes.length > 1) {
@ -326,10 +328,23 @@ class ToolController {
recording.put("unixDate", startTime / 1000)
// Add sanitized thumbnails
recording.put("thumbnails", sanitizeThumbnails(recording.playback.format))
recording.put("playbacks", sanitizePlayback(recording.playback.format))
}
return recordings
}
private List<Object> sanitizePlayback(Object format) {
def response = new ArrayList<Object>()
if (format instanceof Map<?,?>) {
response.add(format)
} else if (format instanceof Collection<?>) {
response = new ArrayList(format)
} else {
response = format
}
return response
}
private List<Object> sanitizeThumbnails(Object format) {
if (format.preview == null || format.preview.images == null || format.preview.images.image == null) {
return new ArrayList()
@ -347,6 +362,26 @@ class ToolController {
def icon = 'http://' + lti_endpoint + '/assets/icon.ico'
def secure_icon = 'https://' + lti_endpoint + '/assets/icon.ico'
def isSSLEnabled = ltiService.isSSLEnabled('https://' + lti_endpoint + '/tool/test')
def extension_url = isSSLEnabled ? secure_launch_url : launch_url
def extension_icon = isSSLEnabled ? secure_icon : icon
def canvasPlacements = ''
def canvasPlacementsList = ltiService.canvasPlacements
if (canvasPlacementsList.length > 0) {
canvasPlacements += '' +
' <blti:extensions platform="canvas.instructure.com">'
canvasPlacementsList.each { placement ->
canvasPlacements += '' +
' <lticm:options name="' + placement + '">' +
' <lticm:property name="canvas_icon_class">icon-lti</lticm:property>' +
' <lticm:property name="icon_url">' + extension_icon + '</lticm:property>' +
' <lticm:property name="text">' + ltiService.canvasPlacementName + '</lticm:property>' +
' <lticm:property name="url">' + extension_url + '</lticm:property>' +
' </lticm:options>'
}
canvasPlacements += '' +
' </blti:extensions>'
}
def cartridge = '' +
'<?xml version="1.0" encoding="UTF-8"?>' +
'<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"' +
@ -370,6 +405,7 @@ class ToolController {
' <lticp:description>Open source web conferencing system for distance learning.</lticp:description>' +
' <lticp:url>http://www.bigbluebutton.org/</lticp:url>' +
' </blti:vendor>' +
canvasPlacements +
' <cartridge_bundle identifierref="BLTI001_Bundle"/>' +
' <cartridge_icon identifierref="BLTI001_Icon"/>' +
'</cartridge_basiclti_link>'

View File

@ -27,8 +27,9 @@ tool.view.app=BigBlueButton
tool.view.title=BigBlueButton LTI Interface
tool.view.join=Join Meeting
tool.view.recording=Recording
tool.view.recording.format.presentation=presentation
tool.view.recording.format.video=video
tool.view.recording.format.presentation=Watch
tool.view.recording.format.video=Video
tool.view.recording.format.presentation_video=Download
tool.view.recording.delete.confirmation=Are you sure to permanently delete this recording?
tool.view.recording.delete.confirmation.warning=Warning
tool.view.recording.delete.confirmation.yes=Yes

View File

@ -0,0 +1,48 @@
#
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
#
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#
# The welcome.header can be static, however if you want the name of the activity (meeting) to be injected use {0} as part of the text
# {1} can be used to inject the name of the course
bigbluebutton.welcome.header=Bem-vindo à sala <b>{0}</b>!
bigbluebutton.welcome.footer=Para saber mais sobre o BigBlueButton, acesse nossos <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>vídeos tutoriais</u></a>.<br><br>Para habilitar o áudio, clique no ícone do hearset. <b>Utilize um headset para evitar ruídos na sessão.
bigbluebutton.welcome.record=Esta sessão está sendo gravada
bigbluebutton.welcome.duration=A duração máxima desta sessão é de {0} minutos
tool.view.app=BigBlueButton
tool.view.title=Inteface LTI do BigBlueButton
tool.view.join=Entrar na sessão
tool.view.recording=Gravação
tool.view.recording.format.presentation=Assistir
tool.view.recording.format.video=Vídeo
tool.view.recording.format.presentation_video=Baixar
tool.view.recording.delete.confirmation=Você tem certeza que deseja excluir permanentemente esta gravação?
tool.view.recording.delete.confirmation.warning=Aviso
tool.view.recording.delete.confirmation.yes=Sim
tool.view.recording.delete.confirmation.no=Não
tool.view.recording.publish=Publicar
tool.view.recording.unpublish=Esconder
tool.view.recording.delete=Excluir
tool.view.activity=Atividade
tool.view.description=Descrição
tool.view.preview=Preview
tool.view.date=Data
tool.view.duration=Duração
tool.view.actions=Ações
tool.view.dateFormat=E, MM dd, yyyy HH:mm:ss Z
tool.error.general=Não foi possível estabelecer a coenxão.

View File

@ -33,6 +33,8 @@ class LtiService {
def mode = "simple"
def restrictedAccess = "true"
def recordedByDefault = "false"
def canvasPlacements = ""
def canvasPlacementName = "BigBlueButton"
Map<String, String> consumerMap
@ -144,4 +146,9 @@ class LtiService {
def String getScheme(request) {
return request.isSecure() ? "https" : "http"
}
def String[] getCanvasPlacements() {
return this.canvasPlacements
}
}

View File

@ -41,8 +41,8 @@
<tr class="r0 lastrow">
<td class="cell c0" style="text-align:center;">
<g:if test="${r.published}">
<g:each in="${r.playback}" var="format">
<a title="<g:message code="tool.view.recording.format.${format.getValue().type}" />" target="_new" href="${format.getValue().url}"><g:message code="tool.view.recording.format.${format.getValue().type}" /></a>&#32;
<g:each in="${r.playbacks}" var="format">
<a title="<g:message code="tool.view.recording.format.${format.type}" />" target="_new" href="${format.url}"><g:message code="tool.view.recording.format.${format.type}" /></a><br>
</g:each>
</g:if>
</td>