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