From 4f612384f3b5b7467add8718883453bfa405cbc0 Mon Sep 17 00:00:00 2001
From: fkiefer <none@none>
Date: Tue, 23 Aug 2016 16:39:08 +0200
Subject: [PATCH] OO-2133 new package org.olat.course.highscore

---
 .../org/olat/course/_spring/courseContext.xml |   2 +-
 .../highscore/manager/HighScoreManager.java   |  75 +++++
 .../highscore/ui/HighScoreController.java     | 287 ++++++++++++++++++
 .../highscore/ui/HighScoreEditController.java | 215 +++++++++++++
 .../ui/HighScoreFlexiTableModel.java          |  58 ++++
 .../highscore/ui/HighScoreRunController.java  | 215 +++++++++++++
 .../highscore/ui/HighScoreTableEntry.java     |  71 +++++
 .../highscore/ui/_content/highscore.html      | 183 +++++++++++
 .../ui/_content/histogram_score.html          |  16 +
 .../ui/_i18n/LocalStrings_de.properties       |  19 ++
 .../ui/_i18n/LocalStrings_en.properties       |  19 ++
 .../nodes/ms/MSCourseNodeEditController.java  |  26 +-
 .../nodes/ms/MSCourseNodeRunController.java   |  11 +-
 .../olat/course/nodes/ms/_content/run.html    |   3 +
 14 files changed, 1197 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/org/olat/course/highscore/manager/HighScoreManager.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/HighScoreController.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/HighScoreEditController.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/HighScoreFlexiTableModel.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/HighScoreRunController.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/HighScoreTableEntry.java
 create mode 100644 src/main/java/org/olat/course/highscore/ui/_content/highscore.html
 create mode 100644 src/main/java/org/olat/course/highscore/ui/_content/histogram_score.html
 create mode 100644 src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_de.properties
 create mode 100644 src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_en.properties

diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml
index aea0913520a..461f7ff2d47 100644
--- a/src/main/java/org/olat/course/_spring/courseContext.xml
+++ b/src/main/java/org/olat/course/_spring/courseContext.xml
@@ -8,7 +8,7 @@
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context.xsd">
 	
-	<context:component-scan base-package="org.olat.course.certificate.manager,org.olat.course.reminder" />
+	<context:component-scan base-package="org.olat.course.certificate.manager,org.olat.course.reminder,org.olat.course.highscore" />
 
 	<import resource="classpath:/org/olat/course/assessment/_spring/assessmentContext.xml"/>
 	<import resource="classpath:/org/olat/course/certificate/_spring/certificateContext.xml"/>
diff --git a/src/main/java/org/olat/course/highscore/manager/HighScoreManager.java b/src/main/java/org/olat/course/highscore/manager/HighScoreManager.java
new file mode 100644
index 00000000000..2921bc4ef7d
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/manager/HighScoreManager.java
@@ -0,0 +1,75 @@
+package org.olat.course.highscore.manager;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.olat.core.id.Identity;
+import org.olat.course.highscore.ui.HighScoreTableEntry;
+import org.olat.modules.assessment.AssessmentEntry;
+import org.olat.user.UserManager;
+import org.springframework.stereotype.Service;
+
+import edu.emory.mathcs.backport.java.util.Collections;
+
+@Service
+public class HighScoreManager {
+	
+	
+	public double[] sortRankByScore (List<AssessmentEntry>  assessEntries,
+			List<HighScoreTableEntry> allMembers, List<HighScoreTableEntry> ownIdMembers,
+			List<List<HighScoreTableEntry>> allPodium, List<Integer> ownIdIndices,	
+			int tableSize, Identity ownIdentity, UserManager userManager){
+
+		for (AssessmentEntry assessmentEntry : assessEntries) {
+			allMembers.add(new HighScoreTableEntry((assessmentEntry.getScore().floatValue()),
+					userManager.getUserDisplayName(assessmentEntry.getIdentity()),
+					assessmentEntry.getIdentity()));
+		}
+		assessEntries.clear();
+		//2 step comparator, sorts by score then own Identity comes first
+		Collections.sort(allMembers, new Comparator<HighScoreTableEntry>() {
+			public int compare(HighScoreTableEntry a, HighScoreTableEntry b){
+				int answer = Float.compare(b.getScore(), a.getScore());
+				if (answer == 0){
+					if (a.getIdentity().equals(ownIdentity))return -1;
+					else if (b.getIdentity().equals(ownIdentity))return 1;
+					else return 0;
+				} else {
+					return answer;
+				}				
+			}			
+		});
+
+		float buffer = -1;
+		int index = 0, rank = 1;
+		double[] allScores = new double[allMembers.size()];
+		for (int j = 0; j < allMembers.size(); j++) {
+			if (allMembers.get(j).getScore() < buffer){
+				index++;
+				rank = j + 1;
+			}
+			//first three position are put in separate lists,
+			if (index < 3){
+				allPodium.get(index).add(allMembers.get(j));
+			}
+			// finding position rank for own id
+			if (allMembers.get(j).getIdentity().equals(ownIdentity)){
+				ownIdIndices.add(j);
+			}
+			//setting rank for each member 
+			allMembers.get(j).setRank(rank);
+			buffer = allMembers.get(j).getScore();
+			//adding scores for histogram
+			allScores[j] = buffer;
+		}
+		//only getting member with own id for 2nd table
+		ownIdMembers.addAll(allMembers.stream()
+				.skip(tableSize)
+				.filter(a -> a.getIdentity().equals(ownIdentity))
+				.collect(Collectors.toList()));
+		
+		return allScores;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/highscore/ui/HighScoreController.java b/src/main/java/org/olat/course/highscore/ui/HighScoreController.java
new file mode 100644
index 00000000000..20d722924fd
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/HighScoreController.java
@@ -0,0 +1,287 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <p>
+* Licensed under the Apache License, Version 2.0 (the "License"); <br>
+* you may not use this file except in compliance with the License.<br>
+* You may obtain a copy of the License at
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <p>
+* Unless required by applicable law or agreed to in writing,<br>
+* software distributed under the License is distributed on an "AS IS" BASIS, <br>
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+* See the License for the specific language governing permissions and <br>
+* limitations under the License.
+* <p>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+package org.olat.course.highscore.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.chart.BarChartComponent;
+import org.olat.core.gui.components.chart.BarSeries;
+import org.olat.core.gui.components.table.BaseTableDataModelWithoutFilter;
+import org.olat.core.gui.components.table.CustomCellRenderer;
+import org.olat.core.gui.components.table.DefaultColumnDescriptor;
+import org.olat.core.gui.components.table.TableController;
+import org.olat.core.gui.components.table.TableGuiConfiguration;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.DefaultController;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.id.Identity;
+import org.olat.core.util.Util;
+import org.olat.course.groupsandrights.CourseGroupManager;
+import org.olat.course.nodes.AssessableCourseNode;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.assessment.AssessmentEntry;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.repository.RepositoryService;
+import org.olat.resource.OLATResource;
+import org.olat.user.DisplayPortraitController;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+public class HighScoreController extends DefaultController{
+	
+	private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(HighScoreController.class);
+
+	private final VelocityContainer myContent;
+
+	protected HighScoreController(UserRequest ureq, WindowControl wControl) {
+		super(wControl);
+		Translator fallbackTrans = Util.createPackageTranslator(CourseNode.class, ureq.getLocale());
+
+		myContent = new VelocityContainer("highscore", VELOCITY_ROOT + "/highscore.html", fallbackTrans, this);
+
+	}
+	
+//	Identity markedIdentity = ureq.getIdentity();
+//	boolean revealIdentities = false;
+//	HighScoreController highScoreCtr = new HighScoreController(ureq, wControl, msCourseNode, markedIdentity, revealIdentities);
+//	myContent.put("highScore", highScoreCtr.getInitialComponent());
+	
+	public HighScoreController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
+			AssessableCourseNode courseNode, Identity markedIdentity, boolean revealIdentities) {
+		super(wControl);	
+		Translator fallbackTrans = Util.createPackageTranslator(CourseNode.class, ureq.getLocale());
+		Translator trans = Util.createPackageTranslator(HighScoreController.class, ureq.getLocale(), fallbackTrans);
+		
+		String s = VELOCITY_ROOT;
+
+		myContent = new VelocityContainer("highscore", VELOCITY_ROOT + "/highscore.html", trans, this);
+
+//		DisplayPortraitController portrait = new DisplayPortraitController(ureq, wControl, markedIdentity, false, true);
+//		Component compi = portrait.getInitialComponent();
+//		myContent.put("portrait", compi);
+		
+		RepositoryEntry entry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		CourseGroupManager cgm = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
+		List<Identity> participants = cgm.getParticipants();
+		List<Identity>  coaches = cgm.getCoaches();
+		//cgm.getParticipantsFromAreas();
+		List<Identity> participantz = cgm.getParticipantsFromBusinessGroups();
+		List<AssessmentEntry>  assessEntries = userCourseEnv.getCourseEnvironment().getAssessmentManager().getAssessmentEntries(courseNode);
+				
+		List<HighScoreTableEntry> members = new ArrayList<>();
+		List<HighScoreTableEntry> members2 = new ArrayList<>();
+		Identity ownIdentity = ureq.getIdentity();
+
+		for (AssessmentEntry assessmentEntry : assessEntries) {
+			members.add(new HighScoreTableEntry((int)(assessmentEntry.getScore().floatValue() * 10),
+					assessmentEntry.getIdentity().getName(), assessmentEntry.getIdentity()));			
+		}
+		
+//		for (Identity identity : participants) {
+//			if (participants.indexOf(identity) < 5) {
+//				members.add(new TableEntry((int)(Math.random()*100), identity.getName(), identity));					
+////			} else if (participants.indexOf(identity) > 2 && ownIdentity.getKey().equals(identity.getKey())){
+////				members2.add(new TableEntry((int)(Math.random()*100), identity.getName(), identity));
+//			}
+//		}
+		
+		members = members.stream()
+				.sorted((a,b) -> Float.compare(b.getScore(),a.getScore()))
+				.collect(Collectors.toList());
+		
+		members2 = members.stream()
+				.skip(2)
+				.filter(a -> a.getIdentity().equals(ownIdentity))
+				.collect(Collectors.toList());
+		
+		final List<HighScoreTableEntry> members3 = members;
+		int[] indices = IntStream.range(0, members.size())
+                .filter(i -> members3.get(i).getIdentity().equals(ownIdentity))
+                .toArray();
+		
+		members = members.stream()
+				.limit(4)
+				.collect(Collectors.toList());
+		
+		
+		String[] localizer = { "first", "second", "third" };
+		for (int i = 0; i < localizer.length; i++) {
+			myContent.contextPut(localizer[i], (members.size() > i) ? members.get(i).getName() : "");
+			myContent.contextPut("score" + (i + 1), (members.size() > i) ? members.get(i).getScore() : "");
+			if (members.size() > i) {
+				DisplayPortraitController portrait = new DisplayPortraitController(ureq, wControl,
+						members.get(i).getIdentity(), false, true);
+				Component compi = portrait.getInitialComponent();
+				myContent.put("portrait" + (i + 1), compi);
+			}
+		}
+//		myContent.contextPut("first", (members.size()>0)?members.get(0).getName():"");
+//		myContent.contextPut("second", (members.size()>1)?members.get(1).getName():"");
+//		myContent.contextPut("third", (members.size()>2)?members.get(2).getName():"");
+//		BarChartComponent barchart = new BarChartComponent("barchart");
+//		barchart.addSeries(new BarSeries());
+		
+		BarChartComponent chartCmp = new BarChartComponent("stat");
+	      List<String> labels = Arrays.asList(new String[]{"eins","zwei","drei","vier"});
+	      List<Integer> values = Arrays.asList(new Integer[]{12,55,33,24});
+	      BarSeries serie = new BarSeries();
+	      for(int i=0; i<labels.size(); i++) {
+	        double value = values.get(i).doubleValue();
+	        String category = labels.get(i);
+	        serie.add(value, category);
+	      }
+	      chartCmp.addSeries(serie);
+		
+		myContent.put("barchart", chartCmp);
+		
+		TableGuiConfiguration tgc = new TableGuiConfiguration();
+		tgc.setPreferencesOffered(true, "TableGuiDemoPrefs");
+		TableController table = new TableController(tgc, ureq, getWindowControl(), trans);
+
+		TableGuiConfiguration tgc2 = new TableGuiConfiguration();
+		tgc2.setPreferencesOffered(true, "TableGuiDemoPrefs");
+		TableController table2 = new TableController(tgc2, ureq, getWindowControl(), trans);
+		
+		TableController[] tables = {table,table2};
+		for (int i = 0; i < tables.length; i++) {
+			for (int j = 0; j < 3; j++) {
+				tables[i].addColumnDescriptor(
+						new DefaultColumnDescriptor("highscore.table.header" + (j+1), j, null, ureq.getLocale()));
+			}
+		}
+		
+
+//		table.addColumnDescriptor(new DefaultColumnDescriptor("highscore.table.header1", 0, null, ureq.getLocale()));
+//		table.addColumnDescriptor(new DefaultColumnDescriptor("highscore.table.header2", 1, null, ureq.getLocale()));
+//		table.addColumnDescriptor(new DefaultColumnDescriptor("highscore.table.header3", 2, null, ureq.getLocale()));
+//		table.addColumnDescriptor(new DefaultColumnDescriptor("guidemo.table.header4", 3, null, ureq.getLocale()));
+//		table.addColumnDescriptor(new DefaultColumnDescriptor("guidemo.table.header5", 4, null, ureq.getLocale()));
+//		table.addColumnDescriptor(new CustomRenderColumnDescriptor("guidemo.table.header6", 5, null, ureq.getLocale(),
+//				CustomRenderColumnDescriptor.ALIGNMENT_CENTER, new ImageCellRenderer()));
+
+		table.setTableDataModel(new SampleTableModel(members,0));
+		myContent.put("table", table.getInitialComponent());
+		
+		if(!members2.isEmpty()){
+			table2.setTableDataModel(new SampleTableModel(members2,indices[0]));
+			myContent.put("table2", table2.getInitialComponent());
+		}		
+		setInitialComponent(myContent);
+
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	protected void doDispose() {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
+class SampleTableModel extends BaseTableDataModelWithoutFilter<java.util.List<Object>> {
+	@Autowired
+	private RepositoryManager repositoryManager;
+
+	private int COLUMN_COUNT = 3;
+	private List<List<Object>> entries;
+	
+	
+	public SampleTableModel(List<HighScoreTableEntry> members, int rank) {
+		
+		RepositoryManager rm = RepositoryManager.getInstance();
+		RepositoryService rs = CoreSpringFactory.getImpl(RepositoryService.class);
+
+		RepositoryEntry entry = rm.lookupRepositoryEntry(34570267L); 
+		
+		List<OLATResource> resourcesForReservations = new ArrayList<>();
+		if(entry != null) {
+			resourcesForReservations.add(entry.getOlatResource());
+		}
+		
+//		List<ResourceReservation> reservations = acService.getReservations(resourcesForReservations);
+		
+//		List<TableEntry> members = new ArrayList<TableEntry>();
+//		members.add(new TableEntry(22,"Fabian Kiefer"));
+//		members.add(new TableEntry(11,"Flad Käfer"));
+//		members.add(new TableEntry(17,"Stefanie Müller"));
+//		members.add(new TableEntry(35,"Heinrich Kiefer"));
+//		members.add(new TableEntry(17,"Frentix Uzzer"));
+		
+
+		
+		
+		int iEntries = members.size() <= 10 ? members.size() : 10;
+		this.entries = new ArrayList<List<Object>>(iEntries);
+		for (int i=0; i < iEntries; i++) {
+			List<Object> row = new ArrayList<Object>(3);
+			row.add(rank != 0 ? rank+1 : Integer.toString(i+1));
+			row.add(Float.toString(members.get(i).getScore()));
+			row.add(members.get(i).getName());
+//			row.add("");
+			entries.add(row);
+		}
+	}
+
+	public int getColumnCount() {
+		return COLUMN_COUNT;
+	}
+
+	public int getRowCount() {
+		return entries.size();
+	}
+
+	public Object getValueAt(int row, int col) {
+		List<Object> entry = entries.get(row);
+		return entry.get(col);
+	}
+}
+class ImageCellRenderer implements CustomCellRenderer {
+
+	public void render(StringOutput sb, Renderer renderer, Object val, Locale locale, int alignment, String action) {
+		sb.append("<img src=\"");
+		Renderer.renderStaticURI(sb, "images/openolat/openolat_logo_16.png");
+		sb.append("\" alt=\"An image within a table...\" />");
+	}
+	
+}
diff --git a/src/main/java/org/olat/course/highscore/ui/HighScoreEditController.java b/src/main/java/org/olat/course/highscore/ui/HighScoreEditController.java
new file mode 100644
index 00000000000..bc0da630439
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/HighScoreEditController.java
@@ -0,0 +1,215 @@
+package org.olat.course.highscore.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+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.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.ModuleConfiguration;
+
+public class HighScoreEditController extends FormBasicController {
+	
+	private final static String[] yesOrNoKeys = new String[] { "highscore.all", "highscore.bestonly" };
+	
+	/** configuration: boolean has a podium */
+	public static final String CONFIG_KEY_HIGHSCORE= "allowHighscore";
+	/** configuration: boolean has a podium */
+	public static final String CONFIG_KEY_PODIUM = "podium";
+	/** configuration: boolean has a histogram */
+	public static final String CONFIG_KEY_HISTOGRAM = "histogram";
+	/** configuration: boolean has a listing */
+	public static final String CONFIG_KEY_LISTING = "listing";
+	/** configuration: boolean bestonly or all */
+	public static final String CONFIG_KEY_BESTONLY = "bestOnly";
+	/** configuration: integer size of table */
+	public static final String CONFIG_KEY_NUMUSER = "numUser";
+	/** configuration: boolean anonymize */
+	public static final String CONFIG_KEY_ANONYMIZE = "anonymize";
+	
+	private SingleSelection horizontalRadioButtons;
+	private SelectionElement allowHighScore;
+	private SelectionElement showPodium;
+	private SelectionElement showHistogram;
+	private SelectionElement showListing;
+	private SelectionElement displayAnonymous;
+	private TextElement numTableRows;
+	private CourseNode msNode;	
+	private ModuleConfiguration config;
+	
+	public HighScoreEditController(UserRequest ureq, WindowControl wControl, CourseNode msNode, UserCourseEnvironment euce) {
+		super(ureq, wControl, FormBasicController.LAYOUT_DEFAULT);
+		this.msNode = msNode;
+
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+
+		allowHighScore = uifactory.addCheckboxesHorizontal("highscore.show", formLayout, new String[] { "xx" },
+				new String[] { null });
+		allowHighScore.addActionListener(FormEvent.ONCLICK);
+		allowHighScore.select("xx", true);
+
+		uifactory.addSpacerElement("spacer", formLayout, false);
+
+		showPodium = uifactory.addCheckboxesHorizontal("highscore.podium", formLayout, new String[] { "xx" },
+				new String[] { null });
+		showHistogram = uifactory.addCheckboxesHorizontal("highscore.histogram", formLayout, new String[] { "xx" },
+				new String[] { null });
+		showListing = uifactory.addCheckboxesHorizontal("highscore.listing", formLayout, new String[] { "xx" },
+				new String[] { null });
+		showListing.addActionListener(FormEvent.ONCLICK);
+
+		// Translate the keys to the yes and no option values
+		final String[] yesOrNoOptions = new String[yesOrNoKeys.length];
+		for (int i = 0; i < yesOrNoKeys.length; i++) {
+			yesOrNoOptions[i] = translate(yesOrNoKeys[i]);
+		}
+		horizontalRadioButtons = uifactory.addRadiosHorizontal("highscore.void", formLayout, yesOrNoKeys,
+				yesOrNoOptions);
+		// A default value is needed for show/hide rules
+		horizontalRadioButtons.select(yesOrNoKeys[1], true);
+		horizontalRadioButtons.addActionListener(FormEvent.ONCLICK);//why
+		
+		numTableRows = uifactory.addTextElement("textField", "highscore.tablesize", 4, "10", formLayout);
+		numTableRows.setMandatory(true);
+		numTableRows.setNotEmptyCheck("highscore.emptycheck");
+		
+		displayAnonymous = uifactory.addCheckboxesHorizontal("highscore.anonymize", formLayout, new String[] { "xx" }, new String[] { null });
+		
+		// Create submit and cancel buttons
+		final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout",
+				getTranslator());
+		formLayout.add(buttonLayout);
+		uifactory.addFormSubmitButton("submit", buttonLayout);
+		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		
+		setFromConfig();
+	}
+	
+	@Override
+	protected void formInnerEvent (UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == allowHighScore){
+			activateForm(false);			
+		} else if (source == showListing){
+			activateListing();
+		} else if (source == horizontalRadioButtons){
+			activateTopUsers();
+		}
+	}
+	
+	private void setFromConfig() {
+		config = msNode.getModuleConfiguration();
+		boolean allowhighscore = config.getBooleanSafe(CONFIG_KEY_HIGHSCORE);
+		allowHighScore.select("xx", allowhighscore);
+		showPodium.select("xx", config.getBooleanSafe(CONFIG_KEY_PODIUM));
+		showHistogram.select("xx", config.getBooleanSafe(CONFIG_KEY_HISTOGRAM));
+		boolean listing = config.getBooleanSafe(CONFIG_KEY_LISTING);
+		showListing.select("xx", listing);
+		int showAll = (int) config.get(CONFIG_KEY_BESTONLY);
+		horizontalRadioButtons.select(yesOrNoKeys[showAll], true);
+		if (showAll == 0 || !listing) {
+			numTableRows.setVisible(false);
+		}
+		displayAnonymous.setVisible(listing);
+		horizontalRadioButtons.setVisible(listing);
+		numTableRows.setValue(config.get(CONFIG_KEY_NUMUSER).toString());
+		activateForm(true);
+	}
+	
+	private void activateForm (boolean init){
+		boolean formactive = allowHighScore.isSelected(0);
+		SelectionElement[] checkboxes = {showPodium,showHistogram,showListing,displayAnonymous};		
+		for (int i = 0; i < checkboxes.length; i++) {
+			checkboxes[i].setEnabled(formactive);
+			if (!init) {
+				checkboxes[i].select("xx", i != 3 ? formactive : false);
+			}
+		}
+		if (!init) {
+			displayAnonymous.setVisible(formactive);
+			horizontalRadioButtons.setVisible(formactive);
+			horizontalRadioButtons.select(yesOrNoKeys[1], true);
+			numTableRows.setVisible(formactive);
+		}
+	}
+
+	private void activateListing() {
+		boolean listingactive = showListing.isSelected(0);
+		displayAnonymous.setVisible(listingactive);
+		horizontalRadioButtons.setVisible(listingactive);
+		horizontalRadioButtons.select(yesOrNoKeys[1], true);
+		numTableRows.setVisible(listingactive);
+	}
+
+	private void activateTopUsers() {
+		int all = horizontalRadioButtons.getSelected();
+		numTableRows.setVisible(all != 0);
+	}
+	
+	public void updateModuleConfiguration(ModuleConfiguration moduleConfiguration) {
+		moduleConfiguration.set(CONFIG_KEY_HIGHSCORE, allowHighScore.isSelected(0));
+		moduleConfiguration.set(CONFIG_KEY_PODIUM, showPodium.isSelected(0));
+		moduleConfiguration.set(CONFIG_KEY_HISTOGRAM, showHistogram.isSelected(0));
+		moduleConfiguration.set(CONFIG_KEY_LISTING, showListing.isSelected(0));
+		if (showListing.isSelected(0)) {
+			moduleConfiguration.set(CONFIG_KEY_ANONYMIZE, displayAnonymous.isSelected(0));
+			moduleConfiguration.set(CONFIG_KEY_BESTONLY, horizontalRadioButtons.getSelected());
+			moduleConfiguration.set(CONFIG_KEY_NUMUSER, convertToInteger(numTableRows.getValue()));
+		}
+	}
+
+	public static int convertToInteger(String str) {
+		if (str == null) {
+			return 10;
+		}
+		int length = str.length();
+		if (length == 0) {
+			return 10;
+		}
+		int i = 0;
+		if (str.charAt(0) == '-') {
+			if (length == 1) {
+				return 10;
+			}
+			i = 1;
+		}
+		for (; i < length; i++) {
+			char c = str.charAt(i);
+			if (c < '0' || c > '9') {
+				return 10;
+			}
+		}
+		return Integer.valueOf(str);
+	}
+
+	
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void doDispose() {
+		// TODO Auto-generated method stub
+
+	}
+	
+	
+
+}
diff --git a/src/main/java/org/olat/course/highscore/ui/HighScoreFlexiTableModel.java b/src/main/java/org/olat/course/highscore/ui/HighScoreFlexiTableModel.java
new file mode 100644
index 00000000000..e505ade2ad5
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/HighScoreFlexiTableModel.java
@@ -0,0 +1,58 @@
+package org.olat.course.highscore.ui;
+
+import java.util.List;
+
+import org.olat.core.gui.components.table.TableDataModel;
+
+public class HighScoreFlexiTableModel implements TableDataModel<HighScoreTableEntry> {
+	
+	private final int COLUMN_COUNT = 3;
+	private final int ROW_COUNT;
+	private final List<HighScoreTableEntry> entries;
+
+	public HighScoreFlexiTableModel(List<HighScoreTableEntry> entries) {
+		this.ROW_COUNT = entries.size();
+		this.entries = entries;
+	}
+
+	@Override
+	public int getColumnCount() {
+		return COLUMN_COUNT;
+	}
+
+	@Override
+	public int getRowCount() {
+		return ROW_COUNT;
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		HighScoreTableEntry entry = entries.get(row);
+		switch(col) {
+			case 0: return entry.getRank();
+			case 1: return entry.getScore();
+			case 2: return entry.getName();
+			default: return entry;
+		}
+	}
+
+	@Override
+	public HighScoreTableEntry getObject(int row) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public void setObjects(List objects) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public Object createCopyWithEmptyList() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+	
+
+}
diff --git a/src/main/java/org/olat/course/highscore/ui/HighScoreRunController.java b/src/main/java/org/olat/course/highscore/ui/HighScoreRunController.java
new file mode 100644
index 00000000000..b09f5609a9a
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/HighScoreRunController.java
@@ -0,0 +1,215 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <p>
+* Licensed under the Apache License, Version 2.0 (the "License"); <br>
+* you may not use this file except in compliance with the License.<br>
+* You may obtain a copy of the License at
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <p>
+* Unless required by applicable law or agreed to in writing,<br>
+* software distributed under the License is distributed on an "AS IS" BASIS, <br>
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+* See the License for the specific language governing permissions and <br>
+* limitations under the License.
+* <p>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+package org.olat.course.highscore.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.chart.BarSeries;
+import org.olat.core.gui.components.chart.StatisticsComponent;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableCssDelegate;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelImpl;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableRendererType;
+import org.olat.core.gui.components.velocity.VelocityContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.id.Identity;
+import org.olat.course.highscore.manager.HighScoreManager;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.ModuleConfiguration;
+import org.olat.modules.assessment.AssessmentEntry;
+import org.olat.user.DisplayPortraitController;
+import org.olat.user.UserAvatarMapper;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+public class HighScoreRunController extends FormBasicController{
+	
+	private FlexiTableDataModel<HighScoreTableEntry> tableDataModel, tableDataModel2;
+	private List<HighScoreTableEntry> allMembers, ownIdMembers;
+	private List<List<HighScoreTableEntry>> allPodium;
+	private List<Integer> ownIdIndices;
+	private int tableSize;
+	private Identity ownIdentity;
+	private boolean viewTable, viewHistogram, viewPodium, viewHighscore;
+	private double[] allScores;
+	
+	@Autowired
+	private HighScoreManager highScoreManager;
+	@Autowired
+	private UserManager userManager;
+
+
+	public HighScoreRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
+			CourseNode courseNode) {
+		super(ureq, wControl, "highscore");
+		
+		List<AssessmentEntry>  assessEntries = userCourseEnv.getCourseEnvironment()
+				.getAssessmentManager().getAssessmentEntries(courseNode);
+		
+		ModuleConfiguration config = courseNode.getModuleConfiguration();		
+		//TODO initialize
+		viewHighscore = config.getBooleanSafe(HighScoreEditController.CONFIG_KEY_HIGHSCORE);
+		if (!viewHighscore)return;
+		viewTable = config.getBooleanSafe(HighScoreEditController.CONFIG_KEY_LISTING);
+		viewHistogram = config.getBooleanSafe(HighScoreEditController.CONFIG_KEY_HISTOGRAM);
+		viewPodium = config.getBooleanSafe(HighScoreEditController.CONFIG_KEY_PODIUM);
+		int bestOnly = (int) config.get(HighScoreEditController.CONFIG_KEY_BESTONLY);
+		tableSize = bestOnly != 0 ? (int) config.get(HighScoreEditController.CONFIG_KEY_NUMUSER) : assessEntries.size();
+		ownIdentity = ureq.getIdentity();
+		initLists();
+		
+		 allScores = highScoreManager.sortRankByScore(assessEntries, allMembers, ownIdMembers,
+				 allPodium, ownIdIndices, tableSize, ownIdentity, userManager);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		
+		VelocityContainer mainVC = ((FormLayoutContainer) formLayout).getFormItemComponent();
+		mainVC.put("loadd3js", new StatisticsComponent("d3loader"));
+
+		if (viewHistogram) {
+			VelocityContainer scoreHistogramVC = createVelocityContainer("histogram_score");
+			scoreHistogramVC.contextPut("datas", BarSeries.datasToString(allScores));
+			scoreHistogramVC.contextPut("cutValue", 
+					ownIdIndices.size() > 0 ? allMembers.get(ownIdIndices.get(0)).getScore()	: "");
+			
+			UserAvatarMapper mapper = new UserAvatarMapper(false);
+			String mapperPath = registerMapper(ureq, mapper);
+			String identityMapperPath = mapper.createPathFor(mapperPath, ownIdentity);
+			scoreHistogramVC.contextPut("mapperUrl", identityMapperPath);
+
+			mainVC.put("scoreHistogram", scoreHistogramVC);
+		}
+		if (viewPodium) {			
+			String[] localizer = { "first", "second", "third" };
+			for (int i = 0; i < localizer.length; i++) {
+				StringBuilder sb = new StringBuilder();
+				for (HighScoreTableEntry te : allPodium.get(i)) {
+					sb.append(te.getName());
+					sb.append("</br>");
+				}			
+				mainVC.contextPut(localizer[i], allPodium.get(i).size() > 0 ? sb.toString() : "") ;
+				mainVC.contextPut("score" + (i + 1), allPodium.get(i).size() > 0 ? 
+						allPodium.get(i).get(0).getScore() : "");
+				if (tableSize > i) {
+					DisplayPortraitController portrait = new DisplayPortraitController(ureq, getWindowControl(),
+							allPodium.get(i).get(0).getIdentity(), i == 0, true);
+					Component compi = portrait.getInitialComponent();
+					mainVC.put("portrait" + (i + 1), compi);
+				}
+			}
+		}
+		if (viewTable) {
+			FlexiTableColumnModel tableColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+			tableColumnModel.addFlexiColumnModel(
+					new DefaultFlexiColumnModel("highscore.table.header1", HighScoreTableEntry.RANK));
+			tableColumnModel.addFlexiColumnModel(
+					new DefaultFlexiColumnModel("highscore.table.header2", HighScoreTableEntry.SCORE));
+			tableColumnModel.addFlexiColumnModel(
+					new DefaultFlexiColumnModel("highscore.table.header3", HighScoreTableEntry.NAME));
+			
+			//trim to tableSize
+			allMembers.subList(tableSize, allMembers.size()).clear();
+
+			tableDataModel = new FlexiTableDataModelImpl<HighScoreTableEntry>(
+					new HighScoreFlexiTableModel(allMembers), tableColumnModel);
+			FlexiTableElement topTenTable = uifactory.addTableElement(
+					getWindowControl(), "table", tableDataModel, getTranslator(), formLayout);
+			topTenTable.setNumOfRowsEnabled(false);
+			topTenTable.setCustomizeColumns(false);
+			topTenTable.setCssDelegate(new MarkedMemberCssDelegate(false));
+
+
+			if (!ownIdMembers.isEmpty()) {
+				tableDataModel2 = new FlexiTableDataModelImpl<HighScoreTableEntry>(
+						new HighScoreFlexiTableModel(ownIdMembers),
+						tableColumnModel);
+				FlexiTableElement tableElement = uifactory.addTableElement(
+						getWindowControl(), "table2", tableDataModel2, getTranslator(), formLayout);
+				tableElement.setNumOfRowsEnabled(false);
+				tableElement.setCustomizeColumns(false);
+				tableElement.setCssDelegate(new MarkedMemberCssDelegate(true));
+			}
+		}
+
+	}
+	
+	private void initLists(){
+		ownIdIndices = new ArrayList<>();
+		allMembers = new ArrayList<>();
+		ownIdMembers = new ArrayList<>();
+		allPodium = new ArrayList<>();
+		allPodium.add(new ArrayList<>());
+		allPodium.add(new ArrayList<>());
+		allPodium.add(new ArrayList<>());
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	protected void doDispose() {
+		// TODO Auto-generated method stub
+		
+	}
+	
+	private class MarkedMemberCssDelegate extends DefaultFlexiTableCssDelegate {
+		private boolean mark;
+		public MarkedMemberCssDelegate(boolean mark) {
+			this.mark = mark;
+		}
+		@Override
+		public String getRowCssClass(FlexiTableRendererType type, int pos) {
+			return ownIdIndices.get(0) < tableSize && pos == ownIdIndices.get(0) || mark ? "o_row_selected" : null;
+		}
+	}
+
+	public boolean isViewHighscore() {
+		return viewHighscore;
+	}
+	
+	
+
+}
+
diff --git a/src/main/java/org/olat/course/highscore/ui/HighScoreTableEntry.java b/src/main/java/org/olat/course/highscore/ui/HighScoreTableEntry.java
new file mode 100644
index 00000000000..9c57275d50e
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/HighScoreTableEntry.java
@@ -0,0 +1,71 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <p>
+* Licensed under the Apache License, Version 2.0 (the "License"); <br>
+* you may not use this file except in compliance with the License.<br>
+* You may obtain a copy of the License at
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <p>
+* Unless required by applicable law or agreed to in writing,<br>
+* software distributed under the License is distributed on an "AS IS" BASIS, <br>
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+* See the License for the specific language governing permissions and <br>
+* limitations under the License.
+* <p>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+package org.olat.course.highscore.ui;
+
+import org.olat.core.id.Identity;
+
+public class HighScoreTableEntry {
+	
+	public static int RANK = 0;
+	public static int SCORE = 1;
+	public static int NAME = 2;
+	
+	private float score;
+	private String name;
+	private Identity identity;
+	private int rank;
+	
+	public HighScoreTableEntry(float score, String name, Identity identity){
+		this.score = score;
+		this.name = name;
+		this.identity = identity;
+	}
+	
+	public float getScore (){
+		return score;
+	}
+	public String getName(){
+		return name;
+	}
+	public Identity getIdentity() {
+		return identity;
+	}
+	public int getRank() {
+		return rank;
+	}
+	public void setScore(float score) {
+		this.score = score;
+	}
+	public void setName(String name) {
+		this.name = name;
+	}
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+	public void setRank(int rank) {
+		this.rank = rank;
+	}
+	
+}
diff --git a/src/main/java/org/olat/course/highscore/ui/_content/highscore.html b/src/main/java/org/olat/course/highscore/ui/_content/highscore.html
new file mode 100644
index 00000000000..3e398ff8b68
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/_content/highscore.html
@@ -0,0 +1,183 @@
+<style>
+.line {
+	fill: none;
+	stroke: steelblue;
+	stroke-width: 2px;
+}
+
+
+.o_podium {
+	position: relative;
+	vertical-align: bottom;
+	height: 300px;
+	margin-bottom: 50px;
+}
+
+.o_step {
+	width: 30%;
+	position: absolute;
+	bottom: 0;
+}
+.o_step:before {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	text-align: center;
+}
+
+.o_step .o_name {
+	position: absolute;
+	top: 100%;
+	width: 100%;
+	text-align: center;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	padding-top: 1em;
+}
+
+.o_step .o_score {
+	width: 100%;
+	text-align: center;
+	position: absolute;
+	top: -20px;
+}
+
+.o_first {
+	height: 150px;
+	background-color: gold;
+	left: 30%;
+}
+.o_first:before {
+	content: "1";
+	font-size: 700%;
+}
+
+.o_second {
+	height: 100px;
+	background-color: silver;
+	left: 0;
+}
+.o_second:before {
+	content: "2";
+	font-size: 500%;
+}
+
+.o_third {
+	height: 80px;
+	background-color: #965A38;
+	left: 60%;
+}
+
+.o_third:before {
+	content: "3";
+	font-size: 300%;
+}
+
+.o_step .o_portrait {
+	position: absolute;
+	width: 100%;
+	text-align: center;
+}
+.o_step.o_first .o_portrait {
+	top: -125px;
+}
+.o_step.o_second .o_portrait {
+	top: -70px;
+}
+.o_step.o_second .o_portrait .o_portrait_avatar_small, 
+.o_step.o_second .o_portrait .o_portrait_dummy_small, 
+.o_step.o_second .o_portrait .o_portrait_dummy_female_small, 
+.o_step.o_second .o_portrait .o_portrait_dummy_male_small, 
+.o_step.o_second .o_portrait .o_portrait_anonymous_small {
+	width: 50px;
+	height: 50px;
+}
+
+.o_step.o_third .o_portrait {
+	top: -50px;
+}
+
+.o_histogram {
+	position: relative;
+	bottom: -40px;
+	margin-bottom: 40px;
+}
+.o_histogram .d3chart {
+	width: 100%;
+	padding-top: 50px;
+	height: 300px;
+}
+
+.o_histogram .d3chart .axis path, 
+.o_histogram .d3chart .axis line {
+	stroke: #888;
+}
+.o_histogram .d3chart text {
+	fill: #888;
+}
+
+/* Align both tables the same way */
+.o_listing table th:nth-of-type(1),
+.o_listing table th:nth-of-type(2) {
+	width: 5em;
+}
+
+</style>
+
+
+<div class="panel panel-default o_comment">
+	<div class="panel-heading" data-toggle="collapse"
+		data-target="#collapseComment">
+		<h4 class="panel-title">
+			<i id="collapseCommentToggler"
+				class="o_icon o_icon-fw o_icon_close_togglebox"> </i>
+			$r.translate("highscore.title")
+		</h4>
+	</div>
+	<div id="collapseComment" class="panel-collapse collapse in">
+		<div class="panel-body">
+			<div class="container-fluid">
+			<div class="row clearfix">
+				#if ($first)
+				<div class="col-sm-6 o_podium">
+					<div class="o_step o_first">
+						#if ($r.available("portrait1")) $r.render("portrait1") #end
+						<div class="o_name">$first</div>
+						<div class="o_score">$score1 $r.translate("graph.axis.points")</div>
+					</div>
+	
+					<div class="o_step o_second">
+						#if ($r.available("portrait2")) $r.render("portrait2") #end
+						<div class="o_name">$second</div>
+						<div class="o_score">$score2 $r.translate("graph.axis.points")</div>
+					</div>
+	
+					<div class="o_step o_third">
+						#if ($r.available("portrait3")) $r.render("portrait3") #end
+						<div class="o_name">$third</div>
+						<div class="o_score">$score3 $r.translate("graph.axis.points")</div>
+					</div>
+	
+				</div>
+				#end
+				
+				#if ($r.available("scoreHistogram")) 
+				<div class="col-sm-6 o_histogram">
+					$r.render("scoreHistogram")
+				</div>
+				#end
+
+			</div>
+			</div>
+			#if ($r.available("table"))
+			<div class="o_listing">
+				<h4>$r.translate("highscore.title")</h4>
+				$r.render("table")
+				#if ($r.available("table2")) $r.render("table2") #end
+			</div>
+			#end
+		</div>
+	</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/highscore/ui/_content/histogram_score.html b/src/main/java/org/olat/course/highscore/ui/_content/histogram_score.html
new file mode 100644
index 00000000000..5291b3bf04c
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/_content/histogram_score.html
@@ -0,0 +1,16 @@
+<div id='histogram_$r.getCId()' class='d3chart'>
+
+<script type='text/javascript'>
+/* <![CDATA[ */
+jQuery(function () {
+	jQuery('#histogram_$r.getCId()').qtiStatistics('highScore', {
+		values: [$datas],
+        cut: #if($cutValue) $cutValue #else null #end,
+        mapperUrl: '$mapperUrl',
+        xBottomLegend: '$r.translate("graph.axis.points")',
+        yLeftLegend: '$r.translate("graph.axis.percent")',
+        yRightLegend: '$r.translate("graph.axis.absolute")'
+	});
+});
+/* ]]> */</script>
+</div>
diff --git a/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_de.properties
new file mode 100644
index 00000000000..c328827ee01
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_de.properties
@@ -0,0 +1,19 @@
+#Mon Aug 15 11:26:04 CET 2016
+highscore.table.header1=Rang
+highscore.table.header2=Punkte
+highscore.table.header3=Name
+graph.axis.points=Punkte
+graph.axis.percent=Prozent
+graph.axis.absolute=Absolut
+highscore.title=Highscore
+pane.tab.highscore=HighScore
+highscore.podium=Siegertreppchen
+highscore.histogram=Histogramm
+highscore.listing=Auflistung
+highscore.show=Highscore anzeigen
+highscore.all=alle
+highscore.bestonly=beste Benutzer anzeigen
+highscore.void=
+highscore.tablesize=Anzahl bester Benutzer
+highscore.anonymize=Anonymisieren
+highscore.emptycheck=Dieses Textfeld darf nicht leer sein!
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_en.properties
new file mode 100644
index 00000000000..6abeaa64b4f
--- /dev/null
+++ b/src/main/java/org/olat/course/highscore/ui/_i18n/LocalStrings_en.properties
@@ -0,0 +1,19 @@
+#Mon Aug 15 11:26:04 CET 2016
+highscore.table.header1=Rank
+highscore.table.header2=Score
+highscore.table.header3=Name
+graph.axis.points=Points
+graph.axis.percent=Percent
+graph.axis.absolute=Absolute
+highscore.title=Highscore
+pane.tab.highscore=HighScore
+highscore.podium=Podium
+highscore.histogram=Histogram
+highscore.listing=Listing
+highscore.show=Show Highscore
+highscore.all=all
+highscore.bestonly=show top users
+highscore.void=
+highscore.tablesize=Number of top users
+highscore.anonymize=Anonymize
+highscore.emptycheck=This Textfield must not be empty!
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeEditController.java b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeEditController.java
index ac28cc7a9f0..07a82d213a8 100644
--- a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeEditController.java
+++ b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeEditController.java
@@ -36,12 +36,14 @@ import org.olat.core.gui.control.ControllerEventListener;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController;
+import org.olat.core.util.Util;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.condition.Condition;
 import org.olat.course.condition.ConditionEditController;
 import org.olat.course.editor.NodeEditController;
+import org.olat.course.highscore.ui.HighScoreEditController;
 import org.olat.course.nodes.MSCourseNode;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.tree.CourseEditorTreeModel;
@@ -53,12 +55,14 @@ import org.olat.course.tree.CourseEditorTreeModel;
 public class MSCourseNodeEditController extends ActivateableTabbableDefaultController implements ControllerEventListener {
 
 	public static final String PANE_TAB_CONFIGURATION = "pane.tab.configuration";
-	private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility";	
+	private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility";
+	public static final String PANE_TAB_HIGHSCORE = "pane.tab.highscore";
 	private static final String[] paneKeys = { PANE_TAB_CONFIGURATION, PANE_TAB_ACCESSIBILITY };
 
 	private MSCourseNode msNode;
 	private VelocityContainer configurationVC;
 	private MSEditFormController modConfigController;
+	private HighScoreEditController highScoreNodeConfigController;
 
 	private ConditionEditController accessibilityCondContr;
 	private TabbedPane myTabbedPane;
@@ -75,6 +79,8 @@ public class MSCourseNodeEditController extends ActivateableTabbableDefaultContr
 	 */
 	public MSCourseNodeEditController(UserRequest ureq, WindowControl wControl, MSCourseNode msNode, ICourse course, UserCourseEnvironment euce) {
 		super(ureq, wControl);
+		setTranslator(Util.createPackageTranslator(HighScoreEditController.class, getLocale(), getTranslator()));
+		
 		this.msNode = msNode;
 		
 		configurationVC = createVelocityContainer("edit");
@@ -93,6 +99,9 @@ public class MSCourseNodeEditController extends ActivateableTabbableDefaultContr
 		listenTo(modConfigController);
 		configurationVC.put("mseditform", modConfigController.getInitialComponent());
 		
+		highScoreNodeConfigController = new HighScoreEditController(ureq, wControl, msNode, euce);
+		listenTo(highScoreNodeConfigController);
+		
 		// if there is already user data available, make for read only
 		//TODO:chg:a concurrency issues?
 		hasLogEntries = auditManager.hasUserNodeLogs(msNode);
@@ -146,8 +155,20 @@ public class MSCourseNodeEditController extends ActivateableTabbableDefaultContr
 				modConfigController.updateModuleConfiguration(msNode.getModuleConfiguration());
 				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
 			}
+			updateHighscoreTab();
+			
+		} else if (source == highScoreNodeConfigController){
+			if (event == Event.DONE_EVENT) {
+				highScoreNodeConfigController.updateModuleConfiguration(msNode.getModuleConfiguration());
+				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+			}
 		}
 	}
+	
+	private void updateHighscoreTab() {
+		Boolean sf = msNode.getModuleConfiguration().getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD,false);
+		myTabbedPane.setEnabled(4, sf);
+	}
 
 	/**
 	 * @see org.olat.core.gui.control.generic.tabbable.TabbableDefaultController#addTabs(org.olat.core.gui.components.TabbedPane)
@@ -156,6 +177,9 @@ public class MSCourseNodeEditController extends ActivateableTabbableDefaultContr
 		myTabbedPane = tabbedPane;
 		tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), accessibilityCondContr.getWrappedDefaultAccessConditionVC(translate("condition.accessibility.title")));
 		tabbedPane.addTab(translate(PANE_TAB_CONFIGURATION), configurationVC);
+		tabbedPane.addTab(translate(PANE_TAB_HIGHSCORE) , highScoreNodeConfigController.getInitialComponent());
+		updateHighscoreTab();
+
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java
index 48ba90a8ada..6a78ff32be4 100644
--- a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java
+++ b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java
@@ -37,6 +37,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.auditing.UserNodeAuditManager;
+import org.olat.course.highscore.ui.HighScoreRunController;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.MSCourseNode;
 import org.olat.course.nodes.ObjectivesHelper;
@@ -74,7 +75,15 @@ public class MSCourseNodeRunController extends DefaultController {
 		Translator trans = Util.createPackageTranslator(MSCourseNodeRunController.class, ureq.getLocale(), fallbackTrans);
 		
 		myContent = new VelocityContainer("olatmsrun", VELOCITY_ROOT + "/run.html", trans, this);
-		
+
+		if (msCourseNode.getModuleConfiguration().getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD,false)){
+			HighScoreRunController highScoreCtr = new HighScoreRunController(ureq, wControl, userCourseEnv, msCourseNode);
+			if (highScoreCtr.isViewHighscore()) {
+				Component compi = highScoreCtr.getInitialComponent();
+				myContent.put("highScore", compi);							
+			}
+		}
+				
 		ModuleConfiguration config = msCourseNode.getModuleConfiguration();
 		myContent.contextPut("displayNodeInfo", Boolean.valueOf(displayNodeInfo));
 		if (displayNodeInfo) {
diff --git a/src/main/java/org/olat/course/nodes/ms/_content/run.html b/src/main/java/org/olat/course/nodes/ms/_content/run.html
index 20bab52534a..2f4139e7741 100644
--- a/src/main/java/org/olat/course/nodes/ms/_content/run.html
+++ b/src/main/java/org/olat/course/nodes/ms/_content/run.html
@@ -99,6 +99,9 @@
 	</script>
 #end
 
+#if ($r.available("highScore"))
+	$r.render("highScore")
+#end
 
 #if($log)
 <div class="o_box">
-- 
GitLab