From e3794592f8275f958df90d663b01183f865e70dd Mon Sep 17 00:00:00 2001
From: gnaegi <none@none>
Date: Fri, 28 Jun 2013 17:25:09 +0200
Subject: [PATCH] OO-644 LTI context help, layout, data privacy accept dialog

---
 .../course/nodes/basiclti/LTIConfigForm.java  |  94 +++++----
 .../nodes/basiclti/LTIRunController.java      | 183 ++++++++++++++++--
 .../nodes/basiclti/_chelp/ced-lti-conf.html   |  86 +++++++-
 .../nodes/basiclti/_content/accept.html       |  39 ++++
 .../course/nodes/basiclti/_content/edit.html  |   5 +-
 .../basiclti/_i18n/LocalStrings_de.properties |  42 ++--
 .../basiclti/_i18n/LocalStrings_en.properties |  72 ++++---
 .../_spring/userPropertiesContext.xml         |   5 +-
 .../themes/openolat/all/modules/_course.scss  |  11 ++
 9 files changed, 440 insertions(+), 97 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/basiclti/_content/accept.html

diff --git a/src/main/java/org/olat/course/nodes/basiclti/LTIConfigForm.java b/src/main/java/org/olat/course/nodes/basiclti/LTIConfigForm.java
index 3544cc2af13..f4ae48f25d3 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/LTIConfigForm.java
+++ b/src/main/java/org/olat/course/nodes/basiclti/LTIConfigForm.java
@@ -39,6 +39,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.SpacerElement;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
@@ -114,9 +115,7 @@ public class LTIConfigForm extends FormBasicController {
 	private String[] ltiRolesKeys = new String[]{
 			"Learner", "Instructor", "Administrator", "TeachingAssistant", "ContentDeveloper", "Mentor"
 	};
-	private String[] ltiRolesValues = new String[]{
-			"Learner", "Instructor", "Administrator", "TeachingAssistant", "ContentDeveloper", "Mentor"
-	};
+	private String[] ltiRolesValues;
 	
 	private String[] displayKeys = new String[]{
 			"iframe", "window"
@@ -157,6 +156,15 @@ public class LTIConfigForm extends FormBasicController {
 		UserManager userManager = CoreSpringFactory.getImpl(UserManager.class);
 		Translator userPropsTranslator = userManager.getPropertyHandlerTranslator(getTranslator());
 		
+		ltiRolesValues = new String[]{
+				translate("roles.lti.learner"),
+				translate("roles.lti.instructor"),
+				translate("roles.lti.administrator"),
+				translate("roles.lti.teachingAssistant"),
+				translate("roles.lti.contentDeveloper"),
+				translate("roles.lti.mentor")		
+		};
+
 		displayValues = new String[]{
 				translate("display.config.window.iframe"), translate("display.config.window.window")
 		};
@@ -230,13 +238,18 @@ public class LTIConfigForm extends FormBasicController {
 		thost = uifactory.addTextElement("host", "LTConfigForm.url", 255, fullURI, formLayout);
 		thost.setExampleKey("LTConfigForm.url.example", null);
 		thost.setDisplaySize(64);
+		thost.setMandatory(true);
 		
 		tkey  = uifactory.addTextElement ("key","LTConfigForm.key", 255, key, formLayout);
 		tkey.setExampleKey ("LTConfigForm.key.example", null);
+		tkey.setMandatory(true);
 		
 		tpass = uifactory.addTextElement ("pass","LTConfigForm.pass", 255, pass, formLayout);
 		tpass.setExampleKey("LTConfigForm.pass.example", null);
-		
+		tpass.setMandatory(true);
+
+		uifactory.addSpacerElement("attributes", formLayout, false);
+
 		sendName = uifactory.addCheckboxesVertical("sendName", "display.config.sendName", formLayout, new String[]{"xx"}, new String[]{null}, null, 1);
 		sendName.select("xx", sendNameConfig);
 		
@@ -254,8 +267,38 @@ public class LTIConfigForm extends FormBasicController {
 			createNameValuePair("", "", -1);
 		}
 		
-		doDebug = uifactory.addCheckboxesVertical("doDebug", "display.config.doDebug", formLayout, new String[]{"xx"}, new String[]{null}, null, 1);
-		doDebug.select("xx", doDebugConfig);
+		uifactory.addSpacerElement("roles", formLayout, false);
+		uifactory.addStaticTextElement("roletitle", "roles.title.oo", translate("roles.title.lti"), formLayout);
+		
+		authorRoleEl = uifactory.addCheckboxesHorizontal("author", "author.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
+		udpateRoles(authorRoleEl, BasicLTICourseNode.CONFIG_KEY_AUTHORROLE, "Instructor,Administrator,TeachingAssistant,ContentDeveloper,Mentor"); 
+		coachRoleEl = uifactory.addCheckboxesHorizontal("coach", "coach.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
+		udpateRoles(coachRoleEl, BasicLTICourseNode.CONFIG_KEY_COACHROLE, "Instructor,TeachingAssistant,Mentor");
+		participantRoleEl = uifactory.addCheckboxesHorizontal("participant", "participant.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
+		udpateRoles(participantRoleEl, BasicLTICourseNode.CONFIG_KEY_PARTICIPANTROLE, "Learner"); 
+		
+		uifactory.addSpacerElement("scoring", formLayout, false);
+		
+		//add score info
+		String[] assessableKeys = new String[]{ "on" };
+		String[] assessableValues = new String[]{ "" };
+		isAssessableEl = uifactory.addCheckboxesHorizontal("isassessable", "assessable.label", formLayout, assessableKeys, assessableValues, null);
+		isAssessableEl.addActionListener(this, FormEvent.ONCHANGE);
+		if(isAssessable) {
+			isAssessableEl.select("on", true);
+		}
+	
+		Float scaleValue = config.getFloatEntry(BasicLTICourseNode.CONFIG_KEY_SCALEVALUE);
+		String scaleFactor = scaleValue == null ? "1.0" : scaleValue.toString();
+		scaleFactorEl = uifactory.addTextElement("scale", "scaleFactor", 10, scaleFactor, formLayout);
+		scaleFactorEl.setDisplaySize(3);
+		scaleFactorEl.setVisible(isAssessable);
+		
+		Float cutValue = config.getFloatEntry(BasicLTICourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
+		String cut = cutValue == null ? "" : cutValue.toString();
+		cutValueEl = uifactory.addTextElement("cutvalue", "cutvalue.label", 10, cut, formLayout);
+		cutValueEl.setDisplaySize(3);
+		cutValueEl.setVisible(isAssessable);
 		
 		uifactory.addSpacerElement("display", formLayout, false);
 		
@@ -282,39 +325,12 @@ public class LTIConfigForm extends FormBasicController {
 				widthEl.select(heightKey, true);
 			}
 		}
-
-		uifactory.addSpacerElement("scoring", formLayout, false);
-		
-		//add score info
-		String[] assessableKeys = new String[]{ "on" };
-		String[] assessableValues = new String[]{ "" };
-		isAssessableEl = uifactory.addCheckboxesHorizontal("isassessable", "assessable.label", formLayout, assessableKeys, assessableValues, null);
-		isAssessableEl.addActionListener(this, FormEvent.ONCHANGE);
-		if(isAssessable) {
-			isAssessableEl.select("on", true);
-		}
-	
-    Float scaleValue = config.getFloatEntry(BasicLTICourseNode.CONFIG_KEY_SCALEVALUE);
-		String scaleFactor = scaleValue == null ? "1.0" : scaleValue.toString();
-		scaleFactorEl = uifactory.addTextElement("scale", "scaleFactor", 10, scaleFactor, formLayout);
-		scaleFactorEl.setDisplaySize(3);
-		scaleFactorEl.setVisible(isAssessable);
 		
-		Float cutValue = config.getFloatEntry(BasicLTICourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
-		String cut = cutValue == null ? "" : cutValue.toString();
-		cutValueEl = uifactory.addTextElement("cutvalue", "cutvalue.label", 10, cut, formLayout);
-		cutValueEl.setDisplaySize(3);
-		cutValueEl.setVisible(isAssessable);
-		
-		uifactory.addSpacerElement("roles", formLayout, false);
-
-		authorRoleEl = uifactory.addCheckboxesHorizontal("author", "author.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
-		udpateRoles(authorRoleEl, BasicLTICourseNode.CONFIG_KEY_AUTHORROLE, "Instructor,Administrator,TeachingAssistant,ContentDeveloper,Mentor"); 
-		coachRoleEl = uifactory.addCheckboxesHorizontal("coach", "coach.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
-		udpateRoles(coachRoleEl, BasicLTICourseNode.CONFIG_KEY_COACHROLE, "Instructor,TeachingAssistant,Mentor");
-		participantRoleEl = uifactory.addCheckboxesHorizontal("participant", "participant.roles", formLayout, ltiRolesKeys, ltiRolesValues, null);
-		udpateRoles(participantRoleEl, BasicLTICourseNode.CONFIG_KEY_PARTICIPANTROLE, "Learner"); 
+		uifactory.addSpacerElement("debug", formLayout, false);
 		
+		doDebug = uifactory.addCheckboxesVertical("doDebug", "display.config.doDebug", formLayout, new String[]{"xx"}, new String[]{null}, null, 1);
+		doDebug.select("xx", doDebugConfig);
+				
 		uifactory.addSpacerElement("buttons", formLayout, false);
 		uifactory.addFormSubmitButton("save", formLayout);
 	}
@@ -487,6 +503,10 @@ public class LTIConfigForm extends FormBasicController {
 		} else if(source instanceof FormLink && source.getName().startsWith("rm_")) {
 			NameValuePair pair = (NameValuePair)source.getUserObject();
 			doRemoveNameValuePair(pair);
+			if(nameValuePairs.isEmpty()) {
+				// add a new empty default pair
+				createNameValuePair("", "", -1);
+			}
 		} else if(source instanceof SingleSelection && source.getName().startsWith("typ_")) {
 			NameValuePair pair = (NameValuePair)source.getUserObject();
 			SingleSelection typeChoice = (SingleSelection)source;
diff --git a/src/main/java/org/olat/course/nodes/basiclti/LTIRunController.java b/src/main/java/org/olat/course/nodes/basiclti/LTIRunController.java
index 86a407e1096..25e394ed4e0 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/LTIRunController.java
+++ b/src/main/java/org/olat/course/nodes/basiclti/LTIRunController.java
@@ -27,6 +27,7 @@ package org.olat.course.nodes.basiclti;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Enumeration;
 import java.util.Map;
 
 import org.imsglobal.basiclti.BasicLTIUtil;
@@ -47,9 +48,14 @@ import org.olat.core.gui.control.creator.ControllerCreator;
 import org.olat.core.gui.control.generic.popup.PopupBrowserWindow;
 import org.olat.core.helpers.Settings;
 import org.olat.core.id.Roles;
+import org.olat.core.id.User;
+import org.olat.core.id.UserConstants;
+import org.olat.core.util.Encoder;
+import org.olat.core.util.SortedProperties;
 import org.olat.core.util.StringHelper;
 import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.BasicLTICourseNode;
+import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.environment.CourseEnvironment;
 import org.olat.course.run.scoring.ScoreEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
@@ -58,6 +64,7 @@ import org.olat.ims.lti.LTIManager;
 import org.olat.ims.lti.ui.PostDataMapper;
 import org.olat.ims.lti.ui.TalkBackMapper;
 import org.olat.modules.ModuleConfiguration;
+import org.olat.properties.Property;
 import org.olat.resource.OLATResource;
 
 /**
@@ -68,14 +75,18 @@ import org.olat.resource.OLATResource;
  * 
  */
 public class LTIRunController extends BasicController {
-
+	private static final String PROP_NAME_DATA_EXCHANGE_ACCEPTED = "LtiDataExchageAccepted";
+	
 	private Link startButton;
 	private final Panel mainPanel;
-	private VelocityContainer run, startPage;
+	private VelocityContainer run, startPage, acceptPage;
 	private BasicLTICourseNode courseNode;
 	private ModuleConfiguration config;
 	private final CourseEnvironment courseEnv;
 	private UserCourseEnvironment userCourseEnv;
+	private SortedProperties userData = new SortedProperties(); 
+	private SortedProperties customUserData = new SortedProperties(); 
+	private Link acceptLink;
 	
 	private final Roles roles;
 	private final LTIManager ltiManager;
@@ -111,33 +122,175 @@ public class LTIRunController extends BasicController {
 	 */
 	public LTIRunController(WindowControl wControl, ModuleConfiguration config, UserRequest ureq, BasicLTICourseNode ltCourseNode,
 			UserCourseEnvironment userCourseEnv) {
-		super(ureq, wControl);
+ 		super(ureq, wControl);
 		this.courseNode = ltCourseNode;
 		this.config = config;
 		this.userCourseEnv = userCourseEnv;
 		this.roles = ureq.getUserSession().getRoles();
-		courseEnv = userCourseEnv.getCourseEnvironment();
-		ltiManager = CoreSpringFactory.getImpl(LTIManager.class);
-		
+		this.courseEnv = userCourseEnv.getCourseEnvironment();
+		this.ltiManager = CoreSpringFactory.getImpl(LTIManager.class);
+		String display = config.getStringValue(BasicLTICourseNode.CONFIG_DISPLAY, "iframe");
+		this.newWindow = "window".equals(display);
+
 		mainPanel = new Panel("ltiContainer");
 		putInitialPanel(mainPanel);
+		
+		// only run directly when user as already accepted to data exchange or no data has to be exchanged
+		createExchangeDataProperties();
+		String dataExchangeHash = createHashFromExchangeDataProperties();
+		if (dataExchangeHash == null || checkHasDataExchangeAccepted(dataExchangeHash)) {
+			doRun(ureq);						
+		} else {
+			doAskDataExchange();
+		}
+
+	}
+
+	/**
+	 * Helper method to check if user has already accepted. this info is stored
+	 * in a user property, the accepted values are stored as an MD5 hash (save
+	 * space, privacy)
+	 * 
+	 * @param hash
+	 *            MD5 hash with all user data
+	 * @return true: user has already accepted for this hash; false: user has
+	 *         not yet accepted or for other values
+	 */
+	private boolean checkHasDataExchangeAccepted(String hash) {
+		// 
+		CoursePropertyManager propMgr = this.userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
+		Property prop = propMgr.findCourseNodeProperty(this.courseNode, getIdentity(), null, PROP_NAME_DATA_EXCHANGE_ACCEPTED);
+		if (prop != null) {
+			// compare if value in property is the same as calculated today. If not, user as to accept again
+			String storedHash = prop.getStringValue();
+			if (storedHash != null && hash != null && storedHash.equals(hash)) {
+				return true;
+			} else {
+				// remove property, not valid anymore
+				propMgr.deleteProperty(prop);
+			}
+		}
+		return false;
+	}
 
+	/**
+	 * Helper to initialize the ask-for-data-exchange screen
+	 */
+	private void doAskDataExchange() {
+		acceptPage = createVelocityContainer("accept");
+		acceptPage.contextPut("userData", userData);
+		acceptPage.contextPut("customUserData", customUserData);
+		acceptLink = LinkFactory.createButton("accept", acceptPage, this);
+		mainPanel.setContent(acceptPage);
+	}
+	
+	/**
+	 * Helper to save the user accepted data exchange
+	 */
+	private void storeDataExchangeAcceptance() {
+		CoursePropertyManager propMgr = this.userCourseEnv.getCourseEnvironment().getCoursePropertyManager();
+		String hash = createHashFromExchangeDataProperties();
+		Property prop = propMgr.createCourseNodePropertyInstance(this.courseNode, getIdentity(), null, PROP_NAME_DATA_EXCHANGE_ACCEPTED, null, null, hash, null);
+		propMgr.saveProperty(prop);
+	}
+	
+	/**
+	 * Helper to read all user data that is exchanged with LTI tool and saves it
+	 * to the userData and customUserData properties fields
+	 */
+	private void createExchangeDataProperties() {
+		final User user = getIdentity().getUser();
+		//user data
+		if (config.getBooleanSafe(LTIConfigForm.CONFIG_KEY_SENDNAME, false)) {
+			userData.put("lastName", user.getProperty(UserConstants.LASTNAME, getLocale()));
+			userData.put("firstName", user.getProperty(UserConstants.FIRSTNAME, getLocale()));
+		}
+		if (config.getBooleanSafe(LTIConfigForm.CONFIG_KEY_SENDEMAIL, false)) {
+			userData.put("email", user.getProperty(UserConstants.EMAIL, getLocale()));
+		}
+		// customUserData
+		String custom = (String)config.get(LTIConfigForm.CONFIG_KEY_CUSTOM);
+		if (StringHelper.containsNonWhitespace(custom)) {
+			String[] params = custom.split("[\n;]");
+			for (int i = 0; i < params.length; i++) {
+				String param = params[i];
+				if (!StringHelper.containsNonWhitespace(param)) {
+					continue;
+				}
+				
+				int pos = param.indexOf("=");
+				if (pos < 1 || pos + 1 > param.length()) {
+					continue;
+				}
+				
+				String key = BasicLTIUtil.mapKeyName(param.substring(0, pos));
+				if(!StringHelper.containsNonWhitespace(key)) {
+					continue;
+				}
+				
+				String value = param.substring(pos + 1).trim();
+				if(value.length() < 1) {
+					continue;
+				}
+				
+				if(value.startsWith(LTIManager.USER_PROPS_PREFIX)) {
+					String userProp = value.substring(LTIManager.USER_PROPS_PREFIX.length(), value.length());
+					value = user.getProperty(userProp, null);
+					customUserData.put(userProp, value);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Helper to create an MD5 hash from the exchanged user properties. 
+	 * @return
+	 */
+	private String createHashFromExchangeDataProperties() {
+		String data = "";
+		String hash = null;
+		if (userData != null && userData.size() > 0) {
+			Enumeration<Object> keys = userData.keys();
+			while (keys.hasMoreElements()) {
+				String key = (String) keys.nextElement();
+				data += userData.getProperty((String)key);				
+			}
+		}
+		if (customUserData != null && customUserData.size() > 0) {
+			Enumeration<Object> keys = customUserData.keys();
+			while (keys.hasMoreElements()) {
+				String key = (String) keys.nextElement();
+				data += customUserData.getProperty((String)key);				
+			}
+		}
+		if (data.length() > 0) {
+			hash = Encoder.encrypt(data);
+		}
+		if (isLogDebugEnabled()) {
+			logDebug("Create accept hash::" + hash + " for data::" + data, null);
+		}
+		return null;
+	}
+	
+	/**
+	 * Helper to initialize the LTI run view after user has accepted data exchange.
+	 * @param ureq
+	 */
+	private void doRun(UserRequest ureq) {
 		run = createVelocityContainer("run");
 		// push title and learning objectives, only visible on intro page
 		run.contextPut("menuTitle", courseNode.getShortTitle());
 		run.contextPut("displayTitle", courseNode.getLongTitle());
 		
-		String display = config.getStringValue(BasicLTICourseNode.CONFIG_DISPLAY, "iframe");
-		newWindow = "window".equals(display);
 
 		startPage = createVelocityContainer("overview");
 		startPage.contextPut("menuTitle", courseNode.getShortTitle());
 		startPage.contextPut("displayTitle", courseNode.getLongTitle());
 		
 		startButton = LinkFactory.createButton("start", startPage, this);
-    if(newWindow) {
-    	startButton.setTarget("_help");
-    }
+	    if(newWindow) {
+	    	startButton.setTarget("_help");
+	    }
 
 
 		Boolean assessable = config.getBooleanEntry(BasicLTICourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
@@ -156,13 +309,14 @@ public class LTIRunController extends BasicController {
 	    startPage.contextPut("score", eval.getScore()); 
 	    mainPanel.setContent(startPage);
 		} else if(newWindow) {
-	    mainPanel.setContent(startPage);
+			mainPanel.setContent(startPage);
 		} else {
 			doBasicLTI(ureq, run);
 			mainPanel.setContent(run);
 		}
-	}
 
+	}
+	
 	public void event(UserRequest ureq, Component source, Event event) {
 		if(source == startButton) {
 			courseNode.incrementUserAttempts(userCourseEnv);
@@ -181,6 +335,9 @@ public class LTIRunController extends BasicController {
 				doBasicLTI(ureq, run);
 				mainPanel.setContent(run);
 			}
+		} else if (source == acceptLink) {
+			storeDataExchangeAcceptance();
+			doRun(ureq);
 		}
 	}
 	
diff --git a/src/main/java/org/olat/course/nodes/basiclti/_chelp/ced-lti-conf.html b/src/main/java/org/olat/course/nodes/basiclti/_chelp/ced-lti-conf.html
index ccf5e3ab4ee..c42e969c987 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/_chelp/ced-lti-conf.html
+++ b/src/main/java/org/olat/course/nodes/basiclti/_chelp/ced-lti-conf.html
@@ -1,11 +1,79 @@
- <br/>$r.translate("cshelp.lti1") $r.translate("chelp.lti2") <br/><br/>
-$r.translate("chelp.lti3") $r.translate("chelp.lti4") <br/><br/>
+ <p>
+ 	$r.translate("cshelp.lti1") $r.translate("chelp.lti2") 
+ </p>
+ <p>
+	$r.translate("chelp.lti.info")
+	<a href="http://www.imsglobal.org/lti/" target="_blank" class="b_link_extern">http://www.imsglobal.org/lti/</a>
+	<hr />
+</p>
 
+ 
+ <p>
+	$r.translate("chelp.lti3") $r.translate("chelp.lti4")
+</p>
 
-<b>$r.translate("LTConfigForm.url"):</b> $r.translate("chelp.lti5") <br/><br/>
-<b>$r.translate("LTConfigForm.key"):</b> $r.translate("chelp.lti6") <br/><br/>
-<b>$r.translate("LTConfigForm.pass"):</b> $r.translate("chelp.lti7") <br/><br/>
-<b>$r.translate("display.config.sendName"):</b> $r.translate("chelp.lti8") <br/><br/>
-<b>$r.translate("display.config.sendEmail"):</b> $r.translate("chelp.lti9") <br/><br/>
-<b>$r.translate("display.config.custom")</b>: $r.translate("chelp.lti10") $r.translate("chelp.lti11")<br/><br/>
-<b>$r.translate("display.config.doDebug")</b>: $r.translate("chelp.lti12")$r.translate("chelp.lti13") <br/><br/>
+ <p class="b_info">
+	$r.translate("chelp.lti.data")
+</p>
+
+<p>
+	$r.translate("chelp.lti.config")
+</p>
+<p>
+	<b>$r.translate("LTConfigForm.url"):</b> $r.translate("chelp.lti5")
+</p>
+<p>
+	<b>$r.translate("LTConfigForm.key"):</b> $r.translate("chelp.lti6") 
+</p>
+<p>
+	<b>$r.translate("LTConfigForm.pass"):</b> $r.translate("chelp.lti7") 
+	<hr />
+</p>
+
+<p>
+	<b>$r.translate("display.config.sendName"):</b> $r.translate("chelp.lti8") 
+</p>
+<p>
+	<b>$r.translate("display.config.sendEmail"):</b> $r.translate("chelp.lti9") 
+</p>
+<p>
+	<b>$r.translate("display.config.custom")</b>: $r.translate("chelp.lti10") $r.translate("chelp.lti11")
+	<hr />
+</p>
+
+<p>
+	<b>$r.translate("roles.title.oo")</b>: 
+	$r.translate("chelp.lti.roles") 
+	<hr />
+</p>
+
+<p>
+	<b>$r.translate("assessable.label")</b>: 
+	$r.translate("chelp.lti.assessable")
+</p>
+<p>
+	<b>$r.translate("scaleFactor")</b>: 
+	$r.translate("chelp.lti.scale")
+</p>
+<p>
+	<b>$r.translate("cutvalue.label")</b>: 
+	$r.translate("chelp.lti.cut")
+	<hr />
+</p>
+
+<p>
+	<b>$r.translate("display.config.window")</b>: 
+	$r.translate("chelp.lti.display") 
+</p>
+<p>
+	<b>$r.translate("display.config.height")</b>: 
+	$r.translate("chelp.lti.pixel") 
+</p>
+<p>
+	<b>$r.translate("display.config.width")</b>: 
+	$r.translate("chelp.lti.pixel") 
+	<hr />
+</p>
+<p>
+	<b>$r.translate("display.config.doDebug")</b>: $r.translate("chelp.lti12")$r.translate("chelp.lti13") 
+</p>
diff --git a/src/main/java/org/olat/course/nodes/basiclti/_content/accept.html b/src/main/java/org/olat/course/nodes/basiclti/_content/accept.html
new file mode 100644
index 00000000000..514e8a647a0
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/basiclti/_content/accept.html
@@ -0,0 +1,39 @@
+<div class="b_warning">
+	<h4>$r.translate("accept.title")</h4>
+	<p>
+		$r.translate("accept.intro")
+	</p>
+
+<table class="b_grid">
+#if ($userData.get('firstName'))
+	<tr>
+		<td>$r.translateWithPackage("org.olat.user.propertyhandlers","form.name.firstName") </td>
+		<td>$userData.get('firstName')</td>
+	</tr>
+#end
+#if ($userData.get('lastName'))
+	<tr>
+		<td>$r.translateWithPackage("org.olat.user.propertyhandlers","form.name.lastName") </td>
+		<td>$userData.get('lastName')</td>
+	</tr>
+#end
+#if ($userData.get('email'))
+	<tr>
+		<td>$r.translateWithPackage("org.olat.user.propertyhandlers","form.name.email") </td>
+		<td>$userData.get('email')</td>
+	</tr>
+#end
+
+#foreach ($name in $customUserData.propertyNames())
+	<tr>
+		<td>$r.translateWithPackage("org.olat.user.propertyhandlers","form.name.$name")</td>
+		<td>$customUserData.getProperty($name)</td>
+	</tr>
+#end
+</table>
+
+<div class="b_button_group">
+	$r.render("accept")
+</div>
+
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/basiclti/_content/edit.html b/src/main/java/org/olat/course/nodes/basiclti/_content/edit.html
index d56cb11cda8..132c993ccfa 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/_content/edit.html
+++ b/src/main/java/org/olat/course/nodes/basiclti/_content/edit.html
@@ -1,6 +1,7 @@
+<div class="b_lti_edit_wrapper">
 #if ($showPreviewButton)
-	<div class="o_buttons_box_right"><br/><br/><br/>$r.render("command.preview")</div>
+	<div class="o_buttons_box_right">$r.render("command.preview")</div>
 #end
 $r.render("ltConfigForm")
-
+</div>
 
diff --git a/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_de.properties
index ed0a5d1b303..cfa446280c5 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_de.properties
@@ -1,3 +1,6 @@
+accept.title=Zustimmung Datenübertragung
+accept.intro=Die folgende Seite wird von einem externen Server geladen. Zu diesem Zweck müssen einige ihrer persönliche Daten übermittelt werden, diese werden untenstehend angezeigt. Bitte bestätigen Sie die Übermittlung Ihrer Daten. 
+accept=Ich stimme der Datenübertragung zu
 title_lti=LTI-Seite
 LTConfigForm.invalidurl=Bitte geben Sie eine g\u00FCltige URL an.
 LTConfigForm.key=Schlüssel
@@ -14,28 +17,36 @@ form.title=Konfiguration LTI Seite
 help.hover.lt.conf=Hilfe zur Konfiguration der LTI Seite
 pane.tab.accessibility=Zugang
 pane.tab.ltconfig=Seiteninhalt
-display.config.sendName=Name zum Anbieter senden
-display.config.sendEmail=E-Mailadresse zum Anbieter senden
-display.config.custom=Spezielle Konfiguration (Name=Wert)
-display.config.doDebug=Gesendete Information anzeigen
+display.config.sendName=Vorname/Name übertragen
+display.config.sendEmail=E-Mailadresse übertragen
+display.config.custom=Zusätzliche Attribute
+display.config.doDebug=Alle beim Start gesendete Information anzeigen (Debug)
 display.config.window=Anzeige
-display.config.window.iframe=iframe
-display.config.window.window=Neue Fenster
-display.config.height=$org.olat.course.nodes.scorm\:height.label
+display.config.window.iframe=Eingebettet in Kurs (iFrame)
+display.config.window.window=Neues Fenster öffnen
+display.config.height=$org.olat.core.gui.control.generic.iframe\:height.label
 display.config.width=Breite Anzeigefläche
 display.config.free=Text
 display.config.free.userprops=Benutzer
 add=+
 remove=-
-height.auto=$org.olat.course.nodes.scorm\:height.auto
+height.auto=$org.olat.core.gui.control.generic.iframe\:height.auto
 command.preview=Vorschau anzeigen
-scaleFactor=Scaling factor
-assessable.label=Resultat aus LTI 1.1 übertragen
+scaleFactor=Skalierungsfaktor
+assessable.label=Punkte übertragen
 cutvalue.label=Notwendige Punktzahl für 'bestanden'
 form.error.wrongFloat=$org.olat.course.assessment\:form.error.wrongFloat
+roles.title.oo=OpenOLAT Rollen
+roles.title.lti=LTI Rollen
 participant.roles=Teilnehmer
 coach.roles=Betreuer
 author.roles=Besitzer
+roles.lti.learner=Lerner
+roles.lti.instructor=Instruktur
+roles.lti.administrator=Administrator
+roles.lti.teachingAssistant=Assistent Lehrperson
+roles.lti.contentDeveloper=Inhaltersteller
+roles.lti.mentor=Mentor		
 preview=Vorschau
 start=LTI-Lerninhalt anzeigen
 score.title=$org.olat.course.nodes.scorm\:score.title
@@ -47,16 +58,25 @@ passed.no=$org.olat.course.nodes.scorm\:passed.no
 passed.yes=$org.olat.course.nodes.scorm\:passed.yes
 chelp.ced-lti-conf.title=LTI-Seite konfigurieren
 cshelp.lti1=LTI steht für "Learning Tool Interoperability" und ist ein IMS Standard zur Einbindung von externen Lernapplikationen in eine Lernplattform.
+chelp.lti.info=Weitere Informationen zu LTI finden Sie auf der LTI Projekthomepage:
 chelp.lti2= Mit der LTI-Seite können Sie externe Software wie zum Beispiel einen externen Chat, ein Mediawiki, einen Testeditor oder ein virtuelles Chemielabor in Ihren Kurs integrieren.
 chelp.lti3=Wenn der Benutzer die LTI-Seite in der Navigation auswählt, erscheint die eingebundene Lernapplikation im OLAT-Kurs.
 chelp.lti4=Im Hintergrund werden dabei die Benutzerdaten und Kursinformationen sowie der Schlüssel geschützt an die eingebundene Lernapplikation übermittelt. Die Lernapplikation überprüft die Zugangsrechte und erlaubt bei gültigem Schlüssel den Zugriff.
+chelp.lti.data=Zum Schutz der Privatsphäre und Erfüllung der Datenschutzgesetzt muss der Benutzer beim erstmaligen Aufruf das Übertragen der Benutzerinformationen bestätigen. Diese Bestätigung wird immer dann wieder erzwungen wenn sich die Konfiguration des Bausteins in Bezug auf übermittelte Daten ändert. 
+chelp.lti.config=Die folgenden Parameter können konfiguriert werden:
 chelp.lti5=In diesem Eingabefeld geben Sie die Adresse der externen Lernapplikation im Format "http://wiscrowd.appspot.com/wiscrowd/" ein.
 chelp.lti6=Hier geben Sie den Schlüssel ein, den Sie vom Anbieter der externen Lernapplikation erhalten haben ("12345" im obigen Beispiel).
 chelp.lti7=Hier geben Sie das zum Schlüssel passende Passwort ein, das Sie ebenfalls vom Anbieter der externen Lernapplikation erhalten haben ("secret" im obigen Beispiel).
 chelp.lti8=Wenn Sie diese Checkbox ankreuzen, wird der Vor- und Nachname des Benutzers an die externe Lernapplikation weitergegeben. Ansonsten kann der Benutzer die externe Lernapplikation anonym nutzen.
 chelp.lti9=Markieren Sie die Checkbox, wird die E-Mailadresse des Benutzers an die externe Lernapplikation weitergegeben.
 chelp.lti10=In dieses Eingabefeld können Sie weitere Parameter eingeben, die an die Lernapplikation übermittelt werden sollen. So kann der Lernapplikation beispielsweise mitgeteilt werden, dass die Anfrage von der Lernplattform OLAT übermittelt wird. (Die externe Lernapplikation muss die weitergegebenen Informationen verarbeiten können, weshalb eine Absprache mit dem Anbieter nötig ist).
-chelp.lti11=Wichtig ist, dass die Parameter immer mit Namen und Wert übergeben werden. Im obigen Beispiel könnte das Format so aussehen: LMS=OLAT  
+chelp.lti11=Sie haben die Wahl von statischen Text-Attributen (Für alle Benutzer ist Wert identisch) oder zusätzlichen dynamischen Benutzerattributen (pro Benutzer unterschiedlich). Sie können beliebig viele Zusatzattribute definieren, die LTI Ressource muss allerdings wissen, dass es diese Attribute gibt da diese nicht im Standard definiert sind. 
+chelp.lti.roles=In diesem Bereich können Sie definieren welche Rolle die einzelnen Benutzer einnehmen wenn Sie die LTI Ressource starten. Es werden dabei die drei OpenOLAT $\:author.roles, $\:coach.roles und $\:participant.roles unterstützt. Für jede Rolle kann genau definiert werden, welche Rollen dafür auf Seiten der LTI Ressource angewendet werden soll. Die folgenden LTI Rollen können konfiguriert werden: $\:roles.lti.learner, $\:roles.lti.instructor, $\:roles.lti.administrator, $\:roles.lti.teachingAssistant, $\:roles.lti.contentDeveloper und $\:roles.lti.mentor.		
+chelp.lti.assessable=Wählen Sie diese Scheckbox wenn die LTI Ressource Punkte erzeugen und mit dem LTI 1.1 Standard an OpenOLAT übermitteln kann. Dies ist optional. Übermittelte Punkte erscheinen beim Benutzer auf der Startseite des LTI Bausteins sowie dem Leistungsnachweis. Bitte beachten Sie, dass LTI gemäss Standard nur einen Wert zwischen 0 und 1 liefern kann. 
+chelp.lti.scale=Mit dem Skalierungsfaktor können Sie die LTI Resultate, die gemäss Standard einen Wert zwischen 0 und 1 einnehmen müssen, auf einen im OpenOLAT Kurs praktischeren Wert Skalieren. Möchten Sie beispielsweise in OpenOLAT maximal 10 Punkte für eine LTI Aufgabe vergeben, so müssen Sie als Skalierungsfaktor den Wert "10" eintragen. Möchten Sie die Punkte unverändert übernehmen wählen Sie den Wert "1".
+chelp.lti.cut=Geben Sie hier den optionalen Schwellwert ab wann das LTI Element als bestanden gilt. Dieser Schwellwert bezieht sich auf das skalierte Endresultat und nicht die von LTI übermittelten Rohdaten! Im obigen Beispiel wäre ein Schwellwert von "5" gleichbedeutend mit "50%". 
+chelp.lti.display=Wählen Sie die Option "$\:display.config.window.iframe" um die LTI Ressource eingebetten im Kurslayout anzuzeigen. Mit der Option "$\:display.config.window.window" wird die LTI Ressource hingegen in einem neuen Fenster geöffnet. Dies ist sinnvoll, wenn die Ressource viel Platz braucht oder parallel mit anderen Kurselementen verwendet werden soll.
+chelp.lti.pixel=Wählen Sie "automatisch" oder eine explizite Grösse aus wenn die automatische Funktion ungenügend ist.
 chelp.lti12=Wenn Sie diese Checkbox ankreuzen, werden den Benutzern die gesendeten Informationen angezeigt. Diese Informationen beinhalten Parameter wie zum Beispiel die Benutzeridentifikation, den Kursnamen, den Kursbaustein etc.
 chelp.lti13=Wenn Sie die in der Kursansicht auf die Schaltfläche "Launch Endpoint with BasicLTI Data" oberhalb der Anzeige der gesendeten Daten drücken, gelangen Sie zur Startseite der Lernapplikation.
 condition.accessibility.title=Zugang
diff --git a/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_en.properties
index 8ce69d11929..230ec82d6cb 100644
--- a/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/basiclti/_i18n/LocalStrings_en.properties
@@ -1,4 +1,4 @@
-#Fri Jan 21 13:55:42 CET 2011
+#Fri Jun 28 17:11:37 CEST 2013
 LTConfigForm.invalidurl=Please indicate a valid URL.
 LTConfigForm.key=Key
 LTConfigForm.key.example=Example\: lmsng.school.edu
@@ -8,9 +8,25 @@ LTConfigForm.save=Save
 LTConfigForm.url=URL
 LTConfigForm.url.example=Example\: http\://www.imsglobal.org/developers/BLTI/tool.php 
 LTConfigForm.user=User name
+accept=I accept the data transfer
+accept.intro=The following page is loaded from an external server. For that purpose some of your personal data are transmitted to the external server, those are listed below. Please review the data and accept the transmission of your data.
+accept.title=Accepting of data transmission
+add=+
+assessable.label=Transfer score
+attempts.yourattempts=$org.olat.course.nodes.scorm\:attempts.yourattempts
+author.roles=Author
 chelp.ced-lti-conf.title=Configure LTI page
+chelp.lti.assessable=Select this checkbox when the LTI resource is generating a score value that can be transmitted to OpenOLAT using the LTI 1.1 standard. This is optional. The submitted score will appear on the users start screen of the LTI course element as well as in the efficiency statement of this course. Please be aware that according to the LTI standard only values between 0 and 1 are allowed.
+chelp.lti.config=The following parameters can be configured\:
+chelp.lti.cut=Here you can configure an optional cut value to define when the LTI course element is considered as being passed. The cut value relates to the score value after scaling and not to the raw value transmitted by LTI. In the example above a cut value of "5" is equivalent to "50%".
+chelp.lti.data=To conform with data protection laws and privacy the user has to accept the transmission of personal data the first time he launches the service and whenever the configuration about transmitted data changes. 
+chelp.lti.display=Select the option "$\:display.config.window.iframe" to embed the LTI resource within the course layout. With the option "$\:display.config.window.window" the LTI resource is opened in a separate window. This can be useful in case the resource needs a lot of space or has to be used in parallel with other course elements.
+chelp.lti.info=More information about LTI can be found on the LTI project page\:
+chelp.lti.pixel=Select "automatic" or an explicit size in case the automatic feature does not work properly.
+chelp.lti.roles=Here you can configure which role the user will have when launching the LTI resource. The three OpenOLAT roles $\:author.roles, $\:coach.roles and $\:participant.roles are supported. For each role the mapping can be defined to a corresponding role on the LTI resource. The following LTI roles are available\: $\:roles.lti.learner, $\:roles.lti.instructor, $\:roles.lti.administrator, $\:roles.lti.teachingAssistant, $\:roles.lti.contentDeveloper and $\:roles.lti.mentor.
+chelp.lti.scale=With the scaling factor the LTI results, which must have a value between 0 and 1 according to the LTI specification, can be scaled to a more practical value for the OpenOLAT course. For example, if you want an LTI exam to have a maximum of 10 points in OpenOLAT, you must specify a scaling factor of "10". If you want the transmitted score to be unmodified, use the factor "1".
 chelp.lti10=You can add additional parameters inside this input box that should be transferred to a learning application. You can e.g. tell this learning application to transfer a query from the learning platform OLAT. (External learning applications have to be able to process such transferred information; therefore it is necessary to have an agreement with your provider.)
-chelp.lti11=It is important that parameters are always transferred along with their proper names and values. According to the example above it should look like this\: LMS\=OLAT
+chelp.lti11=You have the choice between text attributes (identical values for each user) and additional dynamic user attributes (different for each user). You can add as many attributes as you wish, but note that the LTI resource must know which attributes are sent as this is not specified in the standard.
 chelp.lti12=By checking this box other users will be able to see the information sent. This information includes parameters such as user identification, course title, course element, etc.
 chelp.lti13=By clicking on "Launch Endpoint with BasicLTI Data" in the course view on top of your display of the data sent you will get to the homepage of your learning application.
 chelp.lti2=By means of a LTI page you can embed external software into your course, e.g. chats, media Wikis, test editors, or virtual chemistry labs.
@@ -21,35 +37,47 @@ chelp.lti6=Please indicate the key provided by the supplier of your external lea
 chelp.lti7=Please indicate the corresponding password to your key provided by the supplier of your external learning application (in the example above this would be "secret").
 chelp.lti8=By checking this box a user's first and last name will be transferred to your external learning application. Otherwise users will be able to use this application anonymously.
 chelp.lti9=By checking this box a user's e-mail address will be transferred to your external learning application.
+coach.roles=Coach
 command.preview=Show preview
-preview=Preview
 condition.accessibility.title=Access
 cshelp.lti1=LTI means "Learning Tool Interoperability," representing an IMS standard to embed external learning applications into learning platforms.
-display.config.custom=Specific configuration (name\=value)
-display.config.doDebug=Show information sent
-display.config.sendEmail=Send e-mail address to provider
-display.config.sendName=Send name to provider
-display.config.window=Display
-display.config.window.iframe=iframe
-display.config.window.window=New window
-display.config.height=$org.olat.course.nodes.scorm\:height.label
-display.config.width=Display width
+cutvalue.label=Score needed to pass
+display.config.custom=Additional attributes
+display.config.doDebug=Show all information transmitted on launch (Debug)
 display.config.free=Text
 display.config.free.userprops=User
-add=+
-remove=-
-height.auto=$org.olat.course.nodes.scorm\:height.auto
-scaleFactor=Scaling factor
-assessable.label=Transfer score from LTI 1.1
-cutvalue.label=Score needed to pass
-form.error.wrongFloat=$org.olat.course.assessment\:form.error.wrongFloat
-participant.roles=Participant
-coach.roles=Coach
-author.roles=Author
+display.config.height=$org.olat.core.gui.control.generic.iframe\:height.label
+display.config.sendEmail=Transmit e-mail address
+display.config.sendName=Transmit firstname/name
+display.config.width=Display width
+display.config.window=Display
+display.config.window.iframe=Embedded in course (iFrame)
+display.config.window.window=Open in new window
 error.hostmissing.long=In the tab "Page content" a host has to be configured for the external page "{0}"
 error.hostmissing.short=No host indicated for "{0}".
+form.error.wrongFloat=$org.olat.course.assessment\:form.error.wrongFloat
 form.title=Configuration of LTI page
+height.auto=$org.olat.core.gui.control.generic.iframe\:height.auto
 help.hover.lt.conf=Help to configure LTI page
 pane.tab.accessibility=Access
 pane.tab.ltconfig=Page content
+participant.roles=Participant
+passed.no=$org.olat.course.nodes.scorm\:passed.no
+passed.yes=org.olat.course.nodes.scorm\:passed.yes
+passed.yourpassed=$org.olat.course.nodes.scorm\:passed.yourpassed
+preview=Preview
+remove=-
+roles.lti.administrator=Administrator
+roles.lti.contentDeveloper=Content developer
+roles.lti.instructor=Instructor
+roles.lti.learner=Learner
+roles.lti.mentor=Mentor\t\t
+roles.lti.teachingAssistant=Teaching assistant
+roles.title.lti=LTI roles
+roles.title.oo=OpenOLAT roles
+scaleFactor=Scaling factor
+score.noscoreinfoyet=No score information is available for this LTI modules as you never launched the module.
+score.title=$org.olat.course.nodes.scorm\:score.title
+score.yourscore=$org.olat.course.nodes.scorm\:score.yourscore
+start=Start LTI learning module
 title_lti=LTI page
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
index 4c84f642437..c1ea6350db0 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
@@ -803,12 +803,11 @@
 						<property name="description" value="Can be used for LTI" />
 						<property name="propertyHandlers">
 							<list>
-								<ref bean="userPropertyFirstName" />
-								<ref bean="userPropertyLastName" />
-								<ref bean="userPropertyEmail" />	
 								<ref bean="userPropertyInstitutionalName" />
 								<ref bean="userPropertyInstitutionalUserIdentifier" />	
 								<ref bean="userPropertyInstitutionalEmail" />						
+								<ref bean="userPropertyOrgUnit" />
+								<ref bean="userPropertyStudySubject" />
 							</list>
 						</property>
 					</bean>
diff --git a/src/main/webapp/static/themes/openolat/all/modules/_course.scss b/src/main/webapp/static/themes/openolat/all/modules/_course.scss
index 8fae4b5294c..f736ba19787 100644
--- a/src/main/webapp/static/themes/openolat/all/modules/_course.scss
+++ b/src/main/webapp/static/themes/openolat/all/modules/_course.scss
@@ -35,6 +35,17 @@ div.b_module_singlepage_wrapper a.b_content_edit {position:absolute; top:0; righ
 div.b_module_singlepage_wrapper a.b_content_download { position:absolute; top:0; z-index:10; background: url(../openolat/images/docs/document_download.png) no-repeat top left; padding-left: 20px; margin-top:3px; min-height:19px; height:19px; }
 div.b_titled_wrapper div.b_module_singlepage_wrapper a.b_content_download { position:relative; padding-bottom:3px;}
 div.b_titled_wrapper div.b_module_singlepage_wrapper div.b_iframe_wrapper { margin-top:3px; }
+
+div.b_lti_edit_wrapper { 
+	/* fix positioning of preview button */ 
+	div.o_buttons_box_right {
+		z-index: 10;
+		top: 13px;
+		right: 20px;
+	}
+}
+
+
 /* CONTENT POPUP */
 #b_content_popup {float: right; background: url(../openolat/images/applications.png) no-repeat top left; width: 16px; height: 16px; margin: 3px;}
 	
-- 
GitLab