diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..085a879a72ff2a5270fbcfaa20d349610e1e5a76
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+# Contributing
+
+ARSnova needs you! If you are interested in helping, please review the guidelines found in our [mobile repository][mobile-repository].
+
+[mobile-repository]: https://github.com/thm-projects/arsnova-mobile/blob/master/CONTRIBUTING.md
diff --git a/pom.xml b/pom.xml
index 5f8035d461265a5f049b1f4d48867b278f63c371..6e2c2454d0a67ac904e9e29592d5b18b049f22cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,9 +7,11 @@
 	<packaging>war</packaging>
 
 	<properties>
-		<org.springframework-version>4.0.9.RELEASE</org.springframework-version>
-		<org.springframework.security-version>3.2.5.RELEASE</org.springframework.security-version>
-		<org.springframework.integration-mail-version>4.0.6.RELEASE</org.springframework.integration-mail-version>
+		<!-- Tests currently fail with Spring Framework >= 4.1.5 -->
+		<org.springframework-version>4.1.4.RELEASE</org.springframework-version>
+		<org.springframework.security-version>3.2.6.RELEASE</org.springframework.security-version>
+		<org.springframework.integration-mail-version>4.1.2.RELEASE</org.springframework.integration-mail-version>
+		<com.fasterxml.jackson-version>2.5.1</com.fasterxml.jackson-version>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.url>https://scm.thm.de/arsnova</project.url>
 		<sonar.language>java</sonar.language>
@@ -95,7 +97,7 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-javadoc-plugin</artifactId>
-				<version>2.10.1</version>
+				<version>2.10.2</version>
 				<configuration></configuration>
 			</plugin>
 		</plugins>
@@ -186,11 +188,6 @@
 			<artifactId>spring-security-ldap</artifactId>
 			<version>${org.springframework.security-version}</version>
 		</dependency>
-		<dependency>
-			<groupId>jstl</groupId>
-			<artifactId>jstl</artifactId>
-			<version>1.2</version>
-		</dependency>
 		<dependency>
 			<groupId>cglib</groupId>
 			<artifactId>cglib</artifactId>
@@ -212,21 +209,9 @@
 			<version>1.2.17</version>
 		</dependency>
 		<dependency>
-			<groupId>couchdb4j</groupId>
+			<groupId>de.thm.couchdb4j</groupId>
 			<artifactId>couchdb4j</artifactId>
-			<version>0.3.0-i386-1</version>
-			<exclusions>
-				<!-- Exclude httpclient: Selenium loads a more current version -->
-				<exclusion>
-					<groupId>org.apache.httpcomponents</groupId>
-					<artifactId>httpclient</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-		<dependency>
-			<groupId>net.sf.ezmorph</groupId>
-			<artifactId>ezmorph</artifactId>
-			<version>1.0.6</version>
+			<version>0.4-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>javax.servlet</groupId>
@@ -248,7 +233,7 @@
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
-			<version>4.11</version>
+			<version>4.12</version>
 			<scope>test</scope>
 		</dependency>
 		<dependency>
@@ -261,7 +246,7 @@
 		<dependency>
 			<groupId>com.corundumstudio.socketio</groupId>
 			<artifactId>netty-socketio</artifactId>
-			<version>1.7.6</version>
+			<version>1.7.7</version>
 		</dependency>
 		<dependency>
 			<groupId>javax.inject</groupId>
@@ -272,7 +257,12 @@
 		<dependency>
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-databind</artifactId>
-			<version>2.4.5</version>
+			<version>${com.fasterxml.jackson-version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-core</artifactId>
+			<version>${com.fasterxml.jackson-version}</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
@@ -282,41 +272,31 @@
 		<dependency>
 			<groupId>org.aspectj</groupId>
 			<artifactId>aspectjrt</artifactId>
-			<version>1.7.4</version>
+			<version>1.8.5</version>
 		</dependency>
 		<dependency>
 			<groupId>org.aspectj</groupId>
 			<artifactId>aspectjweaver</artifactId>
-			<version>1.7.4</version>
+			<version>1.8.5</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework</groupId>
 			<artifactId>spring-aspects</artifactId>
 			<version>${org.springframework-version}</version>
 		</dependency>
-		<dependency>
-			<groupId>org.seleniumhq.selenium</groupId>
-			<artifactId>selenium-java</artifactId>
-			<version>2.42.2</version>
-			<scope>test</scope>
-		</dependency>
 		<dependency>
 			<groupId>org.mockito</groupId>
 			<artifactId>mockito-all</artifactId>
-			<version>1.9.5</version>
+			<version>1.10.19</version>
 			<scope>test</scope>
 		</dependency>
-		<dependency>
-			<groupId>org.apache.httpcomponents</groupId>
-			<artifactId>httpclient</artifactId>
-			<version>4.3.6</version>
-		</dependency>
 		<dependency>
 			<groupId>de.thm.arsnova.connector</groupId>
 			<artifactId>connector-client</artifactId>
 			<version>0.73.0</version>
 		</dependency>
 		<dependency>
+			<!-- Tests are currently not compatible with json-path-assert >= 1.0.0 -->
 			<groupId>com.jayway.jsonpath</groupId>
 			<artifactId>json-path-assert</artifactId>
 			<version>0.9.1</version>
@@ -350,7 +330,7 @@
 			<plugin>
 				<groupId>org.eclipse.jetty</groupId>
 				<artifactId>jetty-maven-plugin</artifactId>
-				<version>9.2.7.v20150116</version>
+				<version>9.2.10.v20150310</version>
 				<configuration>
 					<scanIntervalSeconds>1</scanIntervalSeconds>
 					<webApp>
@@ -389,7 +369,7 @@
 			<plugin>
 				<groupId>org.jacoco</groupId>
 				<artifactId>jacoco-maven-plugin</artifactId>
-				<version>0.7.2.201409121644</version>
+				<version>0.7.4.201502262128</version>
 				<executions>
 					<execution>
 						<id>default-prepare-agent</id>
@@ -414,7 +394,7 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-checkstyle-plugin</artifactId>
-				<version>2.13</version>
+				<version>2.14</version>
 				<configuration>
 					<configLocation>ARSnova-checkstyle-checker.xml</configLocation>
 				</configuration>
diff --git a/src/main/java/de/thm/arsnova/ImageUtils.java b/src/main/java/de/thm/arsnova/ImageUtils.java
index f291f82347dbb5384ddc03ede4da3bbdb7b418fb..093ecc831a35feb0fdf45a1e40a53a186459665b 100644
--- a/src/main/java/de/thm/arsnova/ImageUtils.java
+++ b/src/main/java/de/thm/arsnova/ImageUtils.java
@@ -17,7 +17,9 @@
  */
 package de.thm.arsnova;
 
+import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,30 +31,50 @@ import javax.imageio.ImageIO;
 import org.apache.commons.codec.binary.Base64;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.context.annotation.Configuration;
+
+import de.thm.arsnova.entities.Answer;
 
 /**
  * Util class for image operations.
  *
  * @author Daniel Vogel (daniel.vogel@mni.thm.de)
- *
+ * @author Jan Sladek (jan.sladek@mni.thm.de)
  */
+@Component("imageUtils")
 public class ImageUtils {
 
 	// Or whatever size you want to read in at a time.
 	private static final int CHUNK_SIZE = 4096;
 
-	private ImageUtils() {
-	}
+	/** Base64-Mimetype-Prefix start */
+	public static final String IMAGE_PREFIX_START = "data:image/";
+
+	/** Base64-Mimetype-Prefix middle part */
+	public static final String IMAGE_PREFIX_MIDDLE = ";base64,";
+
+	/* default value is 200 pixel in width, set the value in the configuration file */
+	private static final int THUMB_WIDTH_DEFAULT = 200;
+	/* default value is 200 pixel in height, set the value in the configuration file */
+	private static final int THUMB_HEIGHT_DEFAULT = 200;
 
 	public static final Logger LOGGER = LoggerFactory.getLogger(ImageUtils.class);
 
+	@Value("${imageupload.thumbnail.width}")
+	private int thumbWidth = THUMB_WIDTH_DEFAULT;
+
+	@Value("${imageupload.thumbnail.height}")
+	private int thumbHeight = THUMB_HEIGHT_DEFAULT;
+
 	/**
 	 * Converts an image to an Base64 String.
 	 *
 	 * @param  imageUrl The image url as a {@link String}
 	 * @return The Base64 {@link String} of the image on success, otherwise <code>null</code>.
 	 */
-	public static String encodeImageToString(final String imageUrl) {
+	public String encodeImageToString(final String imageUrl) {
 
 		final String[] urlParts = imageUrl.split("\\.");
 		final StringBuilder result   = new StringBuilder();
@@ -73,13 +95,149 @@ public class ImageUtils {
 		return null;
 	}
 
+	/**
+	 * Checks if a {@link String} starts with the Base64-Mimetype prefix.
+	 *
+	 * @param maybeImage The Image as a base64 encoded {@link String}
+	 * @return true if the string is a potentially a base 64 encoded image.
+	 */
+	public boolean isBase64EncodedImage(String maybeImage) {
+		return extractImageInfo(maybeImage) != null;
+	}
+
+	/**
+	 * Extracts information(extension and the raw-image) from a {@link String}
+	 * representing a base64-encoded image and returns it as a two-dimensional
+	 * {@link String}-array, or null if the passed in {@link String} is not a
+	 * valid base64-encoded image.
+	 *
+	 * @param maybeImage
+	 *            a {@link String} representing a base64-encoded image.
+	 * @return two-dimensional {@link String}-array containing the information
+	 *         "extension" and the "raw-image-{@link String}"
+	 */
+	public String[] extractImageInfo(final String maybeImage) {
+		if (maybeImage == null) {
+			return null;
+		} else if (maybeImage.isEmpty()) {
+			return null;
+		} else {
+			if (!maybeImage.startsWith(IMAGE_PREFIX_START)) {
+				return null;
+			} else {
+				final int extensionStartIndex = IMAGE_PREFIX_START.length();
+				final int extensionEndIndex = maybeImage.indexOf(IMAGE_PREFIX_MIDDLE);
+				if (extensionEndIndex < 0) {
+					return null;
+				}
+
+				final String imageWithoutPrefix = maybeImage.substring(extensionEndIndex);
+
+				if (!imageWithoutPrefix.startsWith(IMAGE_PREFIX_MIDDLE)) {
+					return null;
+				} else {
+					final String[] imageInfo = new String[2];
+					final String extension = maybeImage.substring(extensionStartIndex, extensionEndIndex);
+					final String imageString = imageWithoutPrefix.substring(IMAGE_PREFIX_MIDDLE.length());
+
+					imageInfo[0] = extension;
+					imageInfo[1] = imageString;
+
+					return imageInfo;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Rescales an image represented by a Base64-encoded {@link String}
+	 *
+	 * @param originalImageString
+	 *            The original image represented by a Base64-encoded
+	 *            {@link String}
+	 * @param width
+	 *            the new width
+	 * @param height
+	 *            the new height
+	 * @return The rescaled Image as Base64-encoded {@link String}, returns null
+	 *         if the passed-on image isn't in a valid format (a Base64-Image).
+	 */
+	public String createCover(String originalImageString, final int width, final int height) {
+		if (!isBase64EncodedImage(originalImageString)) {
+			return null;
+		} else {
+			final String[] imgInfo = extractImageInfo(originalImageString);
+
+			// imgInfo isn't null and contains two fields, this is checked by "isBase64EncodedImage"-Method
+			final String extension = imgInfo[0];
+			final String base64String = imgInfo[1];
+
+			byte[] imageData = Base64.decodeBase64(base64String);
+			try {
+				BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));
+				BufferedImage newImage = new BufferedImage(width, height, originalImage.getType());
+				Graphics2D g = newImage.createGraphics();
+
+				final double ratio = ((double) originalImage.getWidth()) / ((double) originalImage.getHeight());
+
+				int x = 0, y = 0, w = width, h = height;
+				if (originalImage.getWidth() > originalImage.getHeight()) {
+					final int newWidth = (int) Math.round((float) height * ratio);
+					x = -(newWidth - width) >> 1;
+					w = newWidth;
+				} else if (originalImage.getWidth() < originalImage.getHeight()) {
+					final int newHeight = (int) Math.round((float) width / ratio);
+					y = -(newHeight - height) >> 1;
+					h = newHeight;
+				}
+				g.drawImage(originalImage, x, y, w, h, null);
+				g.dispose();
+
+				StringBuilder result = new StringBuilder();
+				result.append("data:image/");
+				result.append(extension);
+				result.append(";base64,");
+
+				ByteArrayOutputStream output = new ByteArrayOutputStream();
+				ImageIO.write(newImage, extension, output);
+
+				output.flush();
+				output.close();
+
+				result.append(Base64.encodeBase64String(output.toByteArray()));
+
+				return result.toString();
+			} catch (IOException e) {
+				LOGGER.error(e.getLocalizedMessage());
+				return null;
+			}
+		}
+	}
+
+	/**
+	 * Generates a thumbnail image in the {@link Answer}, if none is present.
+	 *
+	 * @param answer
+	 *            the {@link Answer} where the thumbnail should be added.
+	 * @return true if the thumbnail image didn't exist before calling this
+	 *         method, false otherwise
+	 */
+	public boolean generateThumbnailImage(Answer answer) {
+		if (!isBase64EncodedImage(answer.getAnswerThumbnailImage())) {
+			final String thumbImage = createCover(answer.getAnswerImage(), thumbWidth, thumbHeight);
+			answer.setAnswerThumbnailImage(thumbImage);
+			return true;
+		}
+		return false;
+	}
+
 	/**
 	 * Gets the bytestream of an image url.
 	 * s
 	 * @param  imageUrl The image url as a {@link String}
 	 * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>.
 	 */
-	public static byte[] convertImageToByteArray(final String imageUrl, final String extension) {
+	public byte[] convertImageToByteArray(final String imageUrl, final String extension) {
 
 		try {
 			final URL url = new URL(imageUrl);
@@ -107,7 +265,7 @@ public class ImageUtils {
 	 * @param  imageUrl The image url as a {@link String}
 	 * @return The <code>byte[]</code> of the image on success, otherwise <code>null</code>.
 	 */
-	public static byte[] convertFileToByteArray(final String imageUrl) {
+	public byte[] convertFileToByteArray(final String imageUrl) {
 
 
 		try {
diff --git a/src/main/java/de/thm/arsnova/config/ExtraConfig.java b/src/main/java/de/thm/arsnova/config/ExtraConfig.java
index 45b375c2351a2774c7c8233fa17fb66d19613020..f971a219a76f02030d1b4aa025b1d22dd222bd8a 100644
--- a/src/main/java/de/thm/arsnova/config/ExtraConfig.java
+++ b/src/main/java/de/thm/arsnova/config/ExtraConfig.java
@@ -35,6 +35,7 @@ import de.thm.arsnova.connector.client.ConnectorClient;
 import de.thm.arsnova.connector.client.ConnectorClientImpl;
 import de.thm.arsnova.socket.ARSnovaSocket;
 import de.thm.arsnova.socket.ARSnovaSocketIOServer;
+import de.thm.arsnova.ImageUtils;
 
 @Configuration
 @EnableCaching
@@ -108,4 +109,9 @@ public class ExtraConfig {
 	public CacheManager cacheManager() {
 		return new ConcurrentMapCacheManager();
 	}
+
+	@Bean
+	public ImageUtils imageUtils() {
+		return new ImageUtils();
+	}
 }
diff --git a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
index cf15ea4701086bac55eca615846c7deaf94ab186..c08bfe5602e01f8b56215596162012cd73e0f2b2 100644
--- a/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
+++ b/src/main/java/de/thm/arsnova/controller/LecturerQuestionController.java
@@ -339,6 +339,16 @@ public class LecturerQuestionController extends AbstractController {
 		return questionService.updateAnswer(answer);
 	}
 
+	@RequestMapping(value = "/{questionId}/answer/{answerId}/image", method = RequestMethod.GET)
+	public String getImage(
+			@PathVariable final String questionId,
+			@PathVariable final String answerId,
+			final HttpServletResponse response
+			) {
+
+		return questionService.getImage(questionId, answerId);
+	}
+
 	@RequestMapping(value = "/{questionId}/answer/{answerId}", method = RequestMethod.DELETE)
 	public void deleteAnswer(
 			@PathVariable final String questionId,
diff --git a/src/main/java/de/thm/arsnova/controller/SessionController.java b/src/main/java/de/thm/arsnova/controller/SessionController.java
index 893bd56b76bd5b9d23120d7574a3822848614b56..c1e5b78fd3666ec941aaba0f88583fd5d77ae495 100644
--- a/src/main/java/de/thm/arsnova/controller/SessionController.java
+++ b/src/main/java/de/thm/arsnova/controller/SessionController.java
@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import de.thm.arsnova.connector.model.Course;
 import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.SessionFeature;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.transport.ImportExportSession;
 import de.thm.arsnova.entities.transport.LearningProgressValues;
@@ -248,6 +249,23 @@ public class SessionController extends AbstractController {
 		return sessionService.getMyLearningProgress(sessionkey, progressType);
 	}
 
+	@RequestMapping(value = "/{sessionkey}/features", method = RequestMethod.GET)
+	public SessionFeature sessionFeatures(
+			@PathVariable final String sessionkey,
+			final HttpServletResponse response
+			) {
+		return sessionService.getSessionFeatures(sessionkey);
+	}
+
+	@RequestMapping(value = "/{sessionkey}/features", method = RequestMethod.PATCH)
+	public SessionFeature changeSessionFeatures(
+			@PathVariable final String sessionkey,
+			@RequestBody final SessionFeature features,
+			final HttpServletResponse response
+			) {
+		return sessionService.changeSessionFeatures(sessionkey, features);
+	}
+
 	/* internal redirections */
 
 	@RequestMapping(value = "/{sessionKey}/lecturerquestion")
diff --git a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java
index c1476c5434d278ba0be1574dd635386cc3caff99..7877dc37b33615e3189041c239f4393c3d881829 100644
--- a/src/main/java/de/thm/arsnova/dao/CouchDBDao.java
+++ b/src/main/java/de/thm/arsnova/dao/CouchDBDao.java
@@ -54,6 +54,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 import com.fourspaces.couchdb.Database;
 import com.fourspaces.couchdb.Document;
+import com.fourspaces.couchdb.Results;
+import com.fourspaces.couchdb.RowResult;
 import com.fourspaces.couchdb.View;
 import com.fourspaces.couchdb.ViewResults;
 
@@ -125,17 +127,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 		view.setStartKeyArray(user.getUsername());
 		view.setEndKeyArray(user.getUsername(), "{}");
 
-		final ViewResults sessions = getDatabase().view(view);
+		final Results<Session> results = getDatabase().queryView(view, Session.class);
 
 		final List<Session> result = new ArrayList<Session>();
-		for (final Document d : sessions.getResults()) {
-			final Session session = (Session) JSONObject.toBean(
-					d.getJSONObject().getJSONObject("value"),
-					Session.class
-					);
-			session.setCreator(d.getJSONObject().getJSONArray("key").getString(0));
-			session.setName(d.getJSONObject().getJSONArray("key").getString(1));
-			session.set_id(d.getId());
+		for (final RowResult<Session> row : results.getRows()) {
+			final Session session = row.getValue();
+			session.setCreator(row.getKey().getString(0));
+			session.setName(row.getKey().getString(1));
+			session.set_id(row.getId());
 			result.add(session);
 		}
 		return result;
@@ -448,6 +447,7 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 		sessionDocument.put("ppFaculty", session.getPpFaculty());
 		sessionDocument.put("ppLevel", session.getPpLevel());
 		sessionDocument.put("sessionType", session.getSessionType());
+		sessionDocument.put("features", JSONObject.fromObject(session.getFeatures()));
 		try {
 			database.saveDocument(sessionDocument);
 		} catch (final IOException e) {
@@ -549,6 +549,8 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 		q.put("gridType", question.getGridType());
 		q.put("scaleFactor", question.getScaleFactor());
 		q.put("gridScaleFactor", question.getGridScaleFactor());
+		q.put("imageQuestion", question.isImageQuestion());
+		q.put("textAnswerEnabled", question.isTextAnswerEnabled());
 
 		return q;
 	}
@@ -597,6 +599,7 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 			q.put("gridType", question.getGridType());
 			q.put("scaleFactor", question.getScaleFactor());
 			q.put("gridScaleFactor", question.getGridScaleFactor());
+			q.put("imageQuestion", question.isImageQuestion());
 
 			database.saveDocument(q);
 			question.set_rev(q.getRev());
@@ -1084,11 +1087,14 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 		try {
 			final View statsView = new View("statistics/statistics");
 			final View creatorView = new View("statistics/unique_session_creators");
+			final View studentUserView = new View("statistics/active_student_users");
 			statsView.setGroup(true);
 			creatorView.setGroup(true);
+			studentUserView.setGroup(true);
 
 			final ViewResults statsResults = getDatabase().view(statsView);
 			final ViewResults creatorResults = getDatabase().view(creatorView);
+			final ViewResults studentUserResults = getDatabase().view(studentUserView);
 
 			if (!isEmptyResults(statsResults)) {
 				final JSONArray rows = statsResults.getJSONArray("rows");
@@ -1129,6 +1135,15 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 				}
 				stats.setCreators(creators.size());
 			}
+			if (!isEmptyResults(studentUserResults)) {
+				final JSONArray rows = studentUserResults.getJSONArray("rows");
+				Set<String> students = new HashSet<String>();
+				for (int i = 0; i < rows.size(); i++) {
+					final JSONObject row = rows.getJSONObject(i);
+					students.add(row.getString("key"));
+				}
+				stats.setActiveStudents(students.size());
+			}
 			return stats;
 		} catch (final Exception e) {
 			LOGGER.error("Error while retrieving session count", e);
@@ -1246,6 +1261,8 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 		a.put("user", user.getUsername());
 		a.put("piRound", answer.getPiRound());
 		a.put("abstention", answer.isAbstention());
+		a.put("answerImage", answer.getAnswerImage());
+		a.put("answerThumbnailImage", answer.getAnswerThumbnailImage());
 		AnswerQueueElement answerQueueElement = new AnswerQueueElement(session, question, answer, user);
 		this.answerQueue.offer(new AbstractMap.SimpleEntry<Document, AnswerQueueElement>(a, answerQueueElement));
 		return answer;
@@ -1294,6 +1311,9 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 			a.put("timestamp", answer.getTimestamp());
 			a.put("abstention", answer.isAbstention());
 			a.put("questionValue", answer.getQuestionValue());
+			a.put("answerImage", answer.getAnswerImage());
+
+			a.put("answerThumbnailImage", answer.getAnswerThumbnailImage());
 			database.saveDocument(a);
 			answer.set_rev(a.getRev());
 			return answer;
@@ -1372,6 +1392,7 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
 			s.put("shortName", session.getShortName());
 			s.put("active", session.isActive());
 			s.put("learningProgressType", session.getLearningProgressType());
+			s.put("features", JSONObject.fromObject(session.getFeatures()));
 			database.saveDocument(s);
 			session.set_rev(s.getRev());
 
diff --git a/src/main/java/de/thm/arsnova/dao/NovaView.java b/src/main/java/de/thm/arsnova/dao/NovaView.java
index 1248b17196bcf46374102d0b35ea1269ad580ba1..8f320a0fcf0a0f054f5b726c1ca5e7f112413ad0 100644
--- a/src/main/java/de/thm/arsnova/dao/NovaView.java
+++ b/src/main/java/de/thm/arsnova/dao/NovaView.java
@@ -17,168 +17,14 @@
  */
 package de.thm.arsnova.dao;
 
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.lang.StringUtils;
-
 import com.fourspaces.couchdb.View;
 
+/**
+ * Stub class that needs to be removed once migration to our CouchDB4J fork is complete
+ */
 public class NovaView extends View {
 
-	public enum StaleMode {
-		NONE, OK, UPDATE_AFTER
-	}
-
-	protected String keys;
-
-	protected StaleMode stale = StaleMode.NONE;
-
-	protected boolean includeDocs = false;
-
-	public boolean isIncludeDocs() {
-		return includeDocs;
-	}
-
-	public void setIncludeDocs(boolean includeDocs) {
-		this.includeDocs = includeDocs;
-	}
-
-	public NovaView(final String fullname) {
+	public NovaView(String fullname) {
 		super(fullname);
 	}
-
-	@Override
-	public void setStartKey(final String key) {
-		startKey = quote(key);
-	}
-
-	public void setStartKeyArray(final String key) {
-		if (isNumber(key)) {
-			startKey = encode("[" + key + "]");
-		} else {
-			startKey = encode("[\"" + key + "\"]");
-		}
-	}
-
-	public void setStartKeyArray(final String... keys) {
-		this.setStartKey(keys);
-	}
-
-	@Override
-	public void setEndKey(final String key) {
-		endKey = quote(key);
-	}
-
-	public void setEndKeyArray(final String key) {
-		if (isNumber(key)) {
-			endKey = encode("[" + key + "]");
-		} else {
-			endKey = encode("[\"" + key + "\"]");
-		}
-	}
-
-	public void setEndKeyArray(final String... keys) {
-		this.setEndKey(keys);
-	}
-
-	public void setStartKey(final String... keys) {
-		startKey = toJsonArray(keys);
-	}
-
-	public void setEndKey(final String... keys) {
-		endKey = toJsonArray(keys);
-	}
-
-	@Override
-	public void setKey(final String key) {
-		this.key = quote(key);
-	}
-
-	public void setKey(final String... keys) {
-		key = toJsonArray(keys);
-	}
-
-	public void setKeys(List<String> keys) {
-		this.keys = toJsonArray(keys.toArray(new String[keys.size()]));
-	}
-
-	public void setStale(StaleMode stale) {
-		this.stale = stale;
-	}
-
-	@Override
-	public String getQueryString() {
-		final String tempQuery = super.getQueryString();
-		final StringBuilder query = new StringBuilder();
-		if (tempQuery != null) {
-			query.append(tempQuery);
-		}
-		if (keys != null) {
-			if (query.length() > 0) {
-				query.append("&");
-			}
-			query.append("keys=" + keys);
-		}
-		if (stale != null && stale != StaleMode.NONE) {
-			if (query.length() > 0) {
-				query.append("&");
-			}
-			if (stale == StaleMode.OK) {
-				query.append("stale=ok");
-			} else if (stale == StaleMode.UPDATE_AFTER) {
-				query.append("stale=update_after");
-			}
-		}
-		if (includeDocs != false) {
-			if (query.length() > 0) {
-				query.append("&");
-			}
-			query.append("include_docs=true");
-		}
-
-		if (query.length() == 0) {
-			return null;
-		}
-		return query.toString();
-	}
-
-	private String toJsonArray(final String[] strs) {
-		final List<String> strings = new ArrayList<String>();
-		for (final String string : strs) {
-			if (isNumber(string) || isPlaceholder(string) || isArray(string)) {
-				strings.add(string);
-			} else {
-				strings.add("\"" + string + "\"");
-			}
-		}
-		return encode("[" + StringUtils.join(strings, ",") + "]");
-	}
-
-	private String quote(final String string) {
-		return encode("\"" + string + "\"");
-	}
-
-	private boolean isNumber(final String string) {
-		return string.matches("^[0-9]+$");
-	}
-
-	private boolean isPlaceholder(final String string) {
-		return string.equals("{}");
-	}
-
-	private boolean isArray(final String string) {
-		return string.startsWith("[") && string.endsWith("]");
-	}
-
-	private String encode(final String string) {
-		try {
-			return URLEncoder.encode(string, "UTF-8");
-		} catch (final UnsupportedEncodingException e) {
-			// Since we're using 'UTF-8', this should Exception should never occur.
-			return "";
-		}
-	}
 }
diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java
index 55fa104fe89f8aa58a6ab85a2ada0fb82ae01b47..dcc3bc317f960ce65599e2f5b00a0028f1de58e8 100644
--- a/src/main/java/de/thm/arsnova/entities/Answer.java
+++ b/src/main/java/de/thm/arsnova/entities/Answer.java
@@ -36,6 +36,9 @@ public class Answer {
 	private int answerCount = 1;
 	private boolean abstention;
 	private int abstentionCount;
+	@JsonIgnore
+	private String answerImage;
+	private String answerThumbnailImage;
 
 	public Answer() {
 		this.type = "skill_question_answer";
@@ -113,6 +116,23 @@ public class Answer {
 		return user;
 	}
 
+	@JsonIgnore
+	public String getAnswerImage() {
+		return answerImage;
+	}
+
+	public void setAnswerImage(String answerImage) {
+		this.answerImage = answerImage;
+	}
+
+	public String getAnswerThumbnailImage() {
+		return answerThumbnailImage;
+	}
+
+	public void setAnswerThumbnailImage(String answerThumbnailImage) {
+		this.answerThumbnailImage = answerThumbnailImage;
+	}
+
 	public final void setUser(final String user) {
 		this.user = user;
 	}
diff --git a/src/main/java/de/thm/arsnova/entities/Question.java b/src/main/java/de/thm/arsnova/entities/Question.java
index 9fc2c4502671659b7390151ebe6d2ac0093f93d0..4d4516aa4226818b21532cee7d283306d5f66358 100644
--- a/src/main/java/de/thm/arsnova/entities/Question.java
+++ b/src/main/java/de/thm/arsnova/entities/Question.java
@@ -69,6 +69,8 @@ public class Question {
 	private String gridType;
 	private String scaleFactor;
 	private String gridScaleFactor;
+	private boolean imageQuestion;
+	private boolean textAnswerEnabled;
 
 	public final String getType() {
 		return type;
@@ -186,6 +188,14 @@ public class Question {
 		return duration;
 	}
 
+	public final boolean isImageQuestion() {
+		return imageQuestion;
+	}
+
+	public void setImageQuestion(boolean imageQuestion) {
+		this.imageQuestion = imageQuestion;
+	}
+
 	public final void setDuration(final int duration) {
 		this.duration = duration;
 	}
@@ -438,6 +448,14 @@ public class Question {
 		return this.gridScaleFactor;
 	}
 
+	public boolean isTextAnswerEnabled() {
+		return this.textAnswerEnabled;
+	}
+
+	public void setTextAnswerEnabled(boolean textAnswerEnabled) {
+		this.textAnswerEnabled = textAnswerEnabled;
+	}
+
 	@Override
 	public final String toString() {
 		return "Question type '" + type + "': " + subject + ";\n" + text + possibleAnswers;
@@ -449,7 +467,6 @@ public class Question {
 		final int prime = 31;
 		int result = 1;
 		result = prime * result + ((_id == null) ? 0 : _id.hashCode());
-		result = prime * result + ((_rev == null) ? 0 : _rev.hashCode());
 		return result;
 	}
 
@@ -469,13 +486,6 @@ public class Question {
 		} else if (!_id.equals(other._id)) {
 			return false;
 		}
-		if (_rev == null) {
-			if (other._rev != null) {
-				return false;
-			}
-		} else if (!_rev.equals(other._rev)) {
-			return false;
-		}
 		return true;
 	}
 
diff --git a/src/main/java/de/thm/arsnova/entities/Session.java b/src/main/java/de/thm/arsnova/entities/Session.java
index 6a4f17f91244bf6cff7310dd943f4d20c0b092d5..80311ff8a5e60719a181e0c06e807b2eaae92cf9 100644
--- a/src/main/java/de/thm/arsnova/entities/Session.java
+++ b/src/main/java/de/thm/arsnova/entities/Session.java
@@ -38,6 +38,7 @@ public class Session implements Serializable {
 	private List<String> _conflicts;
 	private long creationTime;
 	private String learningProgressType = "questions";
+	private SessionFeature features;
 
 	private String ppAuthorName;
 	private String ppAuthorMail;
@@ -71,6 +72,7 @@ public class Session implements Serializable {
 		copy.courseId = original.courseId;
 		copy.creationTime = original.creationTime;
 		copy.learningProgressType = original.learningProgressType;
+		copy.features = new SessionFeature(original.features);
 		// public pool
 		copy.ppAuthorName = original.ppAuthorName;
 		copy.ppAuthorMail = original.ppAuthorMail;
@@ -209,6 +211,14 @@ public class Session implements Serializable {
 		this.learningProgressType = learningProgressType;
 	}
 
+	public SessionFeature getFeatures() {
+		return features;
+	}
+
+	public void setFeatures(SessionFeature features) {
+		this.features = features;
+	}
+
 	public String getPpAuthorName() {
 		return ppAuthorName;
 	}
diff --git a/src/main/java/de/thm/arsnova/entities/SessionFeature.java b/src/main/java/de/thm/arsnova/entities/SessionFeature.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cbaff5162ac993fc2b9b4f9ad039af63c466daa
--- /dev/null
+++ b/src/main/java/de/thm/arsnova/entities/SessionFeature.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of ARSnova Backend.
+ * Copyright (C) 2012-2015 The ARSnova Team
+ *
+ * ARSnova Backend is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ARSnova Backend is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package de.thm.arsnova.entities;
+
+public class SessionFeature {
+
+	private boolean jitt;
+	private boolean feedback;
+	private boolean interposed;
+	private boolean pi;
+	private boolean learningProgress;
+
+	public SessionFeature(SessionFeature features) {
+		this();
+		if (features != null) {
+			this.jitt = features.jitt;
+			this.feedback = features.feedback;
+			this.interposed = features.interposed;
+			this.pi = features.pi;
+			this.learningProgress = features.learningProgress;
+		}
+	}
+
+	public SessionFeature() {}
+
+	public boolean isJitt() {
+		return jitt;
+	}
+
+	public void setJitt(boolean jitt) {
+		this.jitt = jitt;
+	}
+
+	public boolean isFeedback() {
+		return feedback;
+	}
+
+	public void setFeedback(boolean feedback) {
+		this.feedback = feedback;
+	}
+
+	public boolean isInterposed() {
+		return interposed;
+	}
+
+	public void setInterposed(boolean interposed) {
+		this.interposed = interposed;
+	}
+
+	public boolean isPi() {
+		return pi;
+	}
+
+	public void setPi(boolean pi) {
+		this.pi = pi;
+	}
+
+	public boolean isLearningProgress() {
+		return learningProgress;
+	}
+
+	public void setLearningProgress(boolean learningProgress) {
+		this.learningProgress = learningProgress;
+	}
+
+}
diff --git a/src/main/java/de/thm/arsnova/entities/Statistics.java b/src/main/java/de/thm/arsnova/entities/Statistics.java
index 6bfa7f6ba40b13fac561e38d0b4ff11e58303a92..1caa6a18cf189a514030c8cc079b509b8776b744 100644
--- a/src/main/java/de/thm/arsnova/entities/Statistics.java
+++ b/src/main/java/de/thm/arsnova/entities/Statistics.java
@@ -26,6 +26,7 @@ public class Statistics {
 	private int closedSessions;
 	private int creators;
 	private int activeUsers;
+	private int activeStudents;
 	private int loggedinUsers;
 	private int interposedQuestions;
 	private int conceptQuestions;
@@ -118,6 +119,14 @@ public class Statistics {
 		this.conceptQuestions = conceptQuestions;
 	}
 
+	public int getActiveStudents() {
+		return activeStudents;
+	}
+
+	public void setActiveStudents(int activeStudents) {
+		this.activeStudents = activeStudents;
+	}
+
 	@Override
 	public int hashCode() {
 		return (this.getClass().getName()
diff --git a/src/main/java/de/thm/arsnova/entities/transport/Answer.java b/src/main/java/de/thm/arsnova/entities/transport/Answer.java
index b12dde79236ae21993546d4d7d9fc41e7302bf79..7bc7e8b43048892ab8ed256c5b221bd8efa6c661 100644
--- a/src/main/java/de/thm/arsnova/entities/transport/Answer.java
+++ b/src/main/java/de/thm/arsnova/entities/transport/Answer.java
@@ -31,6 +31,8 @@ public class Answer {
 
 	private String answerText;
 
+	private String answerImage;
+
 	private boolean abstention;
 
 	public String getAnswerText() {
@@ -71,6 +73,7 @@ public class Answer {
 		theAnswer.setAbstention(this.isAbstention());
 		// calculate learning progress value after all properties are set
 		theAnswer.setQuestionValue(question.calculateValue(theAnswer));
+		theAnswer.setAnswerImage(this.getAnswerImage());
 
 		if ("freetext".equals(question.getQuestionType())) {
 			theAnswer.setPiRound(0);
@@ -80,4 +83,12 @@ public class Answer {
 
 		return theAnswer;
 	}
+
+	public String getAnswerImage() {
+		return answerImage;
+	}
+
+	public void setAnswerImage(String answerImage) {
+		this.answerImage = answerImage;
+	}
 }
diff --git a/src/main/java/de/thm/arsnova/services/IQuestionService.java b/src/main/java/de/thm/arsnova/services/IQuestionService.java
index 8b440166ac43494b358b4073d411b61f9343c8bc..0581dfb6011fcde32d3972c4cdc102bf420b90e4 100644
--- a/src/main/java/de/thm/arsnova/services/IQuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/IQuestionService.java
@@ -136,4 +136,6 @@ public interface IQuestionService {
 	void deleteAllLectureAnswers(String sessionkey);
 
 	int getAbstentionAnswerCount(String questionId);
+
+	String getImage(String questionId, String answerId);
 }
diff --git a/src/main/java/de/thm/arsnova/services/ISessionService.java b/src/main/java/de/thm/arsnova/services/ISessionService.java
index 1661b0e113879bd742bfb8186b39609e3aaaabba..64c3a866307d0919b09173a563a7d97f730e3c74 100644
--- a/src/main/java/de/thm/arsnova/services/ISessionService.java
+++ b/src/main/java/de/thm/arsnova/services/ISessionService.java
@@ -22,6 +22,7 @@ import java.util.UUID;
 
 import de.thm.arsnova.connector.model.Course;
 import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.SessionFeature;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.entities.transport.ImportExportSession;
@@ -69,4 +70,8 @@ public interface ISessionService {
 	List<SessionInfo> getMyVisitedSessionsInfo();
 
 	SessionInfo importSession(ImportExportSession session);
+
+	SessionFeature getSessionFeatures(String sessionkey);
+
+	SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features);
 }
diff --git a/src/main/java/de/thm/arsnova/services/QuestionService.java b/src/main/java/de/thm/arsnova/services/QuestionService.java
index f715355963c22cc7f7b4bc5d285908e76ea5e40f..e47a1f7fe68325349ddac2c8130f4afb31edf3cb 100644
--- a/src/main/java/de/thm/arsnova/services/QuestionService.java
+++ b/src/main/java/de/thm/arsnova/services/QuestionService.java
@@ -25,11 +25,10 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+
 import java.util.Timer;
 import java.util.TimerTask;
 
-import de.thm.arsnova.exceptions.ForbiddenException;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -59,6 +58,7 @@ import de.thm.arsnova.events.NewQuestionEvent;
 import de.thm.arsnova.events.PiRoundDelayedStartEvent;
 import de.thm.arsnova.events.PiRoundEndEvent;
 import de.thm.arsnova.exceptions.BadRequestException;
+import de.thm.arsnova.exceptions.ForbiddenException;
 import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.exceptions.UnauthorizedException;
 
@@ -71,6 +71,9 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 	@Autowired
 	private IUserService userService;
 
+	@Autowired
+	private ImageUtils imageUtils;
+
 	@Value("${upload.filesize_b}")
 	private int uploadFileSizeByte;
 
@@ -118,7 +121,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		// convert imageurl to base64 if neccessary
 		if ("grid".equals(question.getQuestionType())) {
 			if (question.getImage().startsWith("http")) {
-				final String base64ImageString = ImageUtils.encodeImageToString(question.getImage());
+				final String base64ImageString = imageUtils.encodeImageToString(question.getImage());
 				if (base64ImageString == null) {
 					throw new BadRequestException();
 				}
@@ -197,6 +200,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		this.publisher.publishEvent(event);
 	}
 
+	@Override
 	public void startNewPiRound(final String questionId, User user) {
 		if(null == user) {
 			user = userService.getCurrentUser();
@@ -231,6 +235,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		cancelDelayedPiRoundChange(questionId);
 
 		timer.schedule(new TimerTask() {
+			@Override
 			public void run() {
 				questionService.startNewPiRound(questionId, user);
 			}
@@ -246,6 +251,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		this.publisher.publishEvent(new PiRoundDelayedStartEvent(this, session, question));
 	}
 
+	@Override
 	public void cancelDelayedPiRoundChange(final String questionId) {
 		Timer timer = timerList.get(questionId);
 
@@ -406,6 +412,11 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		final List<Answer> filteredAnswers = new ArrayList<Answer>();
 		for (final Answer answer : answers) {
 			final Question question = questionIdToQuestion.get(answer.getQuestionId());
+			if (question == null) {
+				// Question is not present. Most likely it has been locked by the
+				// Session's creator. Locked Questions do not appear in this list.
+				continue;
+			}
 			if (0 == answer.getPiRound() && !"freetext".equals(question.getQuestionType())) {
 				answer.setPiRound(1);
 			}
@@ -495,7 +506,7 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		final User user = userService.getCurrentUser();
 		return update(question, user);
 	}
-	
+
 	@Override
 	@PreAuthorize("isAuthenticated()")
 	public Question update(final Question question, User user) {
@@ -535,6 +546,9 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		}
 
 		Answer theAnswer = answer.generateAnswerEntity(user, question);
+		if ("freetext".equals(question.getQuestionType())) {
+			imageUtils.generateThumbnailImage(theAnswer);
+		}
 
 		return databaseDao.saveAnswer(theAnswer, user, question, getSession(question.getSessionKeyword()));
 	}
@@ -549,6 +563,9 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 		}
 
 		final Question question = getQuestion(answer.getQuestionId());
+		if ("freetext".equals(question.getQuestionType())) {
+			imageUtils.generateThumbnailImage(realAnswer);
+		}
 		final Answer result = databaseDao.updateAnswer(realAnswer);
 		final Session session = databaseDao.getSessionFromKeyword(question.getSessionKeyword());
 		this.publisher.publishEvent(new NewAnswerEvent(this, session, result, user, question));
@@ -780,4 +797,23 @@ public class QuestionService implements IQuestionService, ApplicationEventPublis
 	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
 		this.publisher = publisher;
 	}
+
+	@Override
+	public String getImage(String questionId, String answerId) {
+		final List<Answer> answers = getAnswers(questionId);
+		Answer answer = null;
+
+		for (Answer a : answers) {
+			if (answerId.equals(a.get_id())) {
+				answer = a;
+				break;
+			}
+		}
+
+		if (answer == null) {
+			throw new NotFoundException();
+		}
+
+		return answer.getAnswerImage();
+	}
 }
diff --git a/src/main/java/de/thm/arsnova/services/SessionService.java b/src/main/java/de/thm/arsnova/services/SessionService.java
index 2f8211bd58b5f751d64de0fe4c89f7482bbf776c..78643945a78eafa69db11bf8cfcb7661419e1692 100644
--- a/src/main/java/de/thm/arsnova/services/SessionService.java
+++ b/src/main/java/de/thm/arsnova/services/SessionService.java
@@ -18,7 +18,6 @@
 package de.thm.arsnova.services;
 
 import java.io.Serializable;
-import java.util.AbstractMap.SimpleEntry;
 import java.util.Comparator;
 import java.util.List;
 import java.util.UUID;
@@ -39,6 +38,7 @@ import de.thm.arsnova.dao.IDatabaseDao;
 import de.thm.arsnova.domain.ILearningProgressFactory;
 import de.thm.arsnova.domain.LearningProgress;
 import de.thm.arsnova.entities.Session;
+import de.thm.arsnova.entities.SessionFeature;
 import de.thm.arsnova.entities.SessionInfo;
 import de.thm.arsnova.entities.User;
 import de.thm.arsnova.entities.transport.ImportExportSession;
@@ -48,6 +48,7 @@ import de.thm.arsnova.exceptions.BadRequestException;
 import de.thm.arsnova.exceptions.ForbiddenException;
 import de.thm.arsnova.exceptions.NotFoundException;
 import de.thm.arsnova.exceptions.RequestEntityTooLargeException;
+import de.thm.arsnova.exceptions.UnauthorizedException;
 
 @Service
 public class SessionService implements ISessionService, ApplicationEventPublisherAware {
@@ -100,6 +101,9 @@ public class SessionService implements ISessionService, ApplicationEventPublishe
 	@Autowired(required = false)
 	private ConnectorClient connectorClient;
 
+	@Autowired
+	private ImageUtils imageUtils;
+
 	@Value("${pp.logofilesize_b}")
 	private int uploadFileSizeByte;
 
@@ -222,7 +226,7 @@ public class SessionService implements ISessionService, ApplicationEventPublishe
 		}
 		if (session.getPpLogo() != null) {
 			if (session.getPpLogo().startsWith("http")) {
-				final String base64ImageString = ImageUtils.encodeImageToString(session.getPpLogo());
+				final String base64ImageString = imageUtils.encodeImageToString(session.getPpLogo());
 				if (base64ImageString == null) {
 					throw new BadRequestException();
 				}
@@ -341,4 +345,20 @@ public class SessionService implements ISessionService, ApplicationEventPublishe
 	public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
 		this.publisher = publisher;
 	}
+
+	@Override
+	public SessionFeature getSessionFeatures(String sessionkey) {
+		return databaseDao.getSessionFromKeyword(sessionkey).getFeatures();
+	}
+
+	@Override
+	public SessionFeature changeSessionFeatures(String sessionkey, SessionFeature features) {
+		final Session session = databaseDao.getSessionFromKeyword(sessionkey);
+		final User user = userService.getCurrentUser();
+		if (!session.isCreator(user)) {
+			throw new UnauthorizedException();
+		}
+		session.setFeatures(features);
+		return databaseDao.updateSession(session).getFeatures();
+	}
 }
diff --git a/src/main/resources/arsnova.properties.example b/src/main/resources/arsnova.properties.example
index 49b1fa17b28f271f409dc989559e6d8a0e92741c..8c43c2b650fe99caad4825007fcfb07e8c36940d 100644
--- a/src/main/resources/arsnova.properties.example
+++ b/src/main/resources/arsnova.properties.example
@@ -194,6 +194,10 @@ feedback.cleanup=10
 # maximal filesize in bytes
 upload.filesize_b=1048576
 
+# maximal thumbnail size in pixels
+imageupload.thumbnail.width=200
+imageupload.thumbnail.height=200
+
 # maximal number of answer options allowed for a skill question
 question.answer-option-limit=8
 
diff --git a/src/test/java/de/thm/arsnova/ImageUtilsTest.java b/src/test/java/de/thm/arsnova/ImageUtilsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3159b23318ce1cdae661dd94d11d6dd677f7b47
--- /dev/null
+++ b/src/test/java/de/thm/arsnova/ImageUtilsTest.java
@@ -0,0 +1,80 @@
+package de.thm.arsnova;
+
+import de.thm.arsnova.ImageUtils;
+import static de.thm.arsnova.ImageUtils.IMAGE_PREFIX_START;
+import static de.thm.arsnova.ImageUtils.IMAGE_PREFIX_MIDDLE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextConfiguration(locations = {
+		"file:src/main/webapp/WEB-INF/spring/arsnova-servlet.xml",
+		"file:src/main/webapp/WEB-INF/spring/spring-main.xml",
+		"file:src/test/resources/test-config.xml",
+		"file:src/test/resources/test-socketioconfig.xml"
+})
+@ActiveProfiles("test")
+public class ImageUtilsTest {
+
+  private ImageUtils imageUtils = new ImageUtils();
+
+  @Test
+  public void testNullIsNoValidBase64String() {
+    assertFalse("\"null\" is no valid Base64 String.", imageUtils.isBase64EncodedImage(null));
+  }
+
+  @Test
+  public void testEmptyStringIsNoValidBase64String() {
+    assertFalse("The empty String is no valid Base64 String.", imageUtils.isBase64EncodedImage(""));
+  }
+
+  @Test
+  public void testWrongStringIsNoValidBase64String() {
+    final String[] fakeStrings = new String[] {
+      "data:picture/png;base64,IMAGE-DATA",
+      "data:image/png;base63,IMAGE-DATA"
+    };
+
+    for (String fakeString : fakeStrings) {
+      assertFalse(
+        String.format("The String %s is not a valid Base64 String.", fakeString),
+        imageUtils.isBase64EncodedImage(fakeString)
+      );
+    }
+  }
+
+  @Test
+  public void testValidBase64String() {
+    final String imageString = String.format("%spng%sIMAGE-DATA", IMAGE_PREFIX_START, IMAGE_PREFIX_MIDDLE);
+    assertTrue(imageUtils.isBase64EncodedImage(imageString));
+  }
+
+  @Test
+  public void testImageInfoExtraction() {
+    final String extension = "png";
+    final String imageData = "IMAGE-DATA";
+    final String imageString = String.format("%s%s%s%s", IMAGE_PREFIX_START,
+      extension, IMAGE_PREFIX_MIDDLE, imageData);
+
+    final String[] imageInfo = imageUtils.extractImageInfo(imageString);
+    assertNotNull(imageInfo);
+
+    assertEquals("Extracted information doesn't match its specification.", 2, imageInfo.length);
+
+    assertEquals("Extracted extension is invalid.", extension, imageInfo[0]);
+    assertEquals("Extracted Base64-Image String is invalid.", imageData, imageInfo[1]);
+  }
+
+}
diff --git a/src/test/java/de/thm/arsnova/dao/NovaViewTest.java b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java
index 2459d502dbe3d122ef0de0fd134b22c4b9db5a52..2e655146890be853c784f32b0bdf2be90f618c1a 100644
--- a/src/test/java/de/thm/arsnova/dao/NovaViewTest.java
+++ b/src/test/java/de/thm/arsnova/dao/NovaViewTest.java
@@ -26,7 +26,7 @@ import java.util.Arrays;
 
 import org.junit.Test;
 
-import de.thm.arsnova.dao.NovaView.StaleMode;
+import com.fourspaces.couchdb.View.StaleMode;
 
 public class NovaViewTest {
 
diff --git a/src/test/resources/arsnova.properties.example b/src/test/resources/arsnova.properties.example
index 49b1fa17b28f271f409dc989559e6d8a0e92741c..8c43c2b650fe99caad4825007fcfb07e8c36940d 100644
--- a/src/test/resources/arsnova.properties.example
+++ b/src/test/resources/arsnova.properties.example
@@ -194,6 +194,10 @@ feedback.cleanup=10
 # maximal filesize in bytes
 upload.filesize_b=1048576
 
+# maximal thumbnail size in pixels
+imageupload.thumbnail.width=200
+imageupload.thumbnail.height=200
+
 # maximal number of answer options allowed for a skill question
 question.answer-option-limit=8