001: /*
002: * Copyright (c) JForum Team
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms,
006: * with or without modification, are permitted provided
007: * that the following conditions are met:
008: *
009: * 1) Redistributions of source code must retain the above
010: * copyright notice, this list of conditions and the
011: * following disclaimer.
012: * 2) Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and
014: * the following disclaimer in the documentation and/or
015: * other materials provided with the distribution.
016: * 3) Neither the name of "Rafael Steil" nor
017: * the names of its contributors may be used to endorse
018: * or promote products derived from this software without
019: * specific prior written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
022: * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
023: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
024: * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
025: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR
026: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
027: * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
028: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
029: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES
030: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
031: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
032: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
033: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
034: * IN CONTRACT, STRICT LIABILITY, OR TORT
035: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
036: * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
037: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
038: *
039: * Created on 17/10/2004 23:54:47
040: * The JForum Project
041: * http://www.jforum.net
042: */
043: package net.jforum.view.forum.common;
044:
045: import java.util.ArrayList;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Map;
049:
050: import net.jforum.JForumExecutionContext;
051: import net.jforum.SessionFacade;
052: import net.jforum.dao.DataAccessDriver;
053: import net.jforum.dao.ForumDAO;
054: import net.jforum.dao.TopicDAO;
055: import net.jforum.entities.Forum;
056: import net.jforum.entities.Post;
057: import net.jforum.entities.Topic;
058: import net.jforum.entities.UserSession;
059: import net.jforum.repository.ForumRepository;
060: import net.jforum.repository.SecurityRepository;
061: import net.jforum.repository.TopicRepository;
062: import net.jforum.security.PermissionControl;
063: import net.jforum.security.SecurityConstants;
064: import net.jforum.util.I18n;
065: import net.jforum.util.concurrent.Executor;
066: import net.jforum.util.mail.EmailSenderTask;
067: import net.jforum.util.mail.TopicReplySpammer;
068: import net.jforum.util.preferences.ConfigKeys;
069: import net.jforum.util.preferences.SystemGlobals;
070: import net.jforum.view.forum.ModerationHelper;
071: import freemarker.template.SimpleHash;
072:
073: /**
074: * General utilities methods for topic manipulation.
075: *
076: * @author Rafael Steil
077: * @version $Id: TopicsCommon.java,v 1.47 2007/09/10 23:07:00 rafaelsteil Exp $
078: */
079: public class TopicsCommon {
080: private static final Object MUTEXT = new Object();
081:
082: /**
083: * List all first 'n' topics of a given forum.
084: * This method returns no more than <code>ConfigKeys.TOPICS_PER_PAGE</code>
085: * topics for the forum.
086: *
087: * @param forumId The forum id to which the topics belongs to
088: * @param start The start fetching index
089: * @return <code>java.util.List</code> containing the topics found.
090: */
091: public static List topicsByForum(int forumId, int start) {
092: TopicDAO tm = DataAccessDriver.getInstance().newTopicDAO();
093: int topicsPerPage = SystemGlobals
094: .getIntValue(ConfigKeys.TOPICS_PER_PAGE);
095: List topics;
096:
097: // Try to get the first's page of topics from the cache
098: if (start == 0
099: && SystemGlobals
100: .getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
101: topics = TopicRepository.getTopics(forumId);
102:
103: if (topics.size() == 0
104: || !TopicRepository.isLoaded(forumId)) {
105: synchronized (MUTEXT) {
106: if (topics.size() == 0
107: || !TopicRepository.isLoaded(forumId)) {
108: topics = tm.selectAllByForumByLimit(forumId,
109: start, topicsPerPage);
110: TopicRepository.addAll(forumId, topics);
111: }
112: }
113: }
114: } else {
115: topics = tm.selectAllByForumByLimit(forumId, start,
116: topicsPerPage);
117: }
118:
119: return topics;
120: }
121:
122: /**
123: * Prepare the topics for listing.
124: * This method does some preparation for a set ot <code>net.jforum.entities.Topic</code>
125: * instances for the current user, like verification if the user already
126: * read the topic, if pagination is a need and so on.
127: *
128: * @param topics The topics to process
129: * @return The post-processed topics.
130: */
131: public static List prepareTopics(List topics) {
132: UserSession userSession = SessionFacade.getUserSession();
133:
134: long lastVisit = userSession.getLastVisit().getTime();
135: int hotBegin = SystemGlobals
136: .getIntValue(ConfigKeys.HOT_TOPIC_BEGIN);
137: int postsPerPage = SystemGlobals
138: .getIntValue(ConfigKeys.POSTS_PER_PAGE);
139:
140: List newTopics = new ArrayList(topics.size());
141: Map topicsReadTime = SessionFacade.getTopicsReadTime();
142: Map topicReadTimeByForum = SessionFacade
143: .getTopicsReadTimeByForum();
144:
145: boolean checkUnread = (userSession.getUserId() != SystemGlobals
146: .getIntValue(ConfigKeys.ANONYMOUS_USER_ID));
147:
148: for (Iterator iter = topics.iterator(); iter.hasNext();) {
149: Topic t = (Topic) iter.next();
150:
151: boolean read = false;
152: boolean isReadByForum = false;
153: long lastPostTime = t.getLastPostDate().getTime();
154:
155: if (topicReadTimeByForum != null) {
156: Long currentForumTime = (Long) topicReadTimeByForum
157: .get(new Integer(t.getForumId()));
158: isReadByForum = currentForumTime != null
159: && lastPostTime < currentForumTime.longValue();
160: }
161:
162: boolean isTopicTimeOlder = !isReadByForum
163: && lastPostTime <= lastVisit;
164:
165: if (!checkUnread || isReadByForum || isTopicTimeOlder) {
166: read = true;
167: } else {
168: Integer topicId = new Integer(t.getId());
169: Long currentTopicTime = (Long) topicsReadTime
170: .get(topicId);
171:
172: if (currentTopicTime != null) {
173: read = currentTopicTime.longValue() > lastPostTime;
174: }
175: }
176:
177: if (t.getTotalReplies() + 1 > postsPerPage) {
178: t.setPaginate(true);
179: t.setTotalPages(new Double(Math.floor(t
180: .getTotalReplies()
181: / postsPerPage)));
182: } else {
183: t.setPaginate(false);
184: t.setTotalPages(new Double(0));
185: }
186:
187: // Check if this is a hot topic
188: t.setHot(t.getTotalReplies() >= hotBegin);
189:
190: t.setRead(read);
191: newTopics.add(t);
192: }
193:
194: return newTopics;
195: }
196:
197: /**
198: * Common properties to be used when showing topic data
199: */
200: public static void topicListingBase() {
201: SimpleHash context = JForumExecutionContext
202: .getTemplateContext();
203:
204: // Topic Types
205: context.put("TOPIC_ANNOUNCE", new Integer(Topic.TYPE_ANNOUNCE));
206: context.put("TOPIC_STICKY", new Integer(Topic.TYPE_STICKY));
207: context.put("TOPIC_NORMAL", new Integer(Topic.TYPE_NORMAL));
208:
209: // Topic Status
210: context.put("STATUS_LOCKED", new Integer(Topic.STATUS_LOCKED));
211: context.put("STATUS_UNLOCKED", new Integer(
212: Topic.STATUS_UNLOCKED));
213:
214: // Moderation
215: PermissionControl pc = SecurityRepository.get(SessionFacade
216: .getUserSession().getUserId());
217:
218: context.put("moderator", pc
219: .canAccess(SecurityConstants.PERM_MODERATION));
220: context
221: .put(
222: "can_remove_posts",
223: pc
224: .canAccess(SecurityConstants.PERM_MODERATION_POST_REMOVE));
225: context
226: .put(
227: "can_move_topics",
228: pc
229: .canAccess(SecurityConstants.PERM_MODERATION_TOPIC_MOVE));
230: context
231: .put(
232: "can_lockUnlock_topics",
233: pc
234: .canAccess(SecurityConstants.PERM_MODERATION_TOPIC_LOCK_UNLOCK));
235: context.put("rssEnabled", SystemGlobals
236: .getBoolValue(ConfigKeys.RSS_ENABLED));
237: }
238:
239: /**
240: * Checks if the user is allowed to view the topic.
241: * If there currently logged user does not have access
242: * to the forum, the template context will be set to show
243: * an error message to the user, by calling
244: * <blockquote>new ModerationHelper().denied(I18n.getMessage("PostShow.denied"))</blockquote>
245: * @param forumId The forum id to which the topics belongs to
246: * @return <code>true</code> if the topic is accessible, <code>false</code> otherwise
247: */
248: public static boolean isTopicAccessible(int forumId) {
249: Forum f = ForumRepository.getForum(forumId);
250:
251: if (f == null
252: || !ForumRepository.isCategoryAccessible(f
253: .getCategoryId())) {
254: new ModerationHelper().denied(I18n
255: .getMessage("PostShow.denied"));
256: return false;
257: }
258:
259: return true;
260: }
261:
262: /**
263: * Sends a "new post" notification message to all users watching the topic.
264: *
265: * @param t The changed topic
266: * @param p The new message
267: */
268: public static void notifyUsers(Topic t, Post p) {
269: if (SystemGlobals.getBoolValue(ConfigKeys.MAIL_NOTIFY_ANSWERS)) {
270: TopicDAO dao = DataAccessDriver.getInstance().newTopicDAO();
271: List usersToNotify = dao.notifyUsers(t);
272:
273: // We only have to send an email if there are users
274: // subscribed to the topic
275: if (usersToNotify != null && usersToNotify.size() > 0) {
276: Executor.execute(new EmailSenderTask(
277: new TopicReplySpammer(t, p, usersToNotify)));
278: }
279: }
280: }
281:
282: /**
283: * Updates the board status after a new post is inserted.
284: * This method is used in conjunct with moderation manipulation.
285: * It will increase by 1 the number of replies of the tpoic, set the
286: * last post id for the topic and the forum and refresh the cache.
287: *
288: * @param topic Topic The topic to update
289: * @param lastPostId int The id of the last post
290: * @param topicDao TopicDAO A TopicModel instance
291: * @param forumDao ForumDAO A ForumModel instance
292: * @param firstPost boolean
293: */
294: public static synchronized void updateBoardStatus(Topic topic,
295: int lastPostId, boolean firstPost, TopicDAO topicDao,
296: ForumDAO forumDao) {
297: topic.setLastPostId(lastPostId);
298: topicDao.update(topic);
299:
300: forumDao.setLastPost(topic.getForumId(), lastPostId);
301:
302: if (firstPost) {
303: forumDao.incrementTotalTopics(topic.getForumId(), 1);
304: } else {
305: topicDao.incrementTotalReplies(topic.getId());
306: }
307:
308: topicDao.incrementTotalViews(topic.getId());
309:
310: TopicRepository.addTopic(topic);
311: TopicRepository.pushTopic(topic);
312:
313: ForumRepository.incrementTotalMessages();
314: }
315:
316: /**
317: * Deletes a topic.
318: * This method will remove the topic from the database,
319: * clear the entry frm the cache and update the last
320: * post info for the associated forum.
321: * @param topicId The topic id to remove
322: * @param fromModeration boolean
323: * @param forumId int
324: */
325: public static synchronized void deleteTopic(int topicId,
326: int forumId, boolean fromModeration) {
327: TopicDAO topicDao = DataAccessDriver.getInstance()
328: .newTopicDAO();
329:
330: Topic topic = new Topic();
331: topic.setId(topicId);
332: topic.setForumId(forumId);
333:
334: topicDao.delete(topic, fromModeration);
335:
336: if (!fromModeration) {
337: // Updates the Recent Topics if it contains this topic
338: TopicRepository.loadMostRecentTopics();
339:
340: // Updates the Hottest Topics if it contains this topic
341: TopicRepository.loadHottestTopics();
342: TopicRepository.clearCache(forumId);
343: topicDao.removeSubscriptionByTopic(topicId);
344: }
345: }
346: }
|