Skip to content

Commit

Permalink
SAK-44809 - Fix Content Item Import for CC (sakaiproject#8917)
Browse files Browse the repository at this point in the history
* SAK-44809 - Fix Content Item Import for CC

* SAK-44809 - Add some documentation.

* SAK-44809 - UI cleanup - i18n strings

* SAK-44809 - Issues identified during testing.

* SAK-44809 - Thanks to accesslint
  • Loading branch information
csev authored Dec 23, 2020
1 parent c94860b commit f888d50
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 79 deletions.
6 changes: 6 additions & 0 deletions basiclti/basiclti-tool/src/bundle/ltitool.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gen.activate=Activate
gen.reactivate=Re-Activate
gen.toggle.priv=Toggle Private Key
gen.clipboard=Copy
gen.spinner.alt=Processing...

tool.title=External Tools (IMS LTI)
tool.in.site=Tool Links
Expand Down Expand Up @@ -107,9 +108,14 @@ error.contentitem.no.ltilink=Missing LtiLinkItem in ContentItemResponse @graph
error.contentitem.missing.url=LTILink Item missing launch url
error.contentitem.content.insert=Unable to insert ContentItem
error.contentitem.content.launch=Could not retrieve Launch URL from ContentItem
error.contentitem.no.fileitem==Missing FileItem in ContentItemResponse @graph
error.deeplink.bad=Error in Deep Link Response
error.deeplink.no.ltilink=Missing ltiResourceLink in Deep Link Response
error.deeplink.no.fileitem=Missing file item in Deep Link Response
error.deeplink.no.import.url=Could not find url to import in file item
import.cartridge.continue=Process Cartridge Import
link.add=Adding Site Nagivation Link
link.add.detail=<p>This will add a link to this tool to your site navigation. When you add the link, the entire page will reset to update the navigation.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public class LTIAdminTool extends VelocityPortletPaneledAction {
private static String STATE_CONTENT_ITEM_FAILURES = "lti:state_content_item_failures";
private static String STATE_CONTENT_ITEM_SUCCESSES = "lti:state_content_item_successes";
private static String STATE_LINE_ITEM = "lti:state_line_item";
private static String STATE_CONTENT_ITEM_CARTRIDGE_URL = "lti:state_content_item_cartridge_url";
private static String STATE_CONTENT_ITEM_IMPORT_RETURN_URL = "lti:state_content_item_import_return_url";

private static String ALLOW_MAINTAINER_ADD_SYSTEM_TOOL = "lti:allow_maintainer_add_system_tool";
private static String ALLOW_MAINTAINER_ADD_TOOL_SITE = "lti:allow_maintainer_add_tool_site";
Expand Down Expand Up @@ -146,6 +148,7 @@ public class LTIAdminTool extends VelocityPortletPaneledAction {
private static String FLOW_PARAMETER_LESSONS = "lessons";
private static String FLOW_PARAMETER_EDITOR = "editor";
private static String FLOW_PARAMETER_ASSIGNMENT = "assignment";
private static String FLOW_PARAMETER_IMPORT = "import";

/**
* Service Implementations
Expand Down Expand Up @@ -1559,6 +1562,7 @@ public void doSingleContentItemResponse(RunData data, Context context) {
}

String flow = data.getParameters().getString(FLOW_PARAMETER);
log.debug("doSingleContentItemResponse flow={}", flow);

// Check for a returned "note" from LTI
String lti_msg = data.getParameters().getString("lti_msg");
Expand Down Expand Up @@ -1605,6 +1609,7 @@ public void doSingleContentItemResponse(RunData data, Context context) {
// doSingleContentItemResponse
state.removeAttribute(STATE_LINE_ITEM);
if ( isDeepLink ) {

// Parse and validate the incoming DeepLink
String keyset = (String) tool.get(LTIService.LTI13_TOOL_KEYSET);
if (keyset == null) {
Expand All @@ -1622,27 +1627,39 @@ public void doSingleContentItemResponse(RunData data, Context context) {
return;
}

JSONObject item = dlr.getItemOfType(DeepLinkResponse.TYPE_LTILINKITEM);
if (item == null) {
addAlert(state, rb.getString("error.deeplink.no.ltilink"));
switchPanel(state, errorPanel);
if ( FLOW_PARAMETER_IMPORT.equals(flow) ) {
JSONObject item = dlr.getItemOfType(DeepLinkResponse.TYPE_FILEITEM);
if (item == null) {
addAlert(state, rb.getString("error.deeplink.no.fileitem"));
switchPanel(state, errorPanel);
return;
}
handleImportCartridge(data, context, state, errorPanel, returnUrl, item, sakaiSession);
return;
}
} else {

JSONObject item = dlr.getItemOfType(DeepLinkResponse.TYPE_LTILINKITEM);
if (item == null) {
addAlert(state, rb.getString("error.deeplink.no.ltilink"));
switchPanel(state, errorPanel);
return;
}

reqProps = extractLTIDeepLink(item, tool, toolKey);
reqProps.setProperty("returnUrl", returnUrl);
reqProps = extractLTIDeepLink(item, tool, toolKey);
reqProps.setProperty("returnUrl", returnUrl);

// Create the gradebook column if we need to do so
JSONObject lineItem = getObject(item, DeepLinkResponse.LINEITEM);
// Create the gradebook column if we need to do so
JSONObject lineItem = getObject(item, DeepLinkResponse.LINEITEM);

SakaiLineItem sakaiLineItem = null;
if ( lineItem != null ) {
String lineItemStr = lineItem.toString();
try {
sakaiLineItem = (SakaiLineItem) new ObjectMapper().readValue(lineItemStr, SakaiLineItem.class);
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
} catch (com.fasterxml.jackson.core.JsonProcessingException ex) {
log.warn("Could not parse input as SakaiLineItem {}",lineItemStr);
SakaiLineItem sakaiLineItem = null;
if ( lineItem != null ) {
String lineItemStr = lineItem.toString();
try {
sakaiLineItem = (SakaiLineItem) new ObjectMapper().readValue(lineItemStr, SakaiLineItem.class);
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
} catch (com.fasterxml.jackson.core.JsonProcessingException ex) {
log.warn("Could not parse input as SakaiLineItem {}",lineItemStr);
}
}
}

Expand All @@ -1662,40 +1679,52 @@ public void doSingleContentItemResponse(RunData data, Context context) {
// Properties dataProps = contentItem.getDataProperties();
// log.debug("dataProps={}", dataProps);
// dataProps={remember=always bring a towel}

// Extract the content item data
JSONObject item = contentItem.getItemOfType(ContentItem.TYPE_LTILINKITEM);
if (item == null) {
addAlert(state, rb.getString("error.contentitem.no.ltilink"));
switchPanel(state, errorPanel);
if ( FLOW_PARAMETER_IMPORT.equals(flow) ) {
JSONObject item = contentItem.getItemOfType(ContentItem.TYPE_FILEITEM);
if (item == null) {
addAlert(state, rb.getString("error.contentitem.no.fileitem"));
switchPanel(state, errorPanel);
return;
}
handleImportCartridge(data, context, state, errorPanel, returnUrl, item, sakaiSession);
return;
}
} else {

// Prepare data for the next phase
reqProps = extractLTIContentItem(item, tool, toolKey);
reqProps.setProperty("returnUrl", returnUrl);
JSONObject item = contentItem.getItemOfType(ContentItem.TYPE_LTILINKITEM);
if (item == null) {
addAlert(state, rb.getString("error.contentitem.no.ltilink"));
switchPanel(state, errorPanel);
return;
}

// Extract the lineItem material
String label = reqProps.getProperty(LTIService.LTI_TITLE);
JSONObject lineItem = getObject(item, ContentItem.LINEITEM);
SakaiLineItem sakaiLineItem = null;
if ( lineItem != null ) {
String lineItemStr = lineItem.toString();
try {
sakaiLineItem = (SakaiLineItem) new ObjectMapper().readValue(lineItemStr, SakaiLineItem.class);
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
} catch (com.fasterxml.jackson.core.JsonProcessingException ex) {
log.warn("Could not parse input as SakaiLineItem {}",lineItemStr);
sakaiLineItem = new SakaiLineItem();
// Prepare data for the next phase
reqProps = extractLTIContentItem(item, tool, toolKey);
reqProps.setProperty("returnUrl", returnUrl);

// Extract the lineItem material
String label = reqProps.getProperty(LTIService.LTI_TITLE);
JSONObject lineItem = getObject(item, ContentItem.LINEITEM);
SakaiLineItem sakaiLineItem = null;
if ( lineItem != null ) {
String lineItemStr = lineItem.toString();
try {
sakaiLineItem = (SakaiLineItem) new ObjectMapper().readValue(lineItemStr, SakaiLineItem.class);
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
} catch (com.fasterxml.jackson.core.JsonProcessingException ex) {
log.warn("Could not parse input as SakaiLineItem {}",lineItemStr);
sakaiLineItem = new SakaiLineItem();
}
}
}

if ( label != null && lineItem != null ) {
sakaiLineItem.label = label;
Double scoreMaximum = ContentItem.getScoreMaximum(lineItem);
if ( scoreMaximum != null ) sakaiLineItem.scoreMaximum = scoreMaximum;
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
if ( label != null && lineItem != null ) {
sakaiLineItem.label = label;
Double scoreMaximum = ContentItem.getScoreMaximum(lineItem);
if ( scoreMaximum != null ) sakaiLineItem.scoreMaximum = scoreMaximum;
state.setAttribute(STATE_LINE_ITEM, sakaiLineItem);
}
}

}

// Prepare to forward
Expand Down Expand Up @@ -1728,6 +1757,41 @@ public void doSingleContentItemResponse(RunData data, Context context) {
switchPanel(state, redirectPanel);
}

public void handleImportCartridge(RunData data, Context context, SessionState state,
String errorPanel, String returnUrl, JSONObject item, String sakaiSession)
{
String contentItemUrl = (String) item.get("url");
if (contentItemUrl == null) {
addAlert(state, rb.getString("error.deeplink.no.import.url"));
switchPanel(state, errorPanel);
return;
}
state.setAttribute(STATE_CONTENT_ITEM_CARTRIDGE_URL, contentItemUrl);
state.setAttribute(STATE_CONTENT_ITEM_IMPORT_RETURN_URL, returnUrl);

String importPanel = "ImportReturn";
if (sakaiSession != null) {
importPanel += "&" + RequestFilter.ATTR_SESSION + "=" + sakaiSession;
}
switchPanel(state, importPanel);
}

public String buildImportReturnPanelContext(VelocityPortlet portlet, Context context,
RunData data, SessionState state) {
context.put("tlang", rb);
context.put("includeLatestJQuery", PortalUtils.includeLatestJQuery("LTIAdminTool"));

String returnUrl = (String) state.getAttribute(STATE_CONTENT_ITEM_IMPORT_RETURN_URL);
String content_item_url = (String) state.getAttribute(STATE_CONTENT_ITEM_CARTRIDGE_URL);
state.removeAttribute(STATE_CONTENT_ITEM_CARTRIDGE_URL);
state.removeAttribute(STATE_CONTENT_ITEM_IMPORT_RETURN_URL);

context.put("returnUrl", returnUrl);
context.put("content_item_url", content_item_url);
return "lti_import_return";
}


// This is where we receive a multiple item the Response from the external ContentItem / DeepLink
// producer - the producer will post to this URL so we need to carefully re-establish the
// session cookie
Expand Down Expand Up @@ -2305,6 +2369,7 @@ public String buildContentConfigPanelContext(VelocityPortlet portlet, Context co
if (flow == null && previousPost != null) {
flow = previousPost.getProperty(FLOW_PARAMETER);
}
log.debug("buildContentConfigPanelContext flow={}", flow);

// TODO: Have Lessons use the normal entry point instead of coming directly here
if (flow == null) {
Expand Down Expand Up @@ -2383,7 +2448,13 @@ public String buildContentConfigPanelContext(VelocityPortlet portlet, Context co
// which will come back later. Be mindful of GET length limitations enroute
// to the access servlet.
Properties contentData = new Properties();
contentData.setProperty(ContentItem.ACCEPT_MEDIA_TYPES, ContentItem.MEDIA_LTILINKITEM);
contentData.setProperty(ContentItem.ACCEPT_MULTIPLE, "false");
if ( FLOW_PARAMETER_IMPORT.equals(flow) ) {
contentData.setProperty(ContentItem.ACCEPT_MEDIA_TYPES, ContentItem.MEDIA_CC);
} else {
contentData.setProperty(ContentItem.ACCEPT_MEDIA_TYPES, ContentItem.MEDIA_LTILINKITEM);
}
contentData.setProperty("flow", flow); // An example
contentData.setProperty("remember", "always bring a towel"); // An example

contentLaunch = ContentItem.buildLaunch(contentLaunch, contentReturn, contentData);
Expand Down
32 changes: 32 additions & 0 deletions basiclti/basiclti-tool/src/webapp/vm/lti_import_return.vm
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class="portletBody">
#if ( $returnUrl && $content_item_url )
<script>includeLatestJQuery('lti_import_return.vm');</script>
<script>
// https://stackoverflow.com/questions/2506457/how-to-resize-a-iframe-inside-the-containing-page
var elem = window.parent.document.getElementById('sakai-basiclti-launch-iframe'); // the id of your iframe of course
elem.style.height = '40em';
</script>
<center>
<form method="POST" action="$returnUrl" style="padding: 5em;">
<input type="hidden" name="content_item_url" value="$content_item_url">
<p>
<img id="import-cartridge-spinner" src="/library/image/sakai/spinner.gif" alt="$tlang.getString("gen.spinner.alt")" style="display:none;">
</p>
<p>
<input type="submit" id="import-cartridge-continue" value="$tlang.getString("import.cartridge.continue")">
</p>
</form>
</center>
<script>
$(document).ready( function() {
$('#import-cartridge-continue').on('click', function() {
$('#import-cartridge-spinner').show();
setTimeout(function () { $('#import-cartridge-continue').prop('disabled', true); }, 500);
return true;
});
});
</script>
#else
<h4>$tlang.getString("operation.complete")</h4>
#end
</div>
Loading

0 comments on commit f888d50

Please sign in to comment.