From 58761e1a74e15cfbc4637e1f8d6c607df3d83c41 Mon Sep 17 00:00:00 2001
From: Felipe Cecagno <fcecagno@gmail.com>
Date: Mon, 4 Feb 2019 15:49:30 -0200
Subject: [PATCH] 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
---
 bbb-lti/Dockerfile                            |  6 +--
 bbb-lti/docker-entrypoint.sh                  |  8 +++-
 bbb-lti/grails-app/conf/lti-config.properties |  6 +++
 .../org/bigbluebutton/ToolController.groovy   | 40 +++++++++++++++-
 bbb-lti/grails-app/i18n/messages.properties   |  5 +-
 .../grails-app/i18n/messages_pt.properties    | 48 +++++++++++++++++++
 .../org/bigbluebutton/LtiService.groovy       |  7 +++
 bbb-lti/grails-app/views/tool/index.gsp       |  4 +-
 8 files changed, 114 insertions(+), 10 deletions(-)
 create mode 100644 bbb-lti/grails-app/i18n/messages_pt.properties

diff --git a/bbb-lti/Dockerfile b/bbb-lti/Dockerfile
index 4503f2d172..318681aa41 100644
--- a/bbb-lti/Dockerfile
+++ b/bbb-lti/Dockerfile
@@ -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"]
diff --git a/bbb-lti/docker-entrypoint.sh b/bbb-lti/docker-entrypoint.sh
index 608af0aa7a..83b353b124 100755
--- a/bbb-lti/docker-entrypoint.sh
+++ b/bbb-lti/docker-entrypoint.sh
@@ -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
 
diff --git a/bbb-lti/grails-app/conf/lti-config.properties b/bbb-lti/grails-app/conf/lti-config.properties
index 6984fce90d..7653c5d16a 100644
--- a/bbb-lti/grails-app/conf/lti-config.properties
+++ b/bbb-lti/grails-app/conf/lti-config.properties
@@ -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}
diff --git a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
index 87a85f9051..60c8c4dd53 100644
--- a/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
+++ b/bbb-lti/grails-app/controllers/org/bigbluebutton/ToolController.groovy
@@ -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>'
diff --git a/bbb-lti/grails-app/i18n/messages.properties b/bbb-lti/grails-app/i18n/messages.properties
index 4b708fbecb..87b6e6e670 100644
--- a/bbb-lti/grails-app/i18n/messages.properties
+++ b/bbb-lti/grails-app/i18n/messages.properties
@@ -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
diff --git a/bbb-lti/grails-app/i18n/messages_pt.properties b/bbb-lti/grails-app/i18n/messages_pt.properties
new file mode 100644
index 0000000000..5ab0946088
--- /dev/null
+++ b/bbb-lti/grails-app/i18n/messages_pt.properties
@@ -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.
diff --git a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
index 2465c8e330..b7d103bf73 100644
--- a/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
+++ b/bbb-lti/grails-app/services/org/bigbluebutton/LtiService.groovy
@@ -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
+    }
 }
+
diff --git a/bbb-lti/grails-app/views/tool/index.gsp b/bbb-lti/grails-app/views/tool/index.gsp
index e733636a2d..183e1c163c 100644
--- a/bbb-lti/grails-app/views/tool/index.gsp
+++ b/bbb-lti/grails-app/views/tool/index.gsp
@@ -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>
-- 
GitLab