package controllers; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedList; import java.util.List; import models.Answer; import models.Comment; import models.Notification; import models.Question; import models.Tag; import models.TimeTracker; import models.User; import models.database.IQuestionDatabase; import models.helpers.Tools; import notifiers.Mails; import play.cache.Cache; import play.data.validation.Required; import play.i18n.Lang; import play.libs.Codec; import play.libs.Images; /** * The Application controller controls all the views and none of the actions * associated with the views (see {@link CUser}, {@link CCQuestion} and * {@link CAnswer} for these). */ public class Application extends BaseController { private static final int entriesPerPage = 15; public static final TimeTracker timeTracker = new TimeTracker(); /** * Leads to the index page at a given page of {@link Question}'s. * * @param index * the number of the page of {@link Question}'s. */ public static void index(int index) { List<Question> questions = (List<Question>) Cache .get("index.questions"); if (questions == null) { questions = Database.questions().all(); Collections.sort(questions, new Comparator<Question>() { public int compare(Question q1, Question q2) { return q2.timestamp().compareTo(q1.timestamp()); } }); Cache.set("index.questions", questions, "10mn"); } int maxIndex = Tools.determineMaximumIndex(questions, entriesPerPage); questions = Tools.paginate(questions, entriesPerPage, index); render(questions, index, maxIndex); } /** * Leads to the detailed view of a {@link Question}. * * @param id * the id of the {@link Question}. */ public static void question(int id) { Question question = Database.questions().get(id); if (question == null) { render(); } else { List<Question> similarQuestions = (List<Question>) Cache .get("question." + id + ".similar"); if (similarQuestions == null) { similarQuestions = Database.questions().findSimilar(question); if (similarQuestions.size() > 5) { similarQuestions = similarQuestions.subList(0, 5); } // the Cache chokes on sublists! similarQuestions = new ArrayList(similarQuestions); Cache.set("question." + id + ".similar", similarQuestions, "10mn"); } List<Answer> answers = question.answers(); render(question, answers, similarQuestions); } } /** * Leads to the detailed view of the {@link Question} and the field to * submit a comment. * * @param id * the id of the {@link Question}. */ public static void commentQuestion(int id) { Question question = Database.questions().get(id); render(question); } /** * Leads to the detailed view of the {@link Answer} and the field to submit * a comment. * * @param id * the id of the {@link Answer} */ public static void commentAnswer(int questionId, int answerId) { Question question = Database.questions().get(questionId); Answer answer = getAnswer(questionId, answerId); render(answer, question); } /** * Prompts the user to confirm the deletion of the {@link Question}. * * @param id * the id of the {@link Question}. */ public static void confirmDeleteQuestion(int id) { Question question = Database.questions().get(id); render(question); } /** * Prompts the user to mark this {@link Question} as Spam. * * @param id * the id of the {@link Question} */ public static void confirmMarkSpamQuestion(int id) { Question question = Database.questions().get(id); render(question); } /** * Prompts the user to mark this {@link Answer} as Spam. * * @param questionId * the id of the {@link Question} this {@link Answer} belongs to * @param answerId * the id of the {@link Answer} */ public static void confirmMarkSpamAnswer(int questionId, int answerId) { Question question = Database.questions().get(questionId); Answer answer = getAnswer(questionId, answerId); render(question, answer); } /** * Prompts the user to mark {@link Comment} as Spam. * * @param questionId * the id of the {@link Question} this {@link Answer} belongs to * @param answerId * the id of the {@link Answer} this {@link Comment} belongs to * @param commentId * the id of the {@link Comment} */ public static void confirmMarkSpamAnswerComment(int questionId, int answerId, int commentId) { Question question = Database.questions().get(questionId); Answer answer = question.getAnswer(answerId); Comment comment = answer.getComment(commentId); render(question, answer, comment); } /** * Prompts the user to mark {@link Comment} as Spam. * * @param questionId * the id of the {@link Question} this {@link Answer} belongs to * @param commentId * the id of the {@link Comment} */ public static void confirmMarkSpamQuestionComment(int questionId, int commentId) { Question question = Database.questions().get(questionId); Comment comment = question.getComment(commentId); render(question, comment); } /** * Displays the "are you sure" page before deleting a user. */ public static void deleteuser() { User showUser = Session.user(); render(showUser); } /** * Displays the registration form. */ public static void register() { // random identifier for the CAPTCHA String randomID = Codec.UUID(); render(randomID); } /** * Lets the {@link User} sign up. Error-messages will be displayed, if the * information submitted by the {@link User} is wrong. * * @param username * the name the {@link User} has entered. This field is * mandatory. * @param password * the password the {@link User} chooses. * @param passwordrepeat * the repeated password. */ public static void signup(@Required String username, String password, @Required String email, String passwordrepeat, @Required String code, String randomID) { boolean isUsernameAvailable = Database.users().isAvailable( username); validation.equals(code, Cache.get("captcha." + randomID)); if (validation.hasErrors()) { flash.error("captcha.invalid"); params.flash(); register(); } if (password.equals(passwordrepeat) && isUsernameAvailable) { User user = Database.users().register(username, password, email); boolean success = Mails.welcome(user); if (success) { flash.success("secure.mail.success"); index(0); } else { user.delete(); flash.error("secure.mail.error"); params.flash(); register(); } } else { flash.keep("url"); if (!isUsernameAvailable) { flash.error("secure.usernameerror"); } if (!password.equals(passwordrepeat)) { flash.error("secure.passworderror"); } params.flash(); register(); } } /** * Leads to the view of a {@link User}'s profile. * * @param userName * the name of the {@link User} who is the owner of the profile. */ public static void showprofile(String userName) { User showUser = Database.users().get(userName); List<Tag> expertise = Database.questions().getExpertise(showUser); boolean canEdit = userCanEditProfile(showUser); render(showUser, expertise, canEdit); } /** * Renders a JSON list combining all the currently used tags starting with a * given term and the most often used words in a given (question's) content. * This list can be used for implementing client-side tag autocompletion. * * @param term * the part of the tag a user has already entered and that is * supposed to be auto-completed * @param content * the content of e.g. a question to search through for often * occurring words that might also be useful as tags */ public static void tags(String term, String content) { List<String> tags = Database.tags().suggestTagNames(term); if (content != null) { tags.addAll(Tools.extractImportantWords(content)); } renderJSON(tags); } /** * Performs a search for the entered term. The view is displayed at the * given index. Prevents a user from searching something different too soon * or anonymous users from searching at all - with the exception of the * search for a single tag which remains unrestricted. * * @param term * the term to be searched for. * @param index * the page-number which will be displayed. */ public static void search(String term, int index) { List<Question> results = (List<Question>) Cache.get("search." + term); User user = Session.user(); boolean isPureTagSearch = term.matches("^tag:\\S+$"); boolean isRepeatedSearch = results != null; if (isRepeatedSearch) { // we've already done this search lately, so we can // let the user do it with hardly any additional cost } else if (isPureTagSearch) { // we currently allow the search for a single tag for all // users all the time } else if (user == null) { flash.error("search.notloggedin"); // don't redirect to the calling page, as that might be a search // page which would lead to an infinite loop of failing index(0); } else if (!user.canSearchFor(term)) { flash.error("search.hastowait"); // don't redirect to the calling page, as that might be a search // page which would lead to an infinite loop of failing index(0); } if (results == null) { results = Database.questions().searchFor(term); Cache.set("search." + term, results, "5mn"); } int maxIndex = Tools.determineMaximumIndex(results, entriesPerPage); results = Tools.paginate(results, entriesPerPage, index); if (user != null && !isPureTagSearch && !isRepeatedSearch) { user.setLastSearch(term); } render(results, term, index, maxIndex); } /** * Displays one of four different view-parts concerned with user * notifications: * <ol start="0"> * <li>A user's notifications about watched questions so that the user can * easily access all the new answers to his/her questions. * <li>A list of all the questions the user is currently watching so that * he/she can easily unwatch questions from a centralized place. * <li>A list of questions suggested to the user because he/she might know * to answer them as well, as they do have the same tags as questions the * user has already successfully answered. * <li>For moderators only: A list of questions and answers that have been * marked by other users as possibly being spam so that the moderator can * verify these claims and also easily delete spam and block spamming users. * </ol> * * @param content * the index of what view-part to display (0 = * watch-notifications, 1 = watched questions, 2 = suggested * questions, 3 = spam reports) */ public static void notifications(int content) { User user = Session.user(); if (user != null) { List<Notification> spamNotification = new LinkedList(); List<Question> suggestedQuestions = Database.questions() .suggestQuestions(user); List<Notification> notifications = user.getNotifications(); List<Question> watchingQuestions = Database.questions() .getWatchList(user); if (user.isModerator()) { spamNotification.addAll(Database.users() .getModeratorMailbox().getAllNotifications()); // make sure not to display spam notifications twice notifications.removeAll(spamNotification); } render(notifications, watchingQuestions, suggestedQuestions, spamNotification, content); } else { Application.index(0); } } /** * Leads to the statistical overview. The statistical data is calculated via * the {@link TimeTracker}. */ public static void showStatisticalOverview() { TimeTracker t = Application.timeTracker; IQuestionDatabase questionDB = Database.questions(); int numberOfUsers = Database.users().count(); int numberOfQuestions = questionDB.count(); int numberOfAnswers = questionDB.countAllAnswers(); int numberOfHighRatedAnswers = questionDB.countHighRatedAnswers(); int numberOfBestAnswers = questionDB.countBestRatedAnswers(); float questionsPerDay = (float) numberOfQuestions / t.getDays(); float questionsPerWeek = (float) numberOfQuestions / t.getWeeks(); float questionsPerMonth = (float) numberOfQuestions / t.getMonths(); float answersPerDay = (float) numberOfAnswers / t.getDays(); float answersPerWeek = (float) numberOfAnswers / t.getWeeks(); float answersPerMonth = (float) numberOfAnswers / t.getMonths(); render(numberOfQuestions, numberOfAnswers, numberOfUsers, numberOfHighRatedAnswers, numberOfBestAnswers, questionsPerDay, questionsPerWeek, questionsPerMonth, answersPerDay, answersPerWeek, answersPerMonth); } /** * Leads to the admin page where a moderator can edit several options e.g. * clear the database. */ public static void admin() { User user = Session.user(); if (user == null || !user.isModerator()) { flash.error("secure.moderatorerror"); Application.index(0); } render(); } /** * Leads to the clearDB page. */ public static void clearDB() { User user = Session.user(); if (user == null || !user.isModerator()) { flash.error("secure.moderatorerror"); Application.index(0); } render(); } /** * Changes the language of the user interface. * * @param langId * an ISO-631-like language code (e.g. en, de, fr) */ public static void selectLanguage(@Required String langId) { if (langId != null) { Lang.change(langId); if (!Lang.get().equals(langId)) { flash.error("Unknown language %s!", langId); } } else { flash.error("Wanna silence me? Try again!"); } if (!redirectToCallingPage()) { index(0); } } /** * Leads to the edit-view of the {@link User}'s profile * * @param userName * the name of the {@link User} who owns the profile */ public static void editProfile(@Required String userName) { User showUser = Database.users().get(userName); if (!userCanEditProfile(showUser)) { showprofile(userName); } render(showUser); } /** * Confirm a {@link User}'s profile if they clicked on the right link * * @param username * of the {@link User} * @param key * for the Confirmation */ public static void confirmUser(@Required String username, String key) { User user = Database.users().get(username); boolean existsUser = Database.users().isAvailable(username); if (!existsUser && key.equals(user.getConfirmKey())) { user.confirm(); flash.success("user.confirm.success"); try { Secure.login(); } catch (Throwable e) { e.printStackTrace(); } } else { flash.error("user.confirm.error"); index(0); } } /** * Generates a random captcha-image for a new user to confirm when he/she is * registering for an account (before even a confirmation e-mail is sent). * * @param id * a random ID identify a specific CAPTCHA */ public static void captcha(String id) { Images.Captcha captcha = Images.captcha(); String code = captcha.getText("#ff8400"); Cache.set("captcha." + id, code, "3mn"); renderBinary(captcha); } }