incorporating

r1628 | chrisg23 | 2007-09-17 10:10:40 +0200 (Mo, 17 Sep 2007) | 1 line

Sourceforge patch 1689988 - rather a large commit, but in fact causes very little visible change. Large single commit is necessary because all these changes are essentially atomic.


git-svn-id: https://svn.libreccm.org/ccm/trunk@8 8810af33-2d31-482b-a856-94f89814c4df
master
pb 2008-02-15 08:41:21 +00:00
parent 0885de7620
commit ffa005c685
91 changed files with 5889 additions and 1363 deletions

View File

@ -2,11 +2,12 @@
<ccm:application xmlns:ccm="http://ccm.redhat.com/ccm-project"
name="ccm-forum"
prettyName="Forum"
version="6.5.0"
version="6.5.2"
release="1">
<ccm:dependencies>
<ccm:requires name="ccm-core" version="6.2.0" relation="ge"/>
<ccm:requires name="ccm-cms" version="6.2.0" relation="ge"/>
<ccm:requires name="ccm-core" version="6.5.3"/>
<ccm:requires name="ccm-cms" version="6.5.3"/>
</ccm:dependencies>
<ccm:contacts>
<ccm:contact uri="http://www.redhat.com/software/rhea" type="website"/>

View File

@ -0,0 +1,86 @@
Main new features included in this release are:
Fine grained forum permissions
Allow file attachments and images in posts
Contents of posts are searchable
New portlet type lising links to all forums user has read access to
Allow automatic thread subscription for thread starter (useful for noticeboard type forums)
Forum admin can prevent posts with no topic
Expiry can be specified for all types of forum
Reply count calculation fixed
HTML Editor can be used for posts
Subscription emails can (and should) be sent as html emails)
Other minor changes, fixes and configurable features are also included.
Check new config parameter descriptions in com.arsdigita.forum.ForumConfig_parameter.properties
After deployment, to install new features, either get rid of existing forum app, and load new version, OR
ccm upgrade ccm-forum --from-version 6.5.0 --to-version 6.5.1
ccm upgrade ccm-forum --from-version 6.5.1 --to-version 6.5.2
ccm-run com.arsdigita.london.search.Reindexer --object-type com.arsdigita.forum.Post
When developing this, I tried to ensure that a basic deployment will not affect the XML
that is produced by the current forum application.
I have tested the new implementation with Coventry's theme. There is one theme amendment required (because of a bug fix).
In current release, the drop down list for filtering threads by category has the same name as the drop down list for selecting a category when making a new post. This means that the selected value carries through . ie if you have filtered the threads then make a new post, the new post has the filter category selected as default. More importantly, if you make a post and assign a category, then the thread list is filtered after you complete the post.
To fix the bug, I renamed the drop down list in the post form from topic to PostTopic. This means the category drop down is not visible until the two references to @name='topic' in the forum:postForm template are changed to @name='postTopic'
Only other visible changes if the patch is applied without changing config
are:
Reply count only includes posts that have been approved
Posts are searchable from the main site search. This is a change from the current behaviour . if not desired then it could be made configurable.
Moderation form and emails are formatted slightly differently
recent forum postings portlet prevents users creating new forum application .
only site admin can do this. Also filters forums to those that the user has
read access on.
New portlet . my forums. Lists links to forums that user has read access to.
The other non-visble effect is that the wizard uses java session rather than
hidden fields. This approach makes it much easier to include links in form
steps (eg remove an uploaded file etc). The impact is that users in a load
balanced environment need to consider session affinity. In our production
environment, this is accomplished by the server that sprays requests to the
various back end servers. It identifies each browser session and routes all
their requests to the same back end server. Hence there is no replication of
session - the user is guaranteed to end up at the server that holds their java
session. As a result, when a backend server is stopped, there is a risk that a
user with session on that server in the middle of creating a post will lose
information entered up to that point. On our site we live with this small risk
but mitigate it by restarting servers at unsociable hours where possible.
I have put examle theme files in
ccm-ldn-aplaws/web/__ccm__/themes/aplaws/forum-example: forum-index.xsl,
forum-index.css and the contents of images/forum
To have a look at a forum using these files, copy the xsl and css to the top
level of your theme and make a new forum directory in images and copy the
images there. I.m afraid these files have continued the existing format of
putting all the templates in a single file, so they are not the most easily
maintained, and I make no guarantees about the content, but they do work. and
if required they may be used as a basis for your own look and feel.
There is a separate application - ccm-categorised-forum that adds category path and menu components to the front end,
and adds a categorisation tab for admin users. Load this application if you would like this functionality.
When a forum is created, only site admin can access the admin tabs,
until you assign users/groups to the forum admin group under the permissions tab.
Next things I plan to do are:
list pending and reapprove posts under the moderation tab
create a user info page so users can write a bit about themselves
allow thread branching so administrators can split long threads or threads that have strayed from the point.
Allow different thread views - paginated full details, or tree of titles with one post visible (a la sourceforge)
chris.gilbert@westsussex.gov.uk

View File

@ -1,25 +0,0 @@
// Forum initialization file
//
// ::replyHostName:: -> example.com
// ::digestUserEmail:: -> digests
// ::adminCanEditPosts:: -> true
// ::authorCanEditPosts:: -> true
// ::forums:: -> {}
init com.arsdigita.forum.LegacyInitializer {
// All outbound bboard message will have return address as the
// following format: forumID.bboard@hostname
replyHostName = "::replyHostName::";
// The email address that daily digests come from
digestUserEmail = "::digests::";
// whether administrators can edit posts
adminCanEditPosts = ::adminCanEditPosts::;
// whether the author of a post can edit it later
authorCanEditPosts = ::authorCanEditPosts::;
forums = ::forums::;
}
init com.arsdigita.domain.installer.DomainObjectTraversalInitializer {
adapters = { "/WEB-INF/resources/forum-adapters.xml" };
}

View File

@ -32,8 +32,16 @@ object type Forum extends Application {
Boolean [1..1] isModerated = forum_forums.is_moderated;
Boolean [1..1] isNoticeboard = forum_forums.is_noticeboard;
component Group [0..1] adminGroup =
join forum_forums.admin_group_id to groups.group_id;
component Group [0..1] moderationGroup =
join forum_forums.mod_group_id to groups.group_id;
component Group [0..1] threadCreateGroup =
join forum_forums.create_group_id to groups.group_id;
component Group [0..1] threadRespondGroup =
join forum_forums.respond_group_id to groups.group_id;
component Group [0..1] readGroup =
join forum_forums.read_group_id to groups.group_id;
component ForumSubscription[0..n] subscriptions =
join forum_forums.forum_id to forum_subscriptions.forum_id;
@ -44,7 +52,12 @@ object type Forum extends Application {
join forum_forums.lifecycle_definition_id to lifecycle_definitions.definition_id;
BigDecimal [0..1] expireAfter = forum_forums.expire_after;
Boolean [1..1] fileAttachmentsAllowed = forum_forums.file_attachments_allowed;
Boolean [1..1] imageUploadsAllowed = forum_forums.image_uploads_allowed;
Boolean [1..1] autoSubscribeThreadStarter = forum_forums.subscribe_thread_starter;
Boolean [1..1] noCategoryPostsAllowed = forum_forums.no_category_posts_allowed;
Boolean [1..1] anonymousPostsAllowed = forum_forums.anonymous_posts_allowed;
String [0..1] introduction = forum_forums.introduction VARCHAR(4000);
reference key (forum_forums.forum_id);
}

View File

@ -0,0 +1,30 @@
//
// Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2.1 of
// the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// $Id: RecentPostingsPortlet.pdl 285 2005-02-22 00:29:02Z sskracic $
// $DateTime: 2004/08/17 23:26:27 $
model com.arsdigita.forum;
import com.arsdigita.portal.Portlet;
// lists links to forums for which user has read access
object type MyForumsPortlet extends Portlet {
// no attributes for this one
}

View File

@ -21,6 +21,9 @@ model com.arsdigita.forum;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.kernel.Party;
import com.arsdigita.cms.FileAsset;
import com.arsdigita.cms.ImageAsset;
object type Post extends com.arsdigita.messaging.ThreadedMessage {
@ -29,6 +32,31 @@ object type Post extends com.arsdigita.messaging.ThreadedMessage {
aggressive load(moderator.id);
}
object type PostFileAttachment extends FileAsset {
Integer[0..1] fileOrder = forum_post_files.file_order INTEGER;
reference key (forum_post_files.file_id);
}
object type PostImageAttachment extends ImageAsset {
Integer[0..1] imageOrder = forum_post_images.image_order INTEGER;
reference key (forum_post_images.image_id);
}
association {
composite Post[0..1] fileMessage = join forum_post_files.post_id to forum_posts.post_id;
component PostFileAttachment[0..n] files = join forum_posts.post_id to forum_post_files.post_id;
}
association {
composite Post[0..1] imageMessage = join forum_post_images.post_id to forum_posts.post_id;
component PostImageAttachment[0..n] images = join forum_posts.post_id to forum_post_images.post_id;
}
association {
Party [0..1] moderator = join forum_posts.moderator to parties.party_id;
Post [0..1] moderatedMessage = join parties.party_id to forum_posts.moderator;
@ -53,3 +81,20 @@ query threadModerationStatus {
id = message_threads.thread_id;
}
}
// Subquery for filtering out threads where root post is of a
// particular status (eg unconfirmed)
//
//query threadStatus {
// BigDecimal id;
//
// do {
// select m.thread_id
// from forum_posts f,
// message_threads m
// where m.root_id = f.post_id
// and f.status <> :status2
// } map {
// id = message_threads.thread_id;
// }
// }

View File

@ -0,0 +1,3 @@
alter table forum_forums add (anonymous_posts_allowed CHAR(1));
update forum_forums set anonymous_posts_allowed = 0;

View File

@ -0,0 +1,21 @@
create table forum_post_files
(post_id NUMBER,
file_id NUMBER not null,
file_order NUMBER);
alter table forum_post_files add
constraint FORU_POS_FILES_FILE_ID_F_XBKED foreign key (file_id)
references cms_files(file_id);
alter table forum_post_files add
constraint FORU_POS_FILES_POST_ID_F_K0XJQ foreign key (post_id)
references forum_posts(post_id);
alter table forum_forums add (file_attachments_allowed CHAR(1));
update forum_forums set file_attachments_allowed = 0;

View File

@ -0,0 +1,2 @@
ALTER TABLE FORUM_FORUMS ADD (
INTRODUCTION VARCHAR2(4000) );

View File

@ -0,0 +1,3 @@
alter table forum_forums add (no_category_posts_allowed CHAR(1));
update forum_forums set no_category_posts_allowed = 1;

View File

@ -0,0 +1,20 @@
alter table forum_forums
add (admin_group_id NUMBER,
create_group_id NUMBER,
respond_group_id NUMBER,
read_group_id NUMBER);
alter table forum_forums add
constraint foru_foru_admi_grou_id_f_k0nw6 foreign key (admin_group_id)
references groups(group_id);
alter table forum_forums add
constraint foru_foru_crea_grou_id_f_f7x57 foreign key (create_group_id)
references groups(group_id);
alter table forum_forums add
constraint foru_foru_respo_gro_id_f_rnofz foreign key (respond_group_id)
references groups(group_id);
alter table forum_forums add
constraint foru_forum_rea_grou_id_f_itati foreign key (read_group_id)
references groups(group_id);

View File

@ -0,0 +1,21 @@
create table forum_post_images
(post_id NUMBER,
image_id NUMBER not null,
image_order NUMBER);
alter table forum_post_images add
constraint FORU_POS_IMAGE_IMAG_ID_F_WYRXA foreign key (image_id)
references cms_images(image_id);
alter table forum_post_images add
constraint FORU_POS_IMAGE_POST_ID_F_1HH02 foreign key (post_id)
references forum_posts(post_id);
alter table forum_forums add (image_uploads_allowed CHAR(1));
update forum_forums set image_uploads_allowed = 0;

View File

@ -0,0 +1,18 @@
insert into acs_privileges (privilege)
values ('forum_read');
insert into acs_privileges (privilege)
values ('forum_create_thread');
insert into acs_privileges (privilege)
values ('forum_respond');
insert into acs_privilege_hierarchy (privilege, child_privilege)
values ('forum_moderation', 'forum_create_thread');
insert into acs_privilege_hierarchy (privilege, child_privilege)
values ('forum_create_thread', 'forum_respond');
insert into acs_privilege_hierarchy (privilege, child_privilege)
values ('forum_respond', 'forum_read');

View File

@ -0,0 +1,5 @@
alter table forum_forums add (subscribe_thread_starter CHAR(1));
update forum_forums set subscribe_thread_starter = 0;

View File

@ -0,0 +1,8 @@
@@ ../default/upgrade/6.5.0-6.5.1/add_privileges.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_groups.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_thread_subscriber.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_file_attachments.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_forum_introduction.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_image_uploads.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_forum_no_category_posts.sql
@@ ../default/upgrade/6.5.0-6.5.1/add_anonymous_option.sql

View File

@ -0,0 +1,10 @@
\i ../default/upgrade/6.5.0-6.5.1/add_thread_subscriber.sql
\i ../default/upgrade/6.5.0-6.5.1/add_privileges.sql
\i ../default/upgrade/6.5.0-6.5.1/add_groups.sql
\i ../default/upgrade/6.5.0-6.5.1/add_privileges.sql
\i ../default/upgrade/6.5.0-6.5.1/add_file_attachments.sql
\i ../default/upgrade/6.5.0-6.5.1/add_forum_introduction.sql
\i ../default/upgrade/6.5.0-6.5.1/add_image_uploads.sql
\i ../default/upgrade/6.5.0-6.5.1/add_forum_no_category_posts.sql
\i ../default/upgrade/6.5.0-6.5.1/add_anonymous_option.sql

View File

@ -79,6 +79,8 @@
<xrd:property name="/object/root"/>
<xrd:property name="/object/categories"/>
<xrd:property name="/object/sender"/>
<xrd:property name="/object/images"/>
<xrd:property name="/object/files"/>
</xrd:associations>
</xrd:adapter>
</xrd:context>
@ -132,4 +134,77 @@
</xrd:adapter>
</xrd:context>
<xrd:context name="com.arsdigita.forum.ui.ConfirmStep">
<xrd:adapter objectType="com.arsdigita.forum.PostFileAttachment">
<xrd:attributes rule="exclude"/>
</xrd:adapter>
<xrd:adapter objectType="com.arsdigita.forum.PostImageAttachment">
<xrd:attributes rule="exclude"/>
</xrd:adapter>
<!-- <xrd:adapter objectType="com.arsdigita.forum.Post"
formatter="com.arsdigita.forum.ui.MessageXMLFormatter">
<xrd:attributes rule="exclude">
<xrd:property name="/object/id"/>
<xrd:property name="/object/objectType"/>
<xrd:property name="/object/defaultDomainClass"/>
<xrd:property name="/object/displayName"/>
<xrd:property name="/object/replyTo"/>
<xrd:property name="/object/objectID"/>
<xrd:property name="/object/messageID"/>
<xrd:property name="/object/root"/>
<xrd:property name="/object/inReplyTo"/>
<xrd:property name="/object/categories/objectType"/>
<xrd:property name="/object/categories/defaultDomainClass"/>
<xrd:property name="/object/categories/displayName"/>
<xrd:property name="/object/categories/isAbstract"/>
<xrd:property name="/object/categories/isEnabled"/>
<xrd:property name="/object/categories/defaultAncestors"/>
<xrd:property name="/object/sender/id"/>
<xrd:property name="/object/sender/objectType"/>
<xrd:property name="/object/sender/defaultDomainClass"/>
</xrd:attributes>
<xrd:associations rule="include">
<xrd:property name="/object/root"/>
<xrd:property name="/object/categories"/>
<xrd:property name="/object/sender"/>
<xrd:property name="/object/images"/>
<xrd:property name="/object/files"/>
</xrd:associations>
</xrd:adapter> -->
</xrd:context>
<xrd:context name="com.arsdigita.forum.search.PostMetadataProvider">
<xrd:adapter objectType="com.arsdigita.forum.Post">
<xrd:attributes rule="include">
<xrd:property name="/object/subject"/>
<xrd:property name="/object/body"/>
<xrd:property name="/object/images/description"/>
</xrd:attributes>
<xrd:associations rule="include">
<xrd:property name="/object/images"/>
</xrd:associations>
</xrd:adapter>
</xrd:context>
<xrd:context name="com.arsdigita.forum.search.FileAttachmentMetadataProvider">
<xrd:adapter objectType="com.arsdigita.forum.PostFileAttachment">
<xrd:attributes rule="exclude">
</xrd:attributes>
</xrd:adapter>
</xrd:context>
</xrd:adapters>

View File

@ -5,4 +5,11 @@
<version from="1.4.3" to="1.4.4">
<script sql="ccm-forum/upgrade/::database::-1.4.3-1.4.4.sql"/>
</version>
<version from="6.5.0" to="6.5.1">
<script sql="ccm-forum/upgrade/::database::-6.5.0-6.5.1.sql"/>
<script class="com.arsdigita.forum.upgrade.CreateGroupsAndPortletType"/>
</version>
<version from="6.5.1" to="6.5.2">
<script class="com.arsdigita.forum.upgrade.CreateContainerGroups"/>
</version>
</upgrade>

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageFactory;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.page.BebopMapDispatcher;
import com.arsdigita.bebop.event.RequestEvent;
import com.arsdigita.bebop.event.RequestListener;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.forum.ui.ForumComponent;
import com.arsdigita.forum.ui.ThreadComponent;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
/**
* Maps the bboard user interface onto "index.jsp" and "thread.jsp".
*
* @author Kevin Scaldeferri
* @author Vadim Nasardinov (vadimn@redhat.com)
*
* @version $Revision: #18 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
*/
public class BboardDispatcher extends BebopMapDispatcher
implements Constants {
public static final String versionId =
"$Id: BboardDispatcher.java 287 2005-02-22 00:29:02Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final String XSL_HOOK = "forum";
private static final Logger s_log = Logger.getLogger
(BboardDispatcher.class);
/**
* Constructor. Instantiates the subsite url/page mapping.
*/
public BboardDispatcher() {
s_log.warn("BboardDispatcher created!");
Page index = buildForumPage(new BigDecimalParameter(TOPIC_PARAM));
addPage("", index);
addPage("index.jsp", index);
addPage("thread.jsp", buildThreadPage(
new BigDecimalParameter(THREAD_PARAM)));
}
static Page buildThreadPage(BigDecimalParameter stateParam) {
Page threadPage = PageFactory.buildPage(XSL_HOOK, "Threads", "forumThreadPage");
threadPage.add(new ThreadComponent());
// Register the thread id parameter as a global state parameter.
threadPage.addGlobalStateParam(stateParam);
threadPage.addRequestListener
(new ThreadPageRequestListener(stateParam));
threadPage.lock();
return threadPage;
}
static Page buildForumPage(BigDecimalParameter stateParam) {
Page page = PageFactory.buildPage(XSL_HOOK, "Forum", "forumPage");
ForumComponent forumComp = new ForumComponent();
page.add(forumComp);
page.addGlobalStateParam(stateParam);
page.addRequestListener(new ForumPageRequestListener(stateParam, forumComp));
page.lock();
return page;
}
private static class ThreadPageRequestListener implements RequestListener {
private BigDecimalParameter m_threadID;
public ThreadPageRequestListener(BigDecimalParameter threadID) {
m_threadID = threadID;
}
public void pageRequested(RequestEvent event) {
PageState state = event.getPageState();
ForumContext context = ForumContext.getContext(state);
context.setThreadID
((BigDecimal) event.getPageState().getValue(m_threadID));
}
}
private static class ForumPageRequestListener implements RequestListener {
private BigDecimalParameter m_categorySelection;
private ForumComponent m_forumComp;
public ForumPageRequestListener(BigDecimalParameter categorySelection,
ForumComponent forumComp) {
m_categorySelection = categorySelection;
m_forumComp = forumComp;
}
public void pageRequested(RequestEvent event) {
PageState state = event.getPageState();
ForumContext context = ForumContext.getContext(state);
context.setCategorySelection
((BigDecimal) event.getPageState().getValue(m_categorySelection));
}
}
}

View File

@ -36,7 +36,7 @@ import java.math.BigDecimal;
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
*
* @version $Revision: #8 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
* @version $Revision: 1.1 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
*/
public class DailySubscription extends ForumSubscription {
@ -94,6 +94,11 @@ public class DailySubscription extends ForumSubscription {
return BASE_DATA_OBJECT_TYPE;
}
public String getSubscriptionGroupName() {
return "Daily Digest Subscription Group";
}
private Digest getDigest() {
if (m_digest == null) {
DataObject digestData = (DataObject) get(DIGEST);
@ -115,6 +120,11 @@ public class DailySubscription extends ForumSubscription {
public void sendNotification(Post post) {
Notification notification = new Notification(getGroup(), post);
notification.setDigest(getDigest());
if (Forum.getConfig().deleteNotifications()) {
// make sure we don't delete the post itself!!!
notification.setMessageDelete(Boolean.FALSE);
notification.setIsPermanent(Boolean.FALSE);
}
notification.save();
}

View File

@ -18,6 +18,10 @@
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
import com.arsdigita.categorization.Category;
import com.arsdigita.cms.lifecycle.LifecycleDefinition;
import com.arsdigita.domain.DataObjectNotFoundException;
@ -37,27 +41,27 @@ import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.Filter;
import com.arsdigita.persistence.FilterFactory;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
import com.arsdigita.web.Application;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
/**
* The Forum class represents a discussion forum.
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
*
* @version $Revision: #25 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
* @version $Revision: 1.7 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
*/
public class Forum extends Application {
public static final String THREAD_SUBSCRIPTION_GROUPS_NAME = "Thread Subscription Groups";
public static final String versionId =
"$Id: Forum.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
"$Id: Forum.java 1628 2007-09-17 08:10:40Z chrisg23 $"
+ "$Author: chrisg23 $"
+ "$DateTime: 2004/08/17 23:26:27 $";
private static ForumConfig s_config = new ForumConfig();
@ -76,18 +80,48 @@ public class Forum extends Application {
"com.arsdigita.forum.Forum";
public static final String PACKAGE_TYPE = "forum";
//////
//Forum specific privileges
/////
public static final String FORUM_MODERATION_PRIVILEGE = "forum_moderation";
public static final String CREATE_THREAD_PRIVILEGE = "forum_create_thread";
public static final String RESPOND_TO_THREAD_PRIVILEGE = "forum_respond";
// separate read privilege required because all public users
// have READ on homepage, which is parent of forum, hence
// everyone inherits READ cg
//
// note in hindsight, I have stopped homepage being set as
// permission context for forum, because site search checks
// for READ privilege anyway, and so search results were being
// returned for non public posts. This means there is no longer
// any need for a separate forum_read privilege, though it
// does no harm. Now removed
// public static final String FORUM_READ_PRIVILEGE = "forum_read";
public static final String FORUM_MODERATION_PRIVILEGE
= "forum_moderation";
///////
// pdl forum attribute/association names
///////
private static final String POSTS = "posts";
private static final String SUBSCRIPTIONS = "subscriptions";
private static final String MODERATION = "isModerated";
private static final String NOTICEBOARD = "isNoticeboard";
private static final String ADMIN_GROUP = "adminGroup";
private static final String MODERATION_GROUP = "moderationGroup";
private static final String THREAD_CREATE_GROUP = "threadCreateGroup";
private static final String THREAD_RESPONDER_GROUP = "threadRespondGroup";
private static final String READ_GROUP = "readGroup";
private static final String CATEGORY = "category";
private static final String EXPIRE_AFTER = "expireAfter";
private static final String LIFECYCLE_DEFINITION = "lifecycleDefinition";
// additional attributes added chris.gilbert@westsussex.gov.uk
private static final String ALLOW_FILE_ATTACHMENTS =
"fileAttachmentsAllowed";
private static final String ALLOW_IMAGE_UPLOADS = "imageUploadsAllowed";
private static final String AUTOSUBSCRIBE_THREAD_STARTER =
"autoSubscribeThreadStarter";
private static final String INTRODUCTION = "introduction";
private static final String NO_CATEGORY_POSTS = "noCategoryPostsAllowed";
private static final String ANONYMOUS_POSTS = "anonymousPostsAllowed";
public Forum(DataObject data) {
super(data);
@ -113,16 +147,26 @@ public class Forum extends Application {
* category for the Forum in the event that the Forum should be
* categorized.
* This also sets up instant and daily subscriptions on the Forum.
* The default for moderation is false
* The default for moderation is false.
*
* Also sets default values for other forum settings. These can be
* amended under the setup tab in the ui
*/
public static Forum create(String urlName, String title,
Application parent, boolean moderated) {
s_log.debug("creating forum " + title);
Forum forum = (Forum) Application.createApplication
(BASE_DATA_OBJECT_TYPE, urlName, title, parent);
(BASE_DATA_OBJECT_TYPE, urlName, title, parent, true);
forum.setModerated(moderated);
// default settings ensure legacy forum users do not
// see any change chris.gilbert@westsussex.gov.uk
forum.setAllowFileAttachments(false);
forum.setAllowImageUploads(false);
forum.setAutoSubscribeThreadCreator(false);
forum.setNoCategoryPostsAllowed(true);
forum.setAnonymousPostsAllowed(false);
return forum;
}
@ -151,6 +195,22 @@ public class Forum extends Application {
}
/**
* Set introduction
*/
public void setIntroduction(String introduction) {
set(INTRODUCTION, introduction);
}
/**
* @return introduction
*/
public String getIntroduction() {
return (String) get(INTRODUCTION);
}
/**
* creates a Root Category for the forum.
*/
private Category createRootCategory() {
@ -162,7 +222,11 @@ public class Forum extends Application {
return category;
}
private void createModerationGroup() {
private void createGroups() {
Group administrators = new Group();
administrators.setName(getTitle() + " Administrators");
setAssociation(ADMIN_GROUP, administrators);
Group moderators = new Group();
moderators.setName(getTitle() + " Moderators");
setAssociation( MODERATION_GROUP, moderators );
@ -176,6 +240,37 @@ public class Forum extends Application {
// Actually, the only hack involved is making the email address unique.
String email = "forum-moderator-" + getID() + "-" + moderators.getID() + "@" + s_config.getReplyHostName();
moderators.setPrimaryEmail(new EmailAddress(email));
// chris.gilbert@westsussex.gov.uk create additional groups for privilege
// assignment - could have assigned privileges directly without having associated
// groups, but this reduces rows in the (already enormous) dnm_permissions
// table
Group threadCreators = new Group();
threadCreators.setName(getTitle() + " Thread Creators");
setAssociation(THREAD_CREATE_GROUP, threadCreators);
Group threadResponders = new Group();
threadResponders.setName(getTitle() + " Thread Responders");
setAssociation(THREAD_RESPONDER_GROUP, threadResponders);
Group forumReaders = new Group();
forumReaders.setName(getTitle() + " Readers");
setAssociation(READ_GROUP, forumReaders);
Group container = getGroup();
container.addSubgroup(administrators);
container.addSubgroup(moderators);
container.addSubgroup(threadCreators);
container.addSubgroup(threadResponders);
container.addSubgroup(forumReaders);
Group threadSubscriptions = new Group();
threadSubscriptions.setName(THREAD_SUBSCRIPTION_GROUPS_NAME);
container.addSubgroup(threadSubscriptions);
container.save();
}
public void initialize() {
@ -184,8 +279,13 @@ public class Forum extends Application {
if (isNew()) {
setModerated(false);
setNoticeboard(false);
setAllowFileAttachments(false);
setAllowImageUploads(false);
setAutoSubscribeThreadCreator(false);
setNoCategoryPostsAllowed(true);
setAnonymousPostsAllowed(false);
createRootCategory();
createModerationGroup();
}
}
@ -199,14 +299,71 @@ public class Forum extends Application {
protected void afterSave() {
if (m_wasNew) {
PermissionService.setContext(getRootCategory(), this);
createGroups();
if (getAdminGroup() != null) {
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.ADMIN,
this,
getAdminGroup()));
s_log.debug(
"Current user : "
+ Kernel.getContext().getParty().getPrimaryEmail()
+ " class is "
+ Kernel.getContext().getParty().getClass());
//
// chris.gilbert@westsussex.gov.uk Original plan was that creator of forum
// is administrator by default, but party from Kernel at this point in code is
// acs-system-party - creation must happen in a KernelExcersion somewhere
// though I can't immediately see where.
// as a consequence, code below justs causes a classcast exception,
//
// revisit, but in meantime, only site admin can administer new forum
// until forum admin permissions set in UI
//
// User creator = (User) Kernel.getContext().getParty();
// can't be null but let's be supercautious
// if (creator != null) {
// getAdminGroup().addMember(creator);
// }
///
}
if (getModerationGroup() != null ) {
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.get(FORUM_MODERATION_PRIVILEGE),
this,
getModerationGroup())
);
getModerationGroup()));
}
if (getThreadCreateGroup() != null) {
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.get(CREATE_THREAD_PRIVILEGE),
this,
getThreadCreateGroup()));
// chris.gilbert@westsussex.gov.uk
// wouldn't do this normally, but this enables legacy implementations
// to use new version without any side effects
// public can view forum by default and see create thread link - existing
// code forces login if link is selected
getThreadCreateGroup().addMember(Kernel.getPublicUser());
}
if (getThreadResponderGroup() != null) {
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.get(RESPOND_TO_THREAD_PRIVILEGE),
this,
getThreadResponderGroup()));
}
if (getReadGroup() != null) {
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.READ,
this,
getReadGroup()));
}
KernelExcursion excursion = new KernelExcursion() {
protected void excurse() {
@ -243,7 +400,15 @@ public class Forum extends Application {
}
super.afterSave();
// chris.gilbert@westsussex.gov.uk line removed.
// afterSave in Application sets permission
// context of forum to parent app (portal homepage)
// don't want to inherit permissions of portal,
// as public users have 'READ' privilege on this
// and so get shown postings in search results.
//
//
// super.afterSave();
}
protected String getBaseDataObjectType() {
@ -287,6 +452,38 @@ public class Forum extends Application {
}
/**
* gets all pending messages and messages for reapproval - allows
* moderators to see which messages require their attention
* @return
*/
public DataAssociation getPendingPosts() {
// doesn't use getPosts in view of the warning that it
// may disappear
DataAssociation posts = (DataAssociation) get(POSTS);
FilterFactory factory = posts.getFilterFactory();
Filter pending = factory.equals(Post.STATUS, Post.PENDING);
Filter reapprove = factory.equals(Post.STATUS, Post.REAPPROVE);
;
posts.addFilter(factory.or().addFilter(pending).addFilter(reapprove));
return posts;
}
/**
* gets all suppressed messages - allows moderators to see which messages
* heve been rejectedrequire their attention
* @return
*/
public DataAssociation getSuppressedPosts() {
// doesn't use getPosts in view of the warning that it
// may disappear
DataAssociation posts = (DataAssociation) get(POSTS);
posts.addEqualsFilter(Post.STATUS, Post.SUPPRESSED);
return posts;
}
/**
* Gets a ThreadCollection of the threads in this forum. I.e. the
* top-level posts which are not replies to any other post.
*/
@ -358,9 +555,9 @@ public class Forum extends Application {
}
return threads;
}
}
/**
* Sets up instant and daily subscriptions for the forum. Daily
@ -527,6 +724,12 @@ public class Forum extends Application {
return Boolean.TRUE.equals(get(NOTICEBOARD));
}
/** Returns the administrator group. Null if it doesn't exist */
public Group getAdminGroup() {
DataObject dObj = (DataObject) get(ADMIN_GROUP);
Assert.exists(dObj, DataObject.class);
return (Group) DomainObjectFactory.newInstance(dObj);
}
/** Returns the moderator group. Null if it doesn't exist */
public Group getModerationGroup() {
DataObject dObj = (DataObject) get( MODERATION_GROUP );
@ -534,6 +737,27 @@ public class Forum extends Application {
return (Group)DomainObjectFactory.newInstance(dObj);
}
/** Returns the thread create group. Null if it doesn't exist */
public Group getThreadCreateGroup() {
DataObject dObj = (DataObject) get(THREAD_CREATE_GROUP);
Assert.exists(dObj, DataObject.class);
return (Group) DomainObjectFactory.newInstance(dObj);
}
/** Returns the thread reply group. Null if it doesn't exist */
public Group getThreadResponderGroup() {
DataObject dObj = (DataObject) get(THREAD_RESPONDER_GROUP);
Assert.exists(dObj, DataObject.class);
return (Group) DomainObjectFactory.newInstance(dObj);
}
/** Returns the read group. Null if it doesn't exist */
public Group getReadGroup() {
DataObject dObj = (DataObject) get(READ_GROUP);
Assert.exists(dObj, DataObject.class);
return (Group) DomainObjectFactory.newInstance(dObj);
}
public String getContextPath() {
return "/ccm-forum";
}
@ -555,7 +779,7 @@ public class Forum extends Application {
}
LifecycleDefinition newLife = new LifecycleDefinition();
newLife.setLabel("Delete expired noticeboard postings");
newLife.addPhaseDefinition("Noticeboard posting lifespan",
newLife.addPhaseDefinition("Forum posting lifespan",
null,
new Integer(0),
new Integer(1440 * value), // in minutes
@ -565,13 +789,20 @@ public class Forum extends Application {
// have the same expiration policy.
DataAssociationCursor posts = getPosts().cursor();
while (posts.next()) {
Post post
= (Post)DomainObjectFactory.newInstance(
posts.getDataObject());
s_log.debug("Resetting expiration lifecycle for " + post.getOID());
Post post =
(Post) DomainObjectFactory.newInstance(posts.getDataObject());
if (post
.getThread()
.getRootMessage()
.getID()
.equals(post.getID())) {
s_log.debug(
"Resetting expiration lifecycle for " + post.getOID());
post.setLifecycle(newLife);
}
}
}
public int getExpireAfter() {
BigDecimal expire = (BigDecimal) get(EXPIRE_AFTER);
@ -594,5 +825,119 @@ public class Forum extends Application {
set(LIFECYCLE_DEFINITION, life);
}
/**
* method required for upgrade - normally groups are set during forum creation and so
* there is no need to invoke a setter
* @author cgyg9330
*
*/
public void setAdminGroup(Group group) {
setAssociation(ADMIN_GROUP, group);
PermissionService.grantPermission(
new PermissionDescriptor(PrivilegeDescriptor.ADMIN, this, group));
}
/**
* method required for upgrade - normally groups are set during forum creation and so
* there is no need to invoke a setter
* @author cgyg9330
*
*/
public void setThreadCreatorGroup(Group group) {
setAssociation(THREAD_CREATE_GROUP, group);
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.get(CREATE_THREAD_PRIVILEGE),
this,
group));
}
/**
* method required for upgrade - normally groups are set during forum creation and so
* there is no need to invoke a setter
* @author cgyg9330
*
*/
public void setThreadResponderGroup(Group group) {
setAssociation(THREAD_RESPONDER_GROUP, group);
PermissionService.grantPermission(
new PermissionDescriptor(
PrivilegeDescriptor.get(RESPOND_TO_THREAD_PRIVILEGE),
this,
group));
}
/**
* method required for upgrade - normally groups are set during forum creation and so
* there is no need to invoke a setter
* @author cgyg9330
*
*/
public void setReaderGroup(Group group) {
setAssociation(READ_GROUP, group);
PermissionService.grantPermission(
new PermissionDescriptor(PrivilegeDescriptor.READ, this, group));
}
/**
* @return
*/
public boolean allowFileAttachments() {
return ((Boolean) get(ALLOW_FILE_ATTACHMENTS)).booleanValue();
}
public boolean allowImageUploads() {
return ((Boolean) get(ALLOW_IMAGE_UPLOADS)).booleanValue();
}
public boolean autoSubscribeThreadStarter() {
return ((Boolean) get(AUTOSUBSCRIBE_THREAD_STARTER)).booleanValue();
}
public boolean noCategoryPostsAllowed() {
return ((Boolean) get(NO_CATEGORY_POSTS)).booleanValue();
}
public boolean anonymousPostsAllowed() {
return ((Boolean) get(ANONYMOUS_POSTS)).booleanValue();
}
public void setAllowFileAttachments(boolean allow) {
set(ALLOW_FILE_ATTACHMENTS, new Boolean(allow));
}
public void setAllowImageUploads(boolean allow) {
set(ALLOW_IMAGE_UPLOADS, new Boolean(allow));
}
public void setAutoSubscribeThreadCreator(boolean subscribe) {
set(AUTOSUBSCRIBE_THREAD_STARTER, new Boolean(subscribe));
}
public void setNoCategoryPostsAllowed(boolean allow) {
set(NO_CATEGORY_POSTS, new Boolean(allow));
}
public void setAnonymousPostsAllowed(boolean allow) {
set(ANONYMOUS_POSTS, new Boolean(allow));
}
public void setTitle (String title) {
String oldTitle = getTitle();
super.setTitle(title);
if (!oldTitle.equals(title)) {
// 1. rename permission groups
getAdminGroup().setName(title + " Administrators");
getModerationGroup().setName(title + " Moderators");
getThreadCreateGroup().setName(title + " Thread Creators");
getThreadResponderGroup().setName(title + " Thread Responders");
getReadGroup().setName(title + " Readers");
DataCollection subscriptions = getSubscriptions();
while (subscriptions.next()) {
ForumSubscription subscription = (ForumSubscription)DomainObjectFactory.newInstance(subscriptions.getDataObject());
subscription.getGroup().setName(subscription.getGroupName(this));
}
ThreadCollection threads = getThreads();
while (threads.next()) {
ThreadSubscription threadSub = ThreadSubscription.getThreadSubscription(threads.getMessageThread());
threadSub.getGroup().setName(threadSub.getSubscriptionGroupName(this));
}
}
}
}

View File

@ -18,33 +18,34 @@
*/
package com.arsdigita.forum;
import com.arsdigita.runtime.AbstractConfig;
import com.arsdigita.util.parameter.BooleanParameter;
import com.arsdigita.util.parameter.Parameter;
import com.arsdigita.util.parameter.URLParameter;
import com.arsdigita.util.parameter.StringParameter;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.kernel.UserCollection;
import com.arsdigita.kernel.User;
import com.arsdigita.web.Web;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.log4j.Logger;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.UserCollection;
import com.arsdigita.runtime.AbstractConfig;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.util.parameter.BooleanParameter;
import com.arsdigita.util.parameter.IntegerParameter;
import com.arsdigita.util.parameter.Parameter;
import com.arsdigita.util.parameter.StringParameter;
import com.arsdigita.util.parameter.URLParameter;
import com.arsdigita.web.Web;
/**
* A set of configuration parameters for forums.
*
* @author Justin Ross &lt;jross@redhat.com&gt;
* @version $Id: ForumConfig.java 1377 2006-11-17 10:39:41Z sskracic $
* @version $Id: ForumConfig.java 1628 2007-09-17 08:10:40Z chrisg23 $
*/
public class ForumConfig extends AbstractConfig {
public final static String versionId =
"$Id: ForumConfig.java 1377 2006-11-17 10:39:41Z sskracic $" +
"$Author: sskracic $" +
"$Id: ForumConfig.java 1628 2007-09-17 08:10:40Z chrisg23 $" +
"$Author: chrisg23 $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(ForumConfig.class);
@ -53,7 +54,18 @@ public class ForumConfig extends AbstractConfig {
private Parameter m_authorEditPosts;
private Parameter m_digestUserEmail;
private Parameter m_replyHostName;
private Parameter m_disablePageCaching;
private Parameter m_adminOnlyCreateTopics;
private Parameter m_maxImageSize;
private Parameter m_maxFileSize;
private final Parameter m_adapters;
private Parameter m_showAllThreadAlerts;
private Parameter m_showNewTabs;
private Parameter m_useWysiwygEditor;
private Parameter m_rejectionMessage;
private Parameter m_threadPageSize;
private Parameter m_quickFinish;
private Parameter m_deleteSentSubscriptionNotifications;
public ForumConfig() {
m_adminEditPosts = new BooleanParameter(
@ -72,6 +84,49 @@ public class ForumConfig extends AbstractConfig {
"com.arsdigita.forum.digest_user_email",
Parameter.OPTIONAL,
null);
m_disablePageCaching = new BooleanParameter (
"com.arsdigita.forum.disable_page_caching",
Parameter.REQUIRED,
Boolean.FALSE);
m_adminOnlyCreateTopics = new BooleanParameter (
"com.arsdigita.forum.admin_only_to_create_topics",
Parameter.REQUIRED,
Boolean.FALSE);
m_maxImageSize = new IntegerParameter (
"com.arsdigita.forum.maximum_image_size",
Parameter.OPTIONAL, null);
m_maxFileSize = new IntegerParameter (
"com.arsdigita.forum.maximum_file_size",
Parameter.OPTIONAL, null);
m_showNewTabs = new BooleanParameter(
"com.arsdigita.forum.show_new_tabs",
Parameter.OPTIONAL,
Boolean.FALSE);
m_showAllThreadAlerts = new BooleanParameter(
"com.arsdigita.forum.show_all_forum_thread_alerts",
Parameter.OPTIONAL,
Boolean.TRUE);
m_useWysiwygEditor = new BooleanParameter(
"com.arsdigita.forum.use_wysiwyg_editor",
Parameter.OPTIONAL,
Boolean.FALSE);
m_rejectionMessage = new StringParameter(
"com.arsdigita.forum.rejection_form_message.example",
Parameter.OPTIONAL,
null);
m_threadPageSize = new IntegerParameter (
"com.arsdigita.forum.thread_page_size",
Parameter.REQUIRED, new Integer(10));
m_quickFinish = new BooleanParameter(
"com.arsdigita.forum.allow_quick_finish",
Parameter.OPTIONAL,
Boolean.FALSE);
m_deleteSentSubscriptionNotifications = new BooleanParameter(
"com.arsdigita.forum.delete_sent_subscription_notifications",
Parameter.OPTIONAL,
Boolean.FALSE);
try {
m_adapters = new URLParameter
@ -88,6 +143,17 @@ public class ForumConfig extends AbstractConfig {
register(m_authorEditPosts);
register(m_replyHostName);
register(m_adapters);
register(m_disablePageCaching);
register(m_adminOnlyCreateTopics);
register(m_maxImageSize);
register(m_maxFileSize);
register(m_showAllThreadAlerts);
register(m_showNewTabs);
register(m_useWysiwygEditor);
register(m_rejectionMessage);
register(m_threadPageSize);
register(m_quickFinish);
register(m_deleteSentSubscriptionNotifications);
loadInfo();
}
@ -123,6 +189,28 @@ public class ForumConfig extends AbstractConfig {
return hostName;
}
/**
* Supports prevention of client and middleware caching -
* use in situations where users with different
* permissions share machines
* @return
*/
public boolean disableClientPageCaching () {
return ((Boolean)get(m_disablePageCaching)).booleanValue();
}
/**
* if true, disables topic tab for non admin users. Topic
* tab does not access control topic creation, so set this
* to true to maintain control of the topics on the forum.
*
*
*
* @return
*/
public boolean topicCreationByAdminOnly () {
return ((Boolean)get(m_adminOnlyCreateTopics)).booleanValue();
}
public User getDigestUser() {
String email = getDigestUserEmail();
@ -138,4 +226,85 @@ public class ForumConfig extends AbstractConfig {
users.close();
return user;
}
/**
* returns the maximum allowed size (in bytes) of
* image files attached to posts. Any larger
* files are rejected by UI validation
* @return
*/
public long getMaxImageSize() {
Integer size = (Integer)get(m_maxImageSize);
long longSize = Long.MAX_VALUE;
if (size != null) {
longSize = size.longValue();
}
return longSize;
}
/**
* returns the maximum allowed size (in bytes) of
* files attached to posts. Any larger
* files are rejected by UI validation
* @return
*/
public long getMaxFileSize() {
Integer size = (Integer)get(m_maxFileSize);
long longSize = Long.MAX_VALUE;
if (size != null) {
longSize = size.longValue();
}
return longSize;
}
/**
* if true, alerts tab displays thread alerts for this and all other
* forums. If false, only display thread subscriptions for current
* forum.
* @return
*/
/*
* If true, the thread alert page lists thread alerts from
* all forums - alerts not from the current forum have
* links to the thread displayed within the context of
* the current forum. Looks weird and needs to be sorted out
* presumably the correct forum needs to be set in the ForumContext
* when a link is selected
*/
public boolean showThreadAlertsForAllForums () {
return ((Boolean)get(m_showAllThreadAlerts)).booleanValue();
}
/**
* if true, displays setup and permissions tabs
* @return
*/
public boolean showNewTabs () {
return ((Boolean)get(m_showNewTabs)).booleanValue();
}
public boolean useWysiwygEditor () {
return ((Boolean)get(m_useWysiwygEditor)).booleanValue();
}
/**
* message added to the bottom of the moderation reection email.
* May give details about what the poster can do if not happy
* with rejection
* @return
*/
public String getRejectionMessage () {
return (String)get(m_rejectionMessage);
}
public int getThreadPageSize () {
return ((Integer)get(m_threadPageSize)).intValue();
}
public boolean quickFinishAllowed () {
return ((Boolean)get(m_quickFinish)).booleanValue();
}
public boolean deleteNotifications () {
return ((Boolean)get(m_deleteSentSubscriptionNotifications)).booleanValue();
}
}

View File

@ -18,8 +18,62 @@ com.arsdigita.forum.admin_can_edit_posts.purpose=Whether administrators can edit
com.arsdigita.forum.admin_can_edit_posts.format=[boolean]
com.arsdigita.forum.admin_can_edit_posts.example=true|false
com.arsdigita.forum.admin_only_to_create_topics.title=Prevent other users creating new topics
com.arsdigita.forum.admin_only_to_create_topics.purpose=Whether only admin can create (true) topics or all users (false)
com.arsdigita.forum.admin_only_to_create_topics.format=[boolean]
com.arsdigita.forum.admin_only_to_create_topics.example=true|false
com.arsdigita.forum.author_can_edit_posts.title=Authors can edit posts
com.arsdigita.forum.author_can_edit_posts.purpose=Whether authors can edijt their posts
com.arsdigita.forum.author_can_edit_posts.format=[boolean]
com.arsdigita.forum.author_can_edit_posts.example=true|false
com.arsdigita.forum.disable_page_caching.title=Disable client & middleware page caching
com.arsdigita.forum.disable_page_caching.purpose=Disable caching, particularly for situations where users share PCs
com.arsdigita.forum.disable_page_caching.format=[boolean]
com.arsdigita.forum.disable_page_caching.example=true|false
com.arsdigita.forum.maximum_image_size.title=Maximum size of image uploads in bytes
com.arsdigita.forum.maximum_image_size.purpose=Prevent huge images from being uploaded
com.arsdigita.forum.maximum_image_size.format=[integer]
com.arsdigita.forum.maximum_image_size.example=1048576
com.arsdigita.forum.maximum_file_size.title=Maximum size of file uploads in bytes
com.arsdigita.forum.maximum_file_size.purpose=prevent huge files from being uploaded
com.arsdigita.forum.maximum_file_size.format=[integer]
com.arsdigita.forum.maximum_file_size.example=1048576
com.arsdigita.forum.show_all_forum_thread_alerts.title=Display thread alerts from all forums
com.arsdigita.forum.show_all_forum_thread_alerts.purpose=decide whether to display thread alerts for this forum only or all alerts for user
com.arsdigita.forum.show_all_forum_thread_alerts.format=[boolean]
com.arsdigita.forum.show_all_forum_thread_alerts.example=true|false
com.arsdigita.forum.show_new_tabs.title=Display permissions and setup tabs
com.arsdigita.forum.show_new_tabs.purpose=to allow forum users to completely hide new functionality
com.arsdigita.forum.show_new_tabs.format=[boolean]
com.arsdigita.forum.show_new_tabs.example=true|false
com.arsdigita.forum.use_wysiwyg_editor.title=Use HTML Editor, or stick with legacy version
com.arsdigita.forum.use_wysiwyg_editor.purpose=to allow legacy implementations to stick with existing stylesheets
com.arsdigita.forum.use_wysiwyg_editor.format=[boolean]
com.arsdigita.forum.use_wysiwyg_editor.example=true|false
com.arsdigita.forum.rejection_form_message.title=Instructions to poster added to bottom of email
com.arsdigita.forum.rejection_form_message.purpose=tail of email that may contain site specific information - who to contact etc
com.arsdigita.forum.rejection_form_message.format=[string]
com.arsdigita.forum.rejection_form_message.example=Please do not reply to this email. If you have any comments, please email webmaster@example.com
com.arsdigita.forum.thread_page_size.title=Number of threads displayed per page
com.arsdigita.forum.thread_page_size.purpose=balance page loading time against number of key preses
com.arsdigita.forum.thread_page_size.format=[integer]
com.arsdigita.forum.thread_page_size.example=10
com.arsdigita.forum.allow_quick_finish.title=Allow quick finish on posts
com.arsdigita.forum.allow_quick_finish.purpose=To give users the option of omitting post steps if they have a quick text post
com.arsdigita.forum.allow_quick_finish.format=[boolean]
com.arsdigita.forum.allow_quick_finish.example=true|false
com.arsdigita.forum.delete_sent_subscription_notifications.title=Delete Sent Subscription Notifications
com.arsdigita.forum.delete_sent_subscription_notifications.purpose=Enable deletion of successfully sent notifications to save space in DB
com.arsdigita.forum.delete_sent_subscription_notifications.format=[boolean]
com.arsdigita.forum.delete_sent_subscription_notifications.example=true|false

View File

@ -27,8 +27,11 @@ import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.util.Assert;
import java.io.IOException;
import java.math.BigDecimal;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
/**
@ -103,6 +106,15 @@ public final class ForumContext {
// thread.assertPrivilege(PrivilegeDescriptor.READ);
m_thread = thread;
} catch (DataObjectNotFoundException ex) {
PageState state = PageState.getPageState();
if (state != null) {
try {
state.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (IOException e) {
s_log.warn("Thread not found, but failed to send a response to user");
}
}
throw new UncheckedWrapperException(
"Couldn't find a MessageThread for " + threadID, ex);
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageFactory;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.event.RequestEvent;
import com.arsdigita.bebop.event.RequestListener;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.forum.ui.ForumComponent;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.toolbox.ui.ApplicationAuthenticationListener;
import com.arsdigita.xml.Element;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Implementation of com.arsdigita.forum.PageBuilder that creates a basic forum page with read access check
*/
public class ForumPageBuilder implements PageBuilder, Constants {
public Page buildPage() {
Page page = PageFactory.buildPage(Constants.FORUM_XML_PREFIX, "Forum", "forumPage");
//Output the title in an easy to find place
page.add(new SimpleComponent(){
public void generateXML(PageState state, Element parent) {
Element nameElement = parent.newChildElement(Constants.FORUM_XML_PREFIX + ":name", Constants.FORUM_XML_NS);
nameElement.setText(ForumContext.getContext(state).getForum().getTitle());
Element introductionElement = parent.newChildElement(Constants.FORUM_XML_PREFIX + ":introduction", Constants.FORUM_XML_NS);
introductionElement.setText(ForumContext.getContext(state).getForum().getIntroduction());
}
});
ForumComponent forumComp = getForumComponent();
page.add(forumComp);
BigDecimalParameter topic = new BigDecimalParameter(TOPIC_PARAM);
page.addGlobalStateParam(topic);
page.addRequestListener(new ApplicationAuthenticationListener(PrivilegeDescriptor.READ));
page.addRequestListener(new ForumPageRequestListener(topic, forumComp));
return page;
}
protected ForumComponent getForumComponent() {
return new ForumComponent();
}
private static class ForumPageRequestListener implements RequestListener {
private BigDecimalParameter m_categorySelection;
private ForumComponent m_forumComp;
public ForumPageRequestListener(BigDecimalParameter topicSelection, ForumComponent forumComp) {
m_categorySelection = topicSelection;
m_forumComp = forumComp;
}
public void pageRequested(RequestEvent event) {
PageState state = event.getPageState();
ForumContext context = ForumContext.getContext(state);
context.setCategorySelection
((BigDecimal) event.getPageState().getValue(m_categorySelection));
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.util.Assert;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Factory class that enables projects to provide their own page creators.
* A reason for doing this is that a particular forum project may wish to
* include components on the page that introduce dependencies on other projects
*/
public class ForumPageFactory {
public static final String THREAD_PAGE = "thread";
public static final String FORUM_PAGE = "forum";
private static Map pageBuilders = new HashMap();
static {
// default pageBuilders are those provided with this project
pageBuilders.put(THREAD_PAGE, new ThreadPageBuilder());
pageBuilders.put(FORUM_PAGE, new ForumPageBuilder());
}
public static Page getPage(String pageType) {
Assert.truth(pageBuilders.containsKey(pageType), "Requested page type (" + pageType + ") does not have a builder registered" );
PageBuilder builder = (PageBuilder)pageBuilders.get(pageType);
Page page = builder.buildPage();
page.lock();
return page;
}
public static Iterator getPages () {
return pageBuilders.keySet().iterator();
}
public static void registerPageBuilder (String pageType, PageBuilder builder) {
pageBuilders.put(pageType, builder);
}
}

View File

@ -18,36 +18,67 @@
*/
package com.arsdigita.forum;
import java.util.Iterator;
import java.util.Map;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.page.BebopApplicationServlet;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import javax.servlet.ServletException;
import org.apache.log4j.Logger;
/**
* @author Justin Ross &lt;<a href="mailto:jross@redhat.com">jross@redhat.com</a>&gt;
* @version $Id: ForumServlet.java 287 2005-02-22 00:29:02Z sskracic $
* @version $Id: ForumServlet.java 1628 2007-09-17 08:10:40Z chrisg23 $
*
* Updated chris.gilbert@westsussex.gov.uk to make use of PageFactory and to enable
* disablement of client/middleware caching
*/
public class ForumServlet extends BebopApplicationServlet
implements Constants {
public static final String versionId =
"$Id: ForumServlet.java 287 2005-02-22 00:29:02Z sskracic $" +
"$Author: sskracic $" +
"$Id: ForumServlet.java 1628 2007-09-17 08:10:40Z chrisg23 $" +
"$Author: chrisg23 $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(ForumServlet.class);
public void init() throws ServletException {
super.init();
final Page forum = BboardDispatcher.buildForumPage
(new BigDecimalParameter(TOPIC_PARAM));
final Page thread = BboardDispatcher.buildThreadPage
(new BigDecimalParameter(THREAD_PARAM));
s_log.debug("creating forum page");
final Page forum = ForumPageFactory.getPage(ForumPageFactory.FORUM_PAGE);
s_log.debug("creating thread page");
final Page thread = ForumPageFactory.getPage(ForumPageFactory.THREAD_PAGE);
put("/", forum);
put("/index.jsp", forum);
put("/thread.jsp", thread);
if (Forum.getConfig().disableClientPageCaching()) {
s_log.debug("caching disabled");
disableClientCaching("/");
disableClientCaching("/index.jsp");
disableClientCaching("/thread.jsp");
}
// allow other pages to be added
// eg - allows categorised forum to add load-category page
// for AJAX category asignment
Iterator it = ForumPageFactory.getPages();
while (it.hasNext()) {
Object key = (Object)it.next();
if (!key.equals(ForumPageFactory.FORUM_PAGE) && !key.equals(ForumPageFactory.THREAD_PAGE)) {
put("/" + key, ForumPageFactory.getPage((String)key));
if (Forum.getConfig().disableClientPageCaching()) {
disableClientCaching("/" + key);
}
}
}
}
}

View File

@ -22,6 +22,7 @@ import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.mail.Mail;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
@ -40,7 +41,7 @@ import org.apache.log4j.Logger;
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
*
* @version $Revision: #8 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
* @version $Revision: 1.3 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
*/
public class ForumSubscription extends Subscription {
@ -89,6 +90,10 @@ public class ForumSubscription extends Subscription {
return BASE_DATA_OBJECT_TYPE;
}
public String getSubscriptionGroupName() {
return "Instant Alert Subscription Group";
}
public Forum getForum() {
if (m_forum == null) {
DataObject forumData = (DataObject) get(FORUM);
@ -106,6 +111,17 @@ public class ForumSubscription extends Subscription {
private void setForum(Forum forum) {
m_forum = forum;
setAssociation(FORUM, m_forum);
if (getGroup() != null) {
// in the case of moderation alert this is null - good, because
// mod group has already been placed in the group hierarchy
getGroup().setName(getGroupName(forum));
forum.getGroup().addSubgroup(getGroup());
}
}
public String getGroupName(Forum forum) {
return forum.getTitle() + " " + getSubscriptionGroupName();
}
protected void afterSave() {
@ -120,15 +136,18 @@ public class ForumSubscription extends Subscription {
public String getSignature(ThreadedMessage post) {
StringBuffer sb = new StringBuffer();
// XXX
/*
sb.append(SEPARATOR);
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
sb.append(HTML_SEPARATOR);
sb.append(getReturnURLMessage((Post)post));
sb.append(HTML_SEPARATOR);
sb.append(ALERT_BLURB);
sb.append("You are receiving this email because you subscribed to ");
sb.append("alerts on this forum.\n\n");
sb.append(REPLY_BLURB);
*/
sb.append("alerts on this forum. To unsubscribe, follow the link above, return to the thread list and change the settings under the alerts tab.\n");
sb.append("</font>");
} else {
sb.append(getReturnURLMessage((Post)post));
}
return sb.toString();
}

View File

@ -18,55 +18,47 @@
*/
package com.arsdigita.forum;
import com.arsdigita.db.DbHelper;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.RequestLocal;
import com.arsdigita.db.DbHelper;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.domain.xml.TraversalHandler;
import com.arsdigita.forum.portlet.MyForumsPortlet;
import com.arsdigita.forum.portlet.RecentPostingsPortlet;
import com.arsdigita.forum.search.FileAttachmentMetadataProvider;
import com.arsdigita.forum.search.PostMetadataProvider;
import com.arsdigita.forum.ui.portlet.RecentPostingsPortletEditor;
import com.arsdigita.kernel.ACSObjectInstantiator;
import com.arsdigita.kernel.ResourceType;
import com.arsdigita.kernel.ResourceTypeConfig;
import com.arsdigita.kernel.URLService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.kernel.ui.ResourceConfigFormSection;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.pdl.ManifestSource;
import com.arsdigita.persistence.pdl.NameFilter;
import com.arsdigita.runtime.CompoundInitializer;
import com.arsdigita.runtime.RuntimeConfig;
import com.arsdigita.runtime.PDLInitializer;
import com.arsdigita.runtime.DomainInitEvent;
import com.arsdigita.xml.XML;
import com.arsdigita.kernel.URLFinder;
import com.arsdigita.kernel.URLService;
import com.arsdigita.kernel.NoValidURLException;
import com.arsdigita.kernel.ACSObjectInstantiator;
import com.arsdigita.kernel.ResourceTypeConfig;
import com.arsdigita.kernel.ResourceType;
import com.arsdigita.kernel.ui.ResourceConfigFormSection;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.web.Application;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.forum.portlet.RecentPostingsPortlet;
import com.arsdigita.forum.ui.portlet.RecentPostingsPortletEditor;
import com.arsdigita.runtime.LegacyInitEvent;
import com.arsdigita.runtime.PDLInitializer;
import com.arsdigita.runtime.RuntimeConfig;
import com.arsdigita.search.MetadataProviderRegistry;
import com.arsdigita.web.ui.ApplicationConfigFormSection;
import org.apache.log4j.Logger;
import com.arsdigita.xml.XML;
/**
* The forum initializer.
*
* @author Justin Ross &lt;jross@redhat.com&gt;
* @version $Id: Initializer.java 755 2005-09-02 13:42:47Z sskracic $
* @version $Id: Initializer.java 1628 2007-09-17 08:10:40Z chrisg23 $
*/
public class Initializer extends CompoundInitializer {
public final static String versionId =
"$Id: Initializer.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
"$Id: Initializer.java 1628 2007-09-17 08:10:40Z chrisg23 $"
+ "$Author: chrisg23 $"
+ "$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(Initializer.class);
@ -91,6 +83,29 @@ public class Initializer extends CompoundInitializer {
}
});
e.getFactory().registerInstantiator(
Post.BASE_DATA_OBJECT_TYPE,
new ACSObjectInstantiator() {
public DomainObject doNewInstance(DataObject dataObject) {
return new Post(dataObject);
}
});
e.getFactory().registerInstantiator(
PostFileAttachment.BASE_DATA_OBJECT_TYPE,
new ACSObjectInstantiator() {
protected DomainObject doNewInstance(DataObject dataObject) {
return new PostFileAttachment(dataObject);
}
});
e.getFactory().registerInstantiator(
PostImageAttachment.BASE_DATA_OBJECT_TYPE,
new ACSObjectInstantiator() {
protected DomainObject doNewInstance(DataObject dataObject) {
return new PostImageAttachment(dataObject);
}
});
e.getFactory().registerInstantiator(
"com.arsdigita.forum.Inbox",
new ACSObjectInstantiator() {
public DomainObject doNewInstance(DataObject dataObject) {
@ -107,6 +122,14 @@ public class Initializer extends CompoundInitializer {
});
e.getFactory().registerInstantiator(
MyForumsPortlet.BASE_DATA_OBJECT_TYPE,
new ACSObjectInstantiator() {
protected DomainObject doNewInstance(DataObject dataObject) {
return new MyForumsPortlet(dataObject);
}
});
e.getFactory().registerInstantiator(
ForumSubscription.BASE_DATA_OBJECT_TYPE,
new ACSObjectInstantiator() {
public DomainObject doNewInstance(DataObject dataObject) {
@ -128,42 +151,12 @@ public class Initializer extends CompoundInitializer {
XML.parse(Forum.getConfig().getTraversalAdapters(),
new TraversalHandler());
URLFinder messageFinder = new URLFinder() {
public String find(OID oid, String context)
throws NoValidURLException {
return find(oid);
}
public String find(OID oid) throws NoValidURLException {
DataObject dobj = SessionManager.getSession().retrieve(oid);
if (dobj == null) {
throw new NoValidURLException("No such data object " + oid);
}
Application app = Application.retrieveApplication(dobj);
if (app == null) {
throw new NoValidURLException
("Could not find application instance for " + dobj);
}
try {
ThreadedMessage message = new ThreadedMessage(oid);
String url = app.getPath() +
"/thread.jsp?threadID=" +
message.getThread().getID().toString();
return url;
} catch(DataObjectNotFoundException e) {
throw new NoValidURLException
("Could not find application instance for " + dobj);
}
}
};
URLService.registerFinder(
ThreadedMessage.BASE_DATA_OBJECT_TYPE, messageFinder);
ThreadedMessage.BASE_DATA_OBJECT_TYPE,
new PostFinder());
URLService.registerFinder(PostFileAttachment.BASE_DATA_OBJECT_TYPE, new PostFileAttachmentURLFinder());
new ResourceTypeConfig(RecentPostingsPortlet.BASE_DATA_OBJECT_TYPE) {
public ResourceConfigFormSection getCreateFormSection
@ -183,15 +176,20 @@ public class Initializer extends CompoundInitializer {
}
};
new ResourceTypeConfig(Forum.BASE_DATA_OBJECT_TYPE) {
// chris.gilbert@westsussex.gov.uk use new constructor that allows create form to be hidden from users other than those
// with admin rights on parent app. Particularly appropriate for portlet where users
// customising their own homepage should NOT be allowed to create new forums
new ResourceTypeConfig(Forum.BASE_DATA_OBJECT_TYPE, PrivilegeDescriptor.ADMIN, PrivilegeDescriptor.READ) {
public ResourceConfigFormSection getCreateFormSection
(final ResourceType resType, final RequestLocal parentAppRL) {
(final ResourceType resType,
final RequestLocal parentAppRL) {
final ResourceConfigFormSection config =
new ApplicationConfigFormSection(resType, parentAppRL);
new ApplicationConfigFormSection(resType, parentAppRL, true);
return config;
}
public ResourceConfigFormSection getModifyFormSection
(final RequestLocal application) {
final ResourceConfigFormSection config =
@ -201,5 +199,20 @@ public class Initializer extends CompoundInitializer {
}
};
MetadataProviderRegistry.registerAdapter(Post.BASE_DATA_OBJECT_TYPE, new PostMetadataProvider());
MetadataProviderRegistry.registerAdapter(PostFileAttachment.BASE_DATA_OBJECT_TYPE, new FileAttachmentMetadataProvider());
}
public void init(LegacyInitEvent e) {
super.init(e);
if (RuntimeConfig.getConfig().runBackGroundTasks()) {
RemoveUnattachedAssetsScheduler.startTimer();
}
}
}

View File

@ -22,8 +22,10 @@ import com.arsdigita.loader.PackageLoader;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.EmailAddress;
import com.arsdigita.kernel.UserCollection;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.persistence.SessionManager;
@ -33,6 +35,7 @@ import com.arsdigita.web.ApplicationType;
import com.arsdigita.portal.apportlet.AppPortletType;
import com.arsdigita.portal.PortletType;
import com.arsdigita.forum.portlet.MyForumsPortlet;
import com.arsdigita.forum.portlet.RecentPostingsPortlet;
import org.apache.log4j.Logger;
@ -42,12 +45,12 @@ import org.apache.log4j.Logger;
* Loader.
*
* @author Justin Ross &lt;jross@redhat.com&gt;
* @version $Id: Loader.java 755 2005-09-02 13:42:47Z sskracic $
* @version $Id: Loader.java 1628 2007-09-17 08:10:40Z chrisg23 $
*/
public class Loader extends PackageLoader {
public final static String versionId =
"$Id: Loader.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$Id: Loader.java 1628 2007-09-17 08:10:40Z chrisg23 $" +
"$Author: chrisg23 $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(Loader.class);
@ -60,6 +63,7 @@ public class Loader extends PackageLoader {
setupForumAppType();
//setupInboxAppType();
setupRecentPostingsPortletType();
setupMyForumsPortletType();
setupDigestUser();
SessionManager.getSession().flushAll();
}
@ -97,6 +101,17 @@ public class Loader extends PackageLoader {
return type;
}
public static PortletType setupMyForumsPortletType() {
PortletType type = PortletType
.createPortletType("My Forums",
PortletType.WIDE_PROFILE,
MyForumsPortlet.BASE_DATA_OBJECT_TYPE);
type.setDescription("Lists forums that user has access to, with last posting date");
return type;
}
private static void setupDigestUser() {
s_log.debug("Setting up the digest user");
@ -104,20 +119,36 @@ public class Loader extends PackageLoader {
// specified in the configuration file.
String email = Forum.getConfig().getDigestUserEmail();
UserCollection users = User.retrieveAll();
users.addEqualsFilter("primaryEmail", email);
if (users.next()) {
s_log.debug("user exists");
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("Creating a user with the email " + email);
}
User user = new User();
user.setPrimaryEmail(new EmailAddress(email));
user.getPersonName().setGivenName("Forum");
user.getPersonName().setFamilyName("Digest Sender");
users.close();
}
}
public static void setupPrivileges() {
PrivilegeDescriptor.createPrivilege(
Forum.FORUM_MODERATION_PRIVILEGE);
PrivilegeDescriptor.createPrivilege(
Forum.CREATE_THREAD_PRIVILEGE);
PrivilegeDescriptor.createPrivilege(
Forum.RESPOND_TO_THREAD_PRIVILEGE);
PrivilegeDescriptor.addChildPrivilege(Forum.FORUM_MODERATION_PRIVILEGE, Forum.CREATE_THREAD_PRIVILEGE);
PrivilegeDescriptor.addChildPrivilege(Forum.CREATE_THREAD_PRIVILEGE, Forum.RESPOND_TO_THREAD_PRIVILEGE);
PrivilegeDescriptor.addChildPrivilege(Forum.RESPOND_TO_THREAD_PRIVILEGE, PrivilegeDescriptor.READ.getName());
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.parameters.ParameterModel;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Interface for classes that create forum pages. To build a page different
* from the default, provide new implemention and register it with the
* com.arsdigita.forum.ForumPageFactory
*/
public interface PageBuilder {
public Page buildPage();
}

View File

@ -18,6 +18,11 @@
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.PageState;
import com.arsdigita.categorization.CategorizedObject;
import com.arsdigita.categorization.Category;
import com.arsdigita.categorization.CategoryCollection;
@ -26,6 +31,8 @@ import com.arsdigita.cms.lifecycle.LifecycleDefinition;
import com.arsdigita.cms.lifecycle.LifecycleService;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.ui.PostForm;
import com.arsdigita.kernel.ACSObject;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.kernel.Party;
@ -33,18 +40,14 @@ import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.notification.Notification;
import com.arsdigita.persistence.DataAssociation;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataOperation;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.PersistenceException;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
/**
* A Post represents a single posting to a discussion forum.
@ -105,7 +108,8 @@ import org.apache.log4j.Logger;
*
*</ul>
*</p>
*
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
* @author Nobuko Asakai (nasakai@redhat.com)
*/
@ -121,6 +125,16 @@ public class Post extends ThreadedMessage {
* message */
public static final String MODERATOR = "moderator";
/**
* 0..n association with PostImageAttachments
*/
public static final String IMAGE_ATTACHMENTS = "images";
/**
* 0..n association with PostFileAttachments
*/
public static final String FILE_ATTACHMENTS = "files";
/** The status strings */
public static final String PENDING = "pending";
public static final String APPROVED = "approved";
@ -134,6 +148,8 @@ public class Post extends ThreadedMessage {
private Party m_moderator;
// referred to afterSave method
private boolean m_wasNew;
/*
* The base DomainObject is Post which extends ThreadedMessage. In
@ -147,7 +163,7 @@ public class Post extends ThreadedMessage {
this(BASE_DATA_OBJECT_TYPE);
}
protected Post(String typeName) {
public Post(String typeName) {
super(typeName);
}
@ -155,52 +171,35 @@ public class Post extends ThreadedMessage {
super(oid);
}
public Post(BigDecimal id) {
this(new OID(BASE_DATA_OBJECT_TYPE, id));
}
public Post(DataObject obj) {
super(obj);
}
/**
* Creates a new Posting in a form. If the forum
* is moderated, then the post's status will be
* set to PENDING, otherwise it will be set to
* APPROVED
* Creates a new Posting in a forum. The post is
* not yet in a fit state to be saved as it needs
* it's status to be set, and the subject and message
*
* @param forum the owner forum
*/
public static Post create(Forum forum) {
return create(forum,
forum.isModerated() ? PENDING : APPROVED);
}
/**
* Creates a new Posting in a form. The approval
* status will be set as specified.
* @param forum the owner forum
* @param status the approval status
*/
public static Post create(Forum forum,
String status) {
Post post = new Post();
post.setup(forum, status);
post.setForum(forum);
return post;
}
protected void setup(Forum forum,
String status) {
setForum(forum);
setStatus(status);
}
protected String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
/**
* @deprecated use replyTo() instead
* overridden version of method in ThreadedMessage
* used to create a reply to an existing post
*/
public Post replyToPost() {
return (Post)replyTo();
}
public ThreadedMessage newInstance() {
return create(getForum());
}
@ -210,6 +209,7 @@ public class Post extends ThreadedMessage {
* before saving.
*/
protected void beforeSave() {
m_wasNew = isNew();
Forum forum = getForum();
Assert.exists(forum, Forum.class);
@ -222,14 +222,14 @@ public class Post extends ThreadedMessage {
super.beforeSave();
if (isNew()) {
if (forum.isNoticeboard() && forum.getExpireAfter() > 0) {
s_log.info("Creating expiration lifecycle for " + getOID());
setLifecycle(forum.getLifecycleDefinition());
}
}
}
/**
* set permission contexts for this post to the root post, and for the root
* post to the forum. Additionally create a lifecycle if required for a new
* root post
*/
protected void afterSave() {
super.afterSave();
Forum forum = getForum();
@ -238,22 +238,51 @@ public class Post extends ThreadedMessage {
s_log.info("Setting context for " + getOID() + " to " + root.getOID());
PermissionService.setContext(this, root);
s_log.info("Setting context for " + root.getOID() + " to " +
s_log.info(
"Setting context for " + root.getOID() + " to " +
forum.getOID());
PermissionService.setContext(root, forum);
// originally this was created in beforeSave, but this was when only noticeboard
// (reply disabled) forums could have a lifecycle. Now that all forums may
// have a lifecycle on root posts, the method needs to be here in order
// for persistence to work when users are replying to posts chris.gilbert@westsussex.gov.uk
if (m_wasNew) {
if (getRoot() == null && forum.getExpireAfter() > 0) {
s_log.info("Creating expiration lifecycle for " + getOID());
setLifecycle(forum.getLifecycleDefinition());
}
}
m_wasNew = false;
DataAssociationCursor files = getFiles();
// allow attached files to be returned in search results
// by setting their status as live
while (files.next()) {
PostFileAttachment file =
(PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
if (getStatus().equals(APPROVED)) {
file.setLive();
} else {
file.setDraft();
}
}
}
/**
* Sends out the notifications for any subscriptions to the forum
* or thread to which this message belongs. Only sends
* notificatios if the post is approved.
* notifications if the post is approved.
*/
public void sendNotifications() {
public void sendNotifications(final String context) {
KernelExcursion ex = new KernelExcursion() {
protected void excurse() {
setEffectiveParty(Kernel.getSystemParty());
doSendNotifications();
doSendNotifications(context);
}
};
ex.run();
@ -264,6 +293,9 @@ public class Post extends ThreadedMessage {
* on for the forum.
*/
public void sendModeratorAlerts() {
if (!getStatus().equals(APPROVED)) {
// don't send if pre-approved (ie posted by a moderator)
KernelExcursion ex = new KernelExcursion() {
protected void excurse() {
setEffectiveParty(Kernel.getSystemParty());
@ -271,17 +303,17 @@ public class Post extends ThreadedMessage {
}
};
ex.run();
} else {
s_log.debug("not sending moderator alerts because the post " +
"was pre-approved (created by an approver)");
}
}
private void doSendNotifications() {
if (s_log.isDebugEnabled()) {
private void doSendNotifications(String context) {
s_log.debug("sending user notifications");
}
Forum forum = getForum();
if (getStatus().equals(APPROVED)) {
if (s_log.isDebugEnabled()) {
s_log.debug("Sending forum level subsriptions");
}
DataCollection subscriptions = forum.getSubscriptions();
while (subscriptions.next()) {
@ -290,28 +322,30 @@ public class Post extends ThreadedMessage {
subscriptions.getDataObject());
s_log.debug("notification to " + subscription.getOID());
subscription.sendNotification(Post.this);
subscription.sendNotification(Post.this, Forum.getConfig().deleteNotifications());
}
if (s_log.isDebugEnabled()) {
s_log.debug("Sending thread level subsriptions");
}
if (context == null || !context.equals(PostForm.NEW_CONTEXT)) {
ThreadSubscription sub =
ThreadSubscription.getThreadSubscription(getThread());
if (sub != null ) {
sub.sendNotification(this);
if (sub == null) {
s_log.error(
"Got a null ThreadSubscription from "
+ "Post # "
+ getID());
} else {
s_log.error("Got a null ThreadSubscription from " +
"Post # " + getID());
sub.sendNotification(this, Forum.getConfig().deleteNotifications());
}
}
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("Not sending notifications because the " +
"message is not approved");
}
}
}
private void doSendModeratorAlerts() {
if (s_log.isDebugEnabled()) {
@ -327,19 +361,16 @@ public class Post extends ThreadedMessage {
while (alerts.next()) {
ModerationAlert alert
= (ModerationAlert)
DomainObjectFactory.newInstance(alerts.getDataObject());
if (s_log.isDebugEnabled()) {
DomainObjectFactory.newInstance(
alerts.getDataObject());
s_log.debug("Processing moderation alert " + alert.getOID());
}
alert.sendNotification(this);
alert.sendNotification(this, Forum.getConfig().deleteNotifications());
}
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("Not sending moderator alerts because the " +
"forum is not moderated");
}
}
}
/**
* Set the Forum that contains this post. Just a wrapper for the
@ -382,11 +413,32 @@ public class Post extends ThreadedMessage {
*/
public void clearCategories() {
DataOperation clearCategories = SessionManager.getSession()
.retrieveDataOperation("com.arsdigita.forum.clearCategories");
DataCollection categories =
SessionManager.getSession().retrieve(
Category.BASE_DATA_OBJECT_TYPE);
categories.addEqualsFilter(
Category.CHILD_OBJECTS + "." + ACSObject.ID,
getID());
while (categories.next()) {
Category cat =
(Category) DomainObjectFactory.newInstance(
categories.getDataObject());
cat.removeChild(this);
}
// above is slower than data operation implementation below,
// but data op caused problems in persistence. If edited post
// had topic unchanged, then attempt was made to assign topic
// category before data op had cleared existing. Hence exception
// - attempt to map object to same cat twice
/*
DataOperation clearCategories =
SessionManager.getSession().retrieveDataOperation(
"com.arsdigita.forum.clearCategories");
clearCategories.setParameter("postID", this.getID());
clearCategories.execute();
return;
return;*/
}
/**
@ -397,15 +449,41 @@ public class Post extends ThreadedMessage {
}
/**
* creates a ThreadSubscription, but only if this is a root
* creates a ThreadSubscription, and returns it but only if this is a root,
* else return null
* Note, you must save() the Post before calling this method.
*/
public void createThreadSubscription() {
public ThreadSubscription createThreadSubscription() {
ThreadSubscription sub = null;
if (getRoot() == null) {
ThreadSubscription sub = new ThreadSubscription();
sub = new ThreadSubscription();
sub.setThread(getThread());
sub.save();
}
return sub;
}
public ThreadSubscription getSubscription() {
MessageThread thread;
if (getRoot() != null) {
thread = getRootMsg().getThread();
} else {
thread = getThread();
}
DataCollection subscriptions =
SessionManager.getSession().retrieve(
ThreadSubscription.BASE_DATA_OBJECT_TYPE);
subscriptions.addEqualsFilter(
ThreadSubscription.THREAD,
thread.getID());
ThreadSubscription subscription = null;
while (subscriptions.next()) {
subscription =
(ThreadSubscription) DomainObjectFactory.newInstance(
subscriptions.getDataObject());
}
return subscription;
}
/**
@ -416,7 +494,10 @@ public class Post extends ThreadedMessage {
*/
public boolean canEdit(Party party) {
Party author = getFrom();
return (Forum.getConfig().canAuthorEditPosts()
// cg added - for anonymous posts, don't allow editing, else everyone could edit everyone else's posts
return (
!author.equals(Kernel.getPublicUser())
&& Forum.getConfig().canAuthorEditPosts()
&& author.equals(party))
|| getForum().canEdit(party);
}
@ -439,6 +520,38 @@ public class Post extends ThreadedMessage {
set(STATUS, status);
}
/**
* set the status of a new post according to the priviliges of
* the current user - used by UI when creating new post or reply
* @param state
*/
public void setStatus(PageState state) {
setStatus(state, null);
}
/**
* set the status of an edited post according to the privileges
* of the current user and the status of the post that is being
* edited - used by the edit post UI
* @param state
* @param previousStatus
*/
public void setStatus(PageState state, String previousStatus) {
ForumContext ctx = ForumContext.getContext(state);
Forum forum = ctx.getForum();
// set status of edited post
if (forum.isModerated() && !ctx.canModerate()) {
if (Post.APPROVED.equals(previousStatus)) {
setStatus(Post.REAPPROVE);
} else {
setStatus(Post.PENDING);
}
} else {
setStatus(Post.APPROVED);
}
}
public String getStatus() {
return (String)get(STATUS);
}
@ -458,24 +571,19 @@ public class Post extends ThreadedMessage {
return m_moderator;
}
// note that the replies to this post are deleted in beforeDelete() of
// ThreadedMessage (and hence beforeDelete is called recursively on their replies)
protected void beforeDelete() {
// First delete associated entries in nt_requests this entry and
// all of its replies (in case of a root message) have
List replies = new ArrayList();
List msgIdList = new ArrayList();
msgIdList.add(getID());
if (getRoot() == null) {
DataCollection msgs = SessionManager.getSession()
.retrieve(BASE_DATA_OBJECT_TYPE);
msgs.addEqualsFilter("root", getID());
while (msgs.next()) {
replies.add(msgs.getDataObject());
msgIdList.add( msgs.getDataObject().getOID().get("id"));
}
}
DataCollection requests = SessionManager.getSession()
.retrieve(Notification.BASE_DATA_OBJECT_TYPE);
requests.addFilter("messageID in :msgIdList").set("msgIdList", msgIdList);
s_log.debug("Post - before delete " + getID());
// threaded message recursively deletes children
super.beforeDelete();
// remove any nt_requests
DataCollection requests =
SessionManager.getSession().retrieve(
Notification.BASE_DATA_OBJECT_TYPE);
requests.addEqualsFilter(Notification.MESSAGE_ID, this.getID());
while (requests.next()) {
Notification no = new Notification(requests.getDataObject().getOID());
no.setMessageDelete(Boolean.FALSE);
@ -483,21 +591,23 @@ public class Post extends ThreadedMessage {
}
if (getRoot() == null) {
// This posting is the root of the thread. Make sure all postings
// in this thread are deleted before this very one. Also
// take care of thread subscription.
ThreadSubscription sub = ThreadSubscription.getThreadSubscription(this.getThread());
s_log.debug(
"Root post - get rid of thread subscription and thread");
// This posting is the root of the thread. Remove the thread subscription and thread
MessageThread thread = getThread();
ThreadSubscription sub =
ThreadSubscription.getThreadSubscription(thread);
if (sub != null) {
// if unconfirmed post, then threadsubscription has not been created
sub.delete();
MessageThread thread = MessageThread.getFromRootMessage(this);
thread.delete();
for (Iterator it = replies.iterator(); it.hasNext(); ) {
Post reply = new Post( (DataObject) it.next());
reply.delete();
}
if (thread != null) {
thread.delete();
}
super.beforeDelete();
}
}
// package access only
void setLifecycle(LifecycleDefinition life) {
@ -507,5 +617,91 @@ public class Post extends ThreadedMessage {
cycle.save();
}
public void addImage(PostImageAttachment image) {
DataAssociation images = (DataAssociation) get(Post.IMAGE_ATTACHMENTS);
image.addToAssociation(images);
long currentImageCount = images.getDataAssociationCursor().size();
image.setImageOrder((int) currentImageCount);
}
public void removeImage(PostImageAttachment image) {
DataAssociation images = (DataAssociation) get(Post.IMAGE_ATTACHMENTS);
image.removeFromAssociation(images);
renumberImages();
}
// image order for a new image is based on the count of existing
// images, hence necessary to fill in any gaps when images are deleted
private void renumberImages() {
int count = 1;
DataAssociationCursor images = getImages();
while (images.next()) {
PostImageAttachment image =
(PostImageAttachment) DomainObjectFactory.newInstance(
images.getDataObject());
image.setImageOrder(count);
count++;
}
}
public DataAssociationCursor getImages() {
DataAssociationCursor images =
((DataAssociation) get(Post.IMAGE_ATTACHMENTS))
.getDataAssociationCursor();
images.addOrder(PostImageAttachment.IMAGE_ORDER);
return images;
}
public void addFile(PostFileAttachment file) {
DataAssociation files = (DataAssociation) get(Post.FILE_ATTACHMENTS);
file.addToAssociation(files);
PermissionService.setContext(file, this);
long currentFileCount = files.getDataAssociationCursor().size();
file.setFileOrder((int) currentFileCount);
}
public void removeFile(PostFileAttachment file) {
DataAssociation files = (DataAssociation) get(Post.FILE_ATTACHMENTS);
file.removeFromAssociation(files);
renumberFiles();
}
// file order for a new file is based on the count of existing
// files, hence necessary to fill in any gaps when images are deleted
private void renumberFiles() {
int count = 1;
DataAssociationCursor files = getFiles();
while (files.next()) {
PostFileAttachment file =
(PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
file.setFileOrder(count);
count++;
}
}
public DataAssociationCursor getFiles() {
DataAssociationCursor files =
((DataAssociation) get(Post.FILE_ATTACHMENTS))
.getDataAssociationCursor();
files.addOrder(PostFileAttachment.FILE_ORDER);
return files;
}
/**
* used by thread to prevent counting unapproved posts in the
* reply count.
*
*/
// should really be static - revisit this - refer to MessageThread for use
protected void addReplyFilter(DataCollection replies) {
replies.addEqualsFilter(STATUS, APPROVED);
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import java.util.Calendar;
import org.apache.log4j.Logger;
import com.arsdigita.auditing.BasicAuditTrail;
import com.arsdigita.cms.FileAsset;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Domain object for a file attached to a post. Modelled as domain object
* rather than simple association with link attribute because cloning
* seems to struggle with link attributes and also this allows files
* to be indexed separately for searching
*/
public class PostFileAttachment extends FileAsset {
private static final Logger s_log = Logger.getLogger(PostFileAttachment.class);
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.forum.PostFileAttachment";
public static final String FILE_OWNER = "fileMessage";
public static final String FILE_ORDER = "fileOrder";
public PostFileAttachment() {
super(BASE_DATA_OBJECT_TYPE);
setVersion(DRAFT);
}
public PostFileAttachment(OID oid) {
super(oid);
}
public PostFileAttachment(BigDecimal id) {
this(new OID(BASE_DATA_OBJECT_TYPE, id));
}
public PostFileAttachment(DataObject obj) {
super(obj);
}
public PostFileAttachment(String type) {
super(type);
}
public String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
public void setFileOrder(Integer order) {
set(FILE_ORDER, order);
}
public void setFileOrder(int order) {
set(FILE_ORDER, new Integer(order));
}
/*
* forums don't have a full publishing cycle,
* so we won't apply a heavyweight publishing to attached files
* just switch version tag
*/
public void setLive () {
setVersion(LIVE);
}
/*
* prevent files from being returned in search results -
* used after save of post, so that for example if post is unapproved,
* files are removed from search results
*/
public void setDraft () {
setVersion(DRAFT);
}
public Post getOwner() {
return (Post)DomainObjectFactory.newInstance((DataObject)get(FILE_OWNER));
}
protected static void removeUnattachedFiles() {
s_log.debug("removing orphaned files created more than a day ago");
DataCollection files = SessionManager.getSession().retrieve(BASE_DATA_OBJECT_TYPE);
Calendar yesterday = Calendar.getInstance();
yesterday.add(Calendar.DATE, -1);
files.addFilter(files.getFilterFactory().lessThan(AUDITING + "." + BasicAuditTrail.CREATION_DATE,
yesterday.getTime(),
false));
files.addEqualsFilter(FILE_OWNER, null);
while (files.next()) {
s_log.debug("deleting one");
DomainObjectFactory.newInstance(files.getDataObject()).delete();
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import com.arsdigita.cms.ContentItem;
import com.arsdigita.cms.dispatcher.AssetURLFinder;
import com.arsdigita.kernel.NoValidURLException;
import com.arsdigita.kernel.URLFinder;
import com.arsdigita.kernel.URLService;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* A URLFinder for PostFileAttachments.
*/
public class PostFileAttachmentURLFinder implements URLFinder {
private static final AssetURLFinder s_assetFinder = new AssetURLFinder();
/**
*
* find URL for a file attachment by finding its post
*
* @param oid the OID of the file attachment
* @param content the context of the search (ie draft/live)
*/
public String find(OID oid, String context) throws NoValidURLException {
// a draft attachment is one where the post hasn't been saved yet
// the behaviour is the same as far as finding the url goes
return find(oid);
}
/**
*
* find URL for the context of a file attachment. Delegates to
* AssetURLFinder.
*
* @param oid the OID of the file attachment
*
*/
public String find(OID oid) throws NoValidURLException {
return s_assetFinder.find(oid);
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.kernel.NoValidURLException;
import com.arsdigita.kernel.URLFinder;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.web.Application;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.web.URL;
/**
* @author cgyg9330
* (copied from Matt Booth)
* A URLFinder for Posts
*/
public class PostFinder implements URLFinder {
public String find(OID oid, String context) throws NoValidURLException {
return find(oid);
}
public String find(OID oid) throws NoValidURLException {
DataObject dobj = SessionManager.getSession().retrieve(oid);
if (dobj == null) {
throw new NoValidURLException("No such data object " + oid);
}
Application app = Application.retrieveApplication(dobj);
if (app == null) {
throw new NoValidURLException("Could not find application instance for " + dobj);
}
try {
ThreadedMessage message = new ThreadedMessage(oid);
ParameterMap params = new ParameterMap();
params.setParameter("thread", message.getThread().getID().toString());
return URL.there("/" + app.getPath() + "/thread.jsp", params).toString();
} catch (DataObjectNotFoundException e) {
throw new NoValidURLException("Could not find application instance for " + dobj);
}
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import java.util.Calendar;
import org.apache.log4j.Logger;
import com.arsdigita.auditing.BasicAuditTrail;
import com.arsdigita.cms.ImageAsset;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Domain object for an image attached to a post. Modelled as domain object
* rather than simple association with link attribute because cloning
* seems to struggle with link attributes
*/
public class PostImageAttachment extends ImageAsset {
private static final Logger s_log = Logger.getLogger(PostImageAttachment.class);
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.forum.PostImageAttachment";
public static final String IMAGE_OWNER = "imageMessage";
public static final String IMAGE_ORDER = "imageOrder";
/**
* Default constructor. ;
**/
public PostImageAttachment() {
super(BASE_DATA_OBJECT_TYPE);
setVersion(DRAFT);
}
/**
* Constructor. The contained <code>DataObject</code> is retrieved
* from the persistent storage mechanism with an <code>OID</code>
* specified by <i>oid</i>.
*
* @param oid The <code>OID</code> for the retrieved
* <code>DataObject</code>.
**/
public PostImageAttachment(OID oid) {
super(oid);
}
/**
* Constructor. The contained <code>DataObject</code> is retrieved
* from the persistent storage mechanism with an <code>OID</code>
* specified by <i>id</i> and <code>ContentPage.BASE_DATA_OBJECT_TYPE</code>.
*
* @param id The <code>id</code> for the retrieved
* <code>DataObject</code>.
**/
public PostImageAttachment(BigDecimal id) {
this(new OID(BASE_DATA_OBJECT_TYPE, id));
}
public PostImageAttachment(DataObject obj) {
super(obj);
}
public PostImageAttachment(String type) {
super(type);
}
public String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
public void setImageOrder(Integer order) {
set(IMAGE_ORDER, order);
}
public void setImageOrder(int order) {
set(IMAGE_ORDER, new Integer(order));
}
public Post getOwner() {
return (Post)DomainObjectFactory.newInstance((DataObject)get(IMAGE_OWNER));
}
protected static void removeUnattachedImages() {
s_log.debug("removing orphaned images created more than a day ago");
DataCollection images = SessionManager.getSession().retrieve(BASE_DATA_OBJECT_TYPE);
Calendar yesterday = Calendar.getInstance();
yesterday.add(Calendar.DATE, -1);
images.addFilter(images.getFilterFactory().lessThan(AUDITING + "." + BasicAuditTrail.CREATION_DATE,
yesterday.getTime(),
false));
images.addEqualsFilter(IMAGE_OWNER, null);
while (images.next()) {
s_log.debug("deleting one");
DomainObjectFactory.newInstance(images.getDataObject()).delete();
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import org.apache.log4j.Logger;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.util.UncheckedWrapperException;
/**
* Scheduler thread to remove any files or images that are more than a day old
* and haven't been attached to a post.
* These posts arise if a user starts to create a post and closes their browser
* before completing the post. This
* process gets rid of them on a daily basis to keep them under control
*
* nb - We can't just delete all unattached assets, as some may be there because the user is
* halfway through making a new post
*
* @author Chris Gilbert(chris.gilbert@westsussex.gov.uk)
* @version $Revision: 1.1 $ $DateTime: 2004/08/17 23:15:09 $
*/
public class RemoveUnattachedAssetsScheduler {
public static final String versionId = "$Id: RemoveUnattachedAssetsScheduler.java,v 1.1 2006/07/13 10:19:28 cgyg9330 Exp $ by $Author: cgyg9330 $, $DateTime: 2004/08/17 23:15:09 $";
// For storing timer which runs a method periodically to fire
// begin and end events
private static Timer s_Timer;
// if process runs very very slowly, prevent duplicate attempt fro mrunning before the first one ends
private static boolean s_running = false;
private static Logger s_log =
Logger.getLogger( RemoveUnattachedAssetsScheduler.class);
/**
* startTimer - starts the timer
*/
public static synchronized void startTimer() {
if ( s_Timer != null ) {
return; // Timer already exists
}
// Timer triggers straight away, and then every 24 hours
// don't run timer as a daemon - if server stops, kill this process too, as no one will be creating new posts
s_Timer = new Timer(false);
s_Timer.scheduleAtFixedRate(new RemoveUnattachedAssetsTask(), 0, 1000 * 60 * 60 * 24);
}
/**
* run - Run the task
*/
public static synchronized void run() {
s_log.debug("Firing off scheduler");
Session ssn = SessionManager.getSession();
if ( !s_running ) {
new KernelExcursion() {
protected final void excurse() {
s_running = true;
try {
setEffectiveParty(Kernel.getSystemParty());
TransactionContext txn =SessionManager.getSession().getTransactionContext();
txn.beginTxn();
PostFileAttachment.removeUnattachedFiles();
PostImageAttachment.removeUnattachedImages();
txn.commitTxn();
} catch (Throwable t) {
s_log.error("Attempt to remove unconfirmed forum posts failed", t);
throw new UncheckedWrapperException(t);
} finally {
s_running = false;
}
}
}.run();
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import org.apache.log4j.Logger;
import java.util.TimerTask;
import com.arsdigita.util.UncheckedWrapperException;
/**
*
* @version $Revision: 1.1 $ $DateTime: 2004/08/17 23:15:09 $
**/
class RemoveUnattachedAssetsTask extends TimerTask {
public static final String versionId = "$Id: RemoveUnattachedAssetsTask.java,v 1.1 2006/07/13 10:19:28 cgyg9330 Exp $ by $Author: cgyg9330 $, $DateTime: 2004/08/17 23:15:09 $";
private static final Logger s_log = Logger.getLogger(RemoveUnattachedAssetsTask.class);
public void run() {
RemoveUnattachedAssetsScheduler.run();
}
}

View File

@ -18,16 +18,17 @@
*/
package com.arsdigita.forum;
import org.apache.log4j.Logger;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.web.URL;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.mail.Mail;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.notification.BaseSubscription;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.util.Assert;
import com.arsdigita.util.StringUtils;
import org.apache.log4j.Logger;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.web.URL;
/**
* The abstract Subscription class provides the ability for Users
@ -41,12 +42,13 @@ import org.apache.log4j.Logger;
*/
public abstract class Subscription extends BaseSubscription {
public static final String versionId =
"$Id: Subscription.java 287 2005-02-22 00:29:02Z sskracic $" +
"$Author: sskracic $" +
"$Id: Subscription.java 1628 2007-09-17 08:10:40Z chrisg23 $" +
"$Author: chrisg23 $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(Subscription.class);
protected static final String HTML_SEPARATOR = "<br><br>\n<hr>\n<br><br>\n";
public Subscription(String objectType) {
super(objectType);
}
@ -81,8 +83,17 @@ public abstract class Subscription extends BaseSubscription {
if (author == null) {
author = "Unknown";
}
StringBuffer sb = new StringBuffer();
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
sb.append("<font size='medium' face='Verdana, Arial, Helvetica, sans-serif'>");
sb.append("Forum : ");
sb.append(post.getForum().getDisplayName()).append("\n<br>");
sb.append("Subject : ");
sb.append(post.getSubject()).append("\n<br>");
sb.append("Posted by: ");
sb.append(author).append("\n<br><br>");
} else {
sb.append("Forum : ");
sb.append(post.getForum().getDisplayName()).append("\n");
sb.append("Subject : ");
@ -90,6 +101,9 @@ public abstract class Subscription extends BaseSubscription {
sb.append("Posted by: ");
sb.append(author).append("\n\n");
}
return sb.toString();
}
@ -98,8 +112,12 @@ public abstract class Subscription extends BaseSubscription {
* implementation returns a separator and a generic messages.
*/
public String getSignature(ThreadedMessage post) {
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
return HTML_SEPARATOR + ALERT_BLURB + "</font>";
} else {
return SEPARATOR + ALERT_BLURB;
}
}
/**
* @return an appropriate message to direct people back to the
@ -115,15 +133,13 @@ public abstract class Subscription extends BaseSubscription {
StringBuffer sb = new StringBuffer();
sb.append("To reply to this message, go to:\n");
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
sb.append("<br><a href='" + url.getURL() + "'>" + url.getURL() + "</a>");
} else {
sb.append(url.getURL());
}
return sb.toString();
}
/*
* @return an appropriate separator for the body and signature of a post.
*/
private static String getSeparator() {
return "\n\n" + StringUtils.repeat('-', 20) + "\n\n";
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageFactory;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.event.RequestEvent;
import com.arsdigita.bebop.event.RequestListener;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.forum.ui.ThreadComponent;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.toolbox.ui.ApplicationAuthenticationListener;
import com.arsdigita.xml.Element;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Implementation of com.arsdigita.forum.PageBuilder that creates a basic thread page with read access check
*/
public class ThreadPageBuilder implements PageBuilder, Constants {
/* (non-Javadoc)
* @see com.arsdigita.forum.PageBuilder#buildPage(com.arsdigita.bebop.parameters.ParameterModel)
*/
public Page buildPage() {
Page threadPage = PageFactory.buildPage(Constants.FORUM_XML_PREFIX, "Threads", "forumThreadPage");
//Output the title in an easy to find place
threadPage.add(new SimpleComponent(){
public void generateXML(PageState state, Element parent) {
Element nameElement = parent.newChildElement(Constants.FORUM_XML_PREFIX + ":name", Constants.FORUM_XML_NS);
nameElement.setText(ForumContext.getContext(state).getForum().getTitle());
Element introductionElement = parent.newChildElement(Constants.FORUM_XML_PREFIX + ":introduction", Constants.FORUM_XML_NS);
introductionElement.setText(ForumContext.getContext(state).getForum().getIntroduction());
}
});
threadPage.add(new ThreadComponent());
// Register the thread id parameter as a global state parameter.
BigDecimalParameter threadID = new BigDecimalParameter(THREAD_PARAM);
threadPage.addGlobalStateParam(threadID);
threadPage.addRequestListener(new ApplicationAuthenticationListener(PrivilegeDescriptor.READ));
threadPage.addRequestListener
(new ThreadPageRequestListener(threadID));
return threadPage;
}
private static class ThreadPageRequestListener implements RequestListener {
private BigDecimalParameter m_threadID;
public ThreadPageRequestListener(BigDecimalParameter threadID) {
m_threadID = threadID;
}
public void pageRequested(RequestEvent event) {
PageState state = event.getPageState();
ForumContext context = ForumContext.getContext(state);
context.setThreadID
((BigDecimal) event.getPageState().getValue(m_threadID));
}
}
}

View File

@ -18,25 +18,27 @@
*/
package com.arsdigita.forum;
import java.math.BigDecimal;
import com.arsdigita.bebop.PageState;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainCollection;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.GroupCollection;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.mail.Mail;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.notification.Notification;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.DataQueryDataCollectionAdapter;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import java.math.BigDecimal;
/**
* <font color="red">Experimental</font>
* Class for managing subscriptions to individual threads in a Forum.
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
@ -46,7 +48,7 @@ public class ThreadSubscription extends Subscription {
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.forum.ThreadSubscription";
private static final String THREAD = "thread";
public static final String THREAD = "thread";
private MessageThread m_thread = null;
@ -66,6 +68,29 @@ public class ThreadSubscription extends Subscription {
super(oid);
}
public String getSubscriptionGroupName() {
// not overridden because group should be based on
// thread root post name, but thread hasn't been set when
// this is called. Group name is updated in setThread method
return super.getSubscriptionGroupName();
}
public String getSubscriptionGroupName(Forum forum) {
return forum.getTitle() + ": " + getThreadReal().getRootMessage().getSubject() + " Subscription Group";
}
protected Group getParentGroup() {
GroupCollection forumGroups = ((Forum)Kernel.getContext().getResource()).getGroup().getSubgroups();
forumGroups.addEqualsFilter("name", Forum.THREAD_SUBSCRIPTION_GROUPS_NAME);
Group parent = null;
if (forumGroups.next()) {
parent = forumGroups.getGroup();
forumGroups.close();
}
return parent;
}
public ThreadSubscription(BigDecimal id)
throws DataObjectNotFoundException {
super(new OID(BASE_DATA_OBJECT_TYPE, id));
@ -107,6 +132,7 @@ public class ThreadSubscription extends Subscription {
public void setThread(MessageThread thread) {
m_thread = thread;
setAssociation(THREAD, thread);
getGroup().setName(getSubscriptionGroupName((Forum)Kernel.getContext().getResource()));
}
protected void afterSave() {
@ -158,30 +184,51 @@ public class ThreadSubscription extends Subscription {
return sub;
}
public static DomainCollection getSubsForUser(Party party) {
DataQuery subs = SessionManager.getSession()
.retrieveQuery("com.arsdigita.forum.getUserThreadSubscriptions");
public static DomainCollection getSubsForUser(Party party, PageState state) {
// chris.gilbert@westsussex.gov.uk replace query with standard filtering
DataCollection subscriptions = SessionManager.getSession().retrieve(BASE_DATA_OBJECT_TYPE);
subscriptions.addEqualsFilter("group.allMembers.id", party.getID());
subs.setParameter("userID", party.getID());
// currently specified in config, but could be selected from widget on screen
// ie show thread subscriptions for this forum/all forums
//
// Currently, if subscription for a different forum is selected, the thread is displayed within
// the context of this forum which is not good. I suspect there is a need to set the forum in the
// forum context when thread.jsp is reached. Have not implemented changes as we are only displaying
// subscriptions for current forum cg.
if (!Forum.getConfig().showThreadAlertsForAllForums()){
subscriptions.addEqualsFilter("thread.root.objectID", ForumContext.getContext(state).getForum().getID());
}
return new DomainCollection(subscriptions);
return new DomainCollection(new DataQueryDataCollectionAdapter(subs, "subscription")) {
public DomainObject getDomainObject() {
return new ThreadSubscription(m_dataCollection.getDataObject());
}
};
}
/**
* Returns a signature with information about replying to the
* message
*/
public String getSignature(Post post) {
public String getSignature(ThreadedMessage post) {
StringBuffer sb = new StringBuffer();
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
sb.append(HTML_SEPARATOR);
sb.append(getReturnURLMessage((Post)post));
sb.append(HTML_SEPARATOR);
sb.append(ALERT_BLURB);
sb.append("You are receiving this email because you subscribed to ");
sb.append("alerts on this thread. To unsubscribe, follow the link above and click the 'stop watching thread' link at the top of the page.\n");
sb.append("</font>");
} else {
sb.append(SEPARATOR);
sb.append(ALERT_BLURB);
sb.append("You are receiving this email because you subscribed to ");
sb.append("alerts on this thread.\n\n");
sb.append(REPLY_BLURB);
sb.append(getReturnURLMessage((Post)post));
}
return sb.toString();
return SEPARATOR
+ ALERT_BLURB
+ "You are receiving this email because you subscribed to "
+ "alerts on this thread.\n\n"
+ REPLY_BLURB
+ getReturnURLMessage(post);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.portlet;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ThreadCollection;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.forum.ui.ThreadList;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.bebop.portal.AbstractPortletRenderer;
import com.arsdigita.bebop.PageState;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.xml.Element;
import com.arsdigita.xml.formatters.DateFormatter;
import com.arsdigita.web.URL;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.portal.Portlet;
import com.arsdigita.portal.apportlet.AppPortlet;
/**
*
* @author chris.gilbert@westsussex.gov.uk
*
* portlet with no attributes that displays links to all forums that user has read access to
*
*/
public class MyForumsPortlet extends Portlet {
public static final String versionId = "$Id: MyForumsPortlet.java,v 1.4 2006/07/13 10:19:28 cgyg9330 Exp $ by $Author: cgyg9330 $, $DateTime: 2004/08/17 23:26:27 $";
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.forum.MyForumsPortlet";
protected String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
public MyForumsPortlet(DataObject dataObject) {
super(dataObject);
}
protected AbstractPortletRenderer doGetPortletRenderer() {
return new MyForumsPortletRenderer(this);
}
}
class MyForumsPortletRenderer
extends AbstractPortletRenderer
implements Constants {
private MyForumsPortlet m_portlet;
public MyForumsPortletRenderer(MyForumsPortlet
portlet) {
m_portlet = portlet;
}
protected void generateBodyXML(PageState pageState,
Element parent) {
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":myForumsPortlet",
FORUM_XML_NS);
Party party = Kernel.getContext().getParty();
if (party == null) {
party = Kernel.getPublicUser();
}
DataCollection forums = SessionManager.getSession().retrieve(Forum.BASE_DATA_OBJECT_TYPE);
forums.addOrder("lower(" + Forum.TITLE + ")");
PermissionService.filterObjects(forums, PrivilegeDescriptor.READ, party.getOID());
while (forums.next()) {
Forum forum = (Forum)DomainObjectFactory.newInstance(forums.getDataObject());
Element forumEl = content.newChildElement(FORUM_XML_PREFIX + ":forum", FORUM_XML_NS);
URL url = URL.there(forum, "/", null);
forumEl.addAttribute("url", url.toString());
forumEl.addAttribute("title", forum.getTitle());
// display last forum update info
ThreadCollection threads = forum.getThreads();
threads.addOrder(MessageThread.LAST_UPDATE);
if (threads.next()) {
MessageThread lastUpdatedThread = threads.getMessageThread();
forumEl.addAttribute("lastUpdated", new DateFormatter().format(lastUpdatedThread.getLatestUpdateDate()));
threads.close();
} else {
forumEl.addAttribute("lastUpdated", "");
}
}
}
}

View File

@ -36,7 +36,7 @@ import com.arsdigita.portal.apportlet.AppPortlet;
public class RecentPostingsPortlet extends AppPortlet {
public static final String versionId = "$Id: RecentPostingsPortlet.java 755 2005-09-02 13:42:47Z sskracic $ by $Author: sskracic $, $DateTime: 2004/08/17 23:26:27 $";
public static final String versionId = "$Id: RecentPostingsPortlet.java 1628 2007-09-17 08:10:40Z chrisg23 $ by $Author: chrisg23 $, $DateTime: 2004/08/17 23:26:27 $";
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.forum.RecentPostingsPortlet";
@ -93,6 +93,7 @@ class RecentPostingsPortletRenderer
Forum forum = (Forum)m_portlet.getParentApplication();
content.addAttribute("noticeboard", (new Boolean(forum.isNoticeboard())).toString());
content.addAttribute("forumName", forum.getTitle());
Party party = Kernel.getContext().getParty();

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.search;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.log4j.Logger;
import com.arsdigita.cms.search.AssetExtractor;
import com.arsdigita.cms.search.AssetMetadataProvider;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.PostFileAttachment;
import com.arsdigita.globalization.Globalization;
import com.arsdigita.globalization.SystemLocaleProvider;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.URLService;
import com.arsdigita.kernel.User;
import com.arsdigita.search.ContentProvider;
import com.arsdigita.search.ContentType;
import com.arsdigita.search.MetadataProvider;
import com.arsdigita.toolbox.util.GlobalizationUtil;
/**
* @author chris.gilbert@westsussex.gov.uk
*
*
*/
public class FileAttachmentMetadataProvider extends AssetMetadataProvider {
private static Logger s_log = Logger.getLogger(FileAttachmentMetadataProvider.class);
public String getSummary(DomainObject dobj) {
PostFileAttachment file = (PostFileAttachment)dobj;
s_log.debug("Getting Summary File " + file.getID());
Post owner = file.getOwner();
//s_log.debug("Owner " + (owner == null ? null : owner.getID()));
if (owner == null){
//until post is saved, the file doesn't have an owner
//file is saved as draft so result will not be returned at front end
return "File attached to forum post";
}
String url = URLService.locate(owner.getOID());
String fileDescription = file.getDescription();
StringBuffer summary = new StringBuffer();
if (fileDescription != null) {
summary.append(fileDescription + " - ");
}
summary.append("A file attached to <a href=\"" + url + "\">" +
owner.getSubject() + "</a> - a posting by " +
((User)owner.getFrom()).getName() + " to forum " + owner.getForum().getTitle());
return summary.toString();
}
public boolean isIndexable (DomainObject dobj) {
PostFileAttachment file = (PostFileAttachment)dobj;
Post owner = file.getOwner();
Post root = null;
s_log.debug("start:isIndexable");
boolean indexable = false;
if (owner != null) {
// post has been saved
s_log.debug("owner = " + owner);
BigDecimal rootId = owner.getRoot();
if (rootId != null) {
root = new Post(owner.getRoot());
// else this is a root post
}
indexable = owner.getStatus().equals(Post.APPROVED) && (root == null ? true : root.getStatus().equals(Post.APPROVED));
}
return indexable;
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.search;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.log4j.Logger;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.forum.Post;
import com.arsdigita.globalization.Globalization;
import com.arsdigita.globalization.SystemLocaleProvider;
import com.arsdigita.kernel.Party;
import com.arsdigita.search.ContentProvider;
import com.arsdigita.search.ContentType;
import com.arsdigita.search.MetadataProvider;
import com.arsdigita.toolbox.util.GlobalizationUtil;
/**
* @author chris.gilbert@westsussex.gov.uk
*
*/
public class PostMetadataProvider implements MetadataProvider {
private static Logger s_log = Logger.getLogger(PostMetadataProvider.class);
/**
* No specific information is provided for a forum post
*/
public String getTypeSpecificInfo(DomainObject dobj) {
return null;
}
/**
* returns the default system Locale
*/
public Locale getLocale(DomainObject dobj) {
return new SystemLocaleProvider().getLocale();
}
/**
* returns the subject of the post
* @see com.arsdigita.search.MetadataProvider#getTitle(com.arsdigita.domain.DomainObject)
*/
public String getTitle(DomainObject dobj) {
Post post = (Post)dobj;
return post.getSubject();
}
/**
* returns the date of posting and the author
*/
public String getSummary(DomainObject dobj) {
Post post = (Post)dobj;
return "Posted to " + post.getForum().getTitle() + " by " +
post.getFrom().getName() + " on " +
DateFormat.getDateInstance(DateFormat.MEDIUM).format(post.getSentDate());
}
public Date getCreationDate(DomainObject dobj) {
Post post = (Post)dobj;
return post.getSentDate();
}
public Party getCreationParty(DomainObject dobj) {
Post post = (Post)dobj;
return post.getFrom();
}
public Date getLastModifiedDate(DomainObject dobj) {
//we are not storing the date if the post is edited
return null;
}
public Party getLastModifiedParty(DomainObject dobj) {
//we are not storing the person that edits a post
return null;
}
public ContentProvider[] getContent(DomainObject dobj, ContentType type) {
List content = new ArrayList();
if (type.equals(ContentType.XML)) {
content.add(new XMLContentProvider("xml", (Post)dobj));
} else if (type.equals(ContentType.TEXT)) {
content.add(new TextContentProvider("text", (Post)dobj));
}
// don't deal with raw content - attached files are indexed separately
return (ContentProvider[])content.toArray(
new ContentProvider[content.size()]);
}
public String getContentSection(DomainObject dobj) {
return null;
}
public boolean isIndexable(DomainObject dobj) {
Post post = (Post)dobj;
s_log.debug("Post saved - status is " + post.getStatus() + ". Index Object? " + post.getStatus().equals(Post.APPROVED));
return post.getStatus().equals(Post.APPROVED);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.search;
import com.arsdigita.domain.DomainObjectTextRenderer;
import com.arsdigita.forum.Post;
import com.arsdigita.search.ContentProvider;
import com.arsdigita.search.ContentType;
public class TextContentProvider implements ContentProvider {
private Post m_post;
private String m_context;
public TextContentProvider(String context, Post post) {
m_context = context;
m_post = post;
}
public String getContext() {
return m_context;
}
public ContentType getType() {
return ContentType.TEXT;
}
public byte[] getBytes() {
DomainObjectTextRenderer renderer = new DomainObjectTextRenderer();
renderer.walk(m_post, PostMetadataProvider.class.getName());
String text = renderer.getText();
// if required, retrieve file attachments here and add their content
return text.getBytes();
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.search;
import org.apache.log4j.Logger;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.search.ContentProvider;
import com.arsdigita.search.ContentType;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Document;
import com.arsdigita.xml.Element;
public class XMLContentProvider implements ContentProvider, Constants {
private static final Logger s_log = Logger
.getLogger(XMLContentProvider.class);
private Post m_post;
private String m_context;
public XMLContentProvider(String context, Post post) {
m_context = context;
m_post = post;
}
public String getContext() {
return m_context;
}
public ContentType getType() {
return ContentType.XML;
}
public byte[] getBytes() {
Element root = new Element("forum:post", FORUM_XML_NS);
DomainObjectXMLRenderer renderer =
new DomainObjectXMLRenderer(root);
renderer.setWrapAttributes(true);
renderer.walk(m_post, PostMetadataProvider.class.getName());
Document doc = null;
try {
doc = new Document(root);
} catch (javax.xml.parsers.ParserConfigurationException ex) {
throw new UncheckedWrapperException("Unable to create xml document for post " + m_post.getID(), ex);
}
if (s_log.isDebugEnabled()) {
s_log.debug("XML is " + doc.toString(true));
}
String xml = doc.toString(true);
return xml.getBytes();
}
}

View File

@ -0,0 +1,376 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.FormStep;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormValidationListener;
import com.arsdigita.bebop.event.ParameterEvent;
import com.arsdigita.bebop.form.FileUpload;
import com.arsdigita.bebop.form.Submit;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.parameters.FileSizeValidationListener;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringInRangeValidationListener;
import com.arsdigita.bebop.table.TableModel;
import com.arsdigita.bebop.table.TableModelBuilder;
import com.arsdigita.dispatcher.MultipartHttpServletRequest;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.PostFileAttachment;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
import com.arsdigita.xml.Element;
/**
* @author Chris Gilbert <a href="mailto:chris.gilbert@westsussex.gov.uk">chris.gilbert@westsussex.gov.uk</a>
*
* Wizard step to attach files to a post. This step may be bypassed
* if a forum administrator changes the forum settings on the UI
*
* nb - a simpler reuseable generic wizard file attachments step has been created
* forum will be refactored to use it in the future. see ccm-wsx-wizard-steps in contrib
*/
public class AttachedFilesStep
extends FormStep
implements Constants, FormInitListener, FormProcessListener, FormValidationListener {
private static Logger s_log = Logger.getLogger(AttachedFilesStep.class);
private ACSObjectSelectionModel m_post;
private AttachedFilesTable m_attachedFiles;
private FileUpload m_upload;
private TextArea m_description;
private Submit m_addFile;
private PostForm m_container;
private ArrayParameter m_newFiles = new ArrayParameter("newFiles");
private ArrayParameter m_existingFiles = new ArrayParameter("oldFiles");
public AttachedFilesStep(ACSObjectSelectionModel post, PostForm container) {
super("postFiles",
new SimpleContainer(
FORUM_XML_PREFIX + ":postFormFiles",
FORUM_XML_NS));
m_post = post;
m_container = container;
m_attachedFiles = new AttachedFilesTable(m_newFiles, m_existingFiles, this);
add(m_attachedFiles);
m_upload = new FileUpload("file", true);
m_upload.addValidationListener(new NotEmptyValidationListener(
Text
.gz("forum.ui.validation.file_null")) {
public void validate(ParameterEvent e) {
// don't fire validation if the next or previous button of the wizard has been pressed
if (m_addFile.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
// this validation can fire any time - if there is no file then it won't fail
m_upload.addValidationListener(
new FileSizeValidationListener(Forum.getConfig().getMaxFileSize()));
add(m_upload);
m_description = new TextArea("fileDescription");
m_description.setCols(20);
m_description.setRows(5);
m_description
.addValidationListener(new StringInRangeValidationListener(
0,
4000,
Text.gz("forum.ui.validation.file_description_too_long")) {
public void validate(ParameterEvent e)
throws FormProcessException {
// don't fire validation if the next or previous button of the wizard has been pressed
if (m_addFile.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
add(m_description);
m_addFile = new Submit("Add File");
add(m_addFile);
addInitListener(this);
addProcessListener(this);
addValidationListener(this);
}
/**
* Uploads the file and creates an orphaned PostFileAttachment
* that will be associated with the post when the wizard is completed
* or deleted if the wizard is cancelled.
*/
public void process(FormSectionEvent e) throws FormProcessException {
FormData data = e.getFormData();
PageState state = e.getPageState();
if (m_addFile.isSelected(state)) {
PostFileAttachment attachment;
s_log.debug("adding file");
try {
String fileName = (String) m_upload.getValue(state);
s_log.debug("filename is " + fileName);
File file =
(
(MultipartHttpServletRequest) e
.getPageState()
.getRequest())
.getFile(
"file");
s_log.debug("uploaded file is " + file.getName());
//MimeType mimeType = MimeType.guessMimeTypeFromFile(fileName);
//s_log.debug("mime type is " + mimeType.getMimeType());
attachment = new PostFileAttachment();
attachment.loadFromFile(
fileName,
file,
"application/octet-stream");
attachment.setDescription(
(String) m_description.getValue(state));
BigDecimal id = attachment.getID();
String[] current = (String[]) state.getValue(m_newFiles);
if (current == null) {
current = new String[] { id.toString()};
} else {
List files = Arrays.asList(current);
current = new String[files.size() + 1];
Iterator it = files.iterator();
int i = 0;
while (it.hasNext()) {
current[i++] = (String) it.next();
}
current[i] = id.toString();
}
state.setValue(m_newFiles, current);
s_log.debug("File Uploaded");
m_description.setValue(state, null);
} catch (Exception ex) {
throw new FormProcessException(ex);
}
}
}
/**
* Prevent users navigating away from this page unintentionally without
* adding an uploaded file first
*/
public void validate(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (!m_addFile.isSelected(state)
&& StringUtils.isNotBlank((String) m_upload.getValue(state))) {
throw new FormProcessException(
(String) Text
.gz("forum.ui.validation.file_not_uploaded")
.localize());
}
}
public void removeFile(BigDecimal id, PageState state) {
String[] existingArray = (String[]) state.getValue(m_existingFiles);
if (existingArray != null) {
List existing = Arrays.asList(existingArray);
if (!existing.contains(id)) {
// this has been added during edit (or this is a new post)
s_log.debug("new file - I will actually delete it");
PostFileAttachment attachment = new PostFileAttachment(id);
attachment.delete();
}
}
// m_new_files cannot contain null if request is to delete file
Assert.exists(state.getValue(m_newFiles));
List newFiles =
new ArrayList(Arrays.asList((String[]) state.getValue(m_newFiles)));
newFiles.remove(id);
String[] current = new String[newFiles.size()];
Iterator it = newFiles.iterator();
int i = 0;
while (it.hasNext()) {
current[i++] = (String) it.next();
}
s_log.debug("size of new files array = " + current.length);
state.setValue(m_newFiles, current);
}
public DataCollection getCurrentFiles(PageState state) {
String[] currentArray = (String[]) state.getValue(m_newFiles);
if (currentArray != null && currentArray.length != 0) {
List current = Arrays.asList((String[]) state.getValue(m_newFiles));
DataCollection files =
SessionManager.getSession().retrieve(
PostFileAttachment.BASE_DATA_OBJECT_TYPE);
files.addFilter("id in :files").set("files", current);
return files;
}
return null;
}
/**
* called at the end of the wizard when the form is processed
* set the files attached to the post according to the
* file attachment step
* @param post
* @param state
*/
public void attachFiles(Post post, PageState state) {
List newFiles = Collections.EMPTY_LIST;
String[] newFilesArray = (String[]) state.getValue(m_newFiles);
if (newFilesArray != null) {
newFiles = new ArrayList(Arrays.asList(newFilesArray));
}
String[] existing = (String[]) state.getValue(m_existingFiles);
if (existing != null) {
// we are editing a post - leave any unchanged files, delete any that have been
// removed
for (int i = 0; i < existing.length; i++) {
if (newFiles.contains(existing[i])) {
// no change to this one
newFiles.remove(existing[i]);
} else {
// file has been deleted in edit
PostFileAttachment file =
new PostFileAttachment(new BigDecimal(existing[i]));
post.removeFile(file);
// file deleted
}
}
}
Iterator it = newFiles.iterator();
while (it.hasNext()) {
// new files added
PostFileAttachment file =
new PostFileAttachment(new BigDecimal((String) it.next()));
post.addFile(file);
}
state.setValue(m_newFiles, null);
state.setValue(m_existingFiles, null);
}
public void register(Page p) {
super.register(p);
p.addGlobalStateParam(m_existingFiles);
p.addGlobalStateParam(m_newFiles);
}
public void init(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (m_container
.getContext(state)
.equals(PostForm.EDIT_CONTEXT)) {
Post editPost = (Post) m_post.getSelectedObject(state);
DataAssociationCursor files = editPost.getFiles();
if (files.size() > 0) {
String[] fileIDs =
new String[new Long(files.size()).intValue()];
int i = 0;
while (files.next()) {
PostFileAttachment file =
(PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
fileIDs[i++] = file.getID().toString();
}
state.setValue(m_existingFiles, fileIDs);
state.setValue(m_newFiles, fileIDs);
}
}
}
/**
* generate xml for the confirmation step (unfortunately we can't just
* traverse the post because it hasn't been created at this point)
* @param state
* @param p
*/
public void generatePostXML(PageState state, Element p) {
DataCollection files = getCurrentFiles(state);
if (files == null) {
return;
}
while(files.next()) {
PostFileAttachment file = (PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
DomainObjectXMLRenderer xr = new DomainObjectXMLRenderer(p.newChildElement("files"));
xr.setWrapRoot(false);
xr.setWrapAttributes(true);
xr.setWrapObjects(false);
xr.walk(file, ConfirmStep.class.getName());
}
}
protected void clearParameters(PageState state) {
state.setValue(m_existingFiles, null);
state.setValue(m_newFiles, null);
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.table.TableModel;
import com.arsdigita.cms.FileAsset;
import com.arsdigita.cms.dispatcher.Utilities;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.PostFileAttachment;
import com.arsdigita.forum.PostImageAttachment;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.messaging.MessagePart;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
/**
* Semantic table with delete link. This could be extended to provide
* additional links eg move up/move down. New generic wizard
* file attachment table does include this functionality
* so it will be introduced when forum is refactored
*
* @author Chris Gilbert <a href="mailto:chris.gilbert@westsussex.gov.uk">chris.gilbert@westsussex.gov.uk</a>
* @version $Revision: 1.4 $ $DateTime: 2004/08/17 23:15:09 $
**/
public class AttachedFilesTable extends SimpleComponent implements Constants {
// I have not implemented an edit link or a move up/down link although the data model
// is able to manage this, it seemed like a lot of work for a minor requirement for a forum
// currently, files are listed in the order they are added.
// to add extra links, define new keys for the control event and check key value in the
// respond method (see ThreadDisplay for example code)
//
// only issue is that when editing a post, the editor cannot change the description easily
// (though they could download the file to their PC, delete the existing one and recreate it)
private static Logger s_log = Logger.getLogger(AttachedFilesTable.class);
protected static final String ACTION_DELETE = "delete";
private ArrayParameter m_newFiles;
private ArrayParameter m_existingFiles;
private AttachedFilesStep m_parent;
public AttachedFilesTable(
ArrayParameter newFiles,
ArrayParameter existingFiles,
AttachedFilesStep parent) {
m_newFiles = newFiles;
m_existingFiles = existingFiles;
m_parent = parent;
}
public void respond(PageState state) throws ServletException {
super.respond(state);
String key = state.getControlEventName();
String value = state.getControlEventValue();
if (ACTION_DELETE.equals(key)) {
List existing = Collections.EMPTY_LIST;
String[] existingArray = (String[]) state.getValue(m_existingFiles);
if (existingArray != null) {
existing = Arrays.asList(existingArray);
}
if (!existing.contains(value)) {
// this has been added during edit
OID oid =
new OID(
PostFileAttachment.BASE_DATA_OBJECT_TYPE,
new BigDecimal(value));
DomainObjectFactory.newInstance(oid).delete();
}
List newFiles =
new ArrayList(
Arrays.asList((String[]) state.getValue(m_newFiles)));
newFiles.remove(value);
String[] current = new String[newFiles.size()];
Iterator it = newFiles.iterator();
int i = 0;
while (it.hasNext()) {
current[i++] = (String) it.next();
}
state.setValue(m_newFiles, current);
}
}
public void generateXML(PageState state, Element p) {
Element mainElement =
p.newChildElement(
FORUM_XML_PREFIX + ":attachedFiles",
FORUM_XML_NS);
DataCollection files = m_parent.getCurrentFiles(state);
if (files == null) {
return;
}
while (files.next()) {
PostFileAttachment file =
(PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
Element fileElement =
mainElement.newChildElement(
FORUM_XML_PREFIX + ":file",
FORUM_XML_NS);
fileElement.addAttribute("name", file.getName());
fileElement.addAttribute("url", Utilities.getAssetURL(file));
fileElement.addAttribute("description", file.getDescription());
generateActionXML(state, fileElement, file);
}
}
private void generateActionXML(
PageState state,
Element parent,
PostFileAttachment image) {
//separate method so that if future links require some logic
//to decide whether to display, it can be implemented here
parent.addAttribute("deleteLink", makeURL(state, ACTION_DELETE, image));
}
protected String makeURL(
PageState state,
String action,
PostFileAttachment file) {
state.setControlEvent(this, action, file.getID().toString());
String url = null;
try {
url = state.stateAsURL();
} catch (IOException ex) {
throw new UncheckedWrapperException("cannot create url", ex);
}
state.clearControlEvent();
return url;
}
}

View File

@ -21,11 +21,14 @@ package com.arsdigita.forum.ui;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormSubmissionListener;
import com.arsdigita.bebop.form.Submit;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.form.TextField;
@ -45,12 +48,12 @@ import org.apache.log4j.Logger;
* parent category. temporary hack for testing purposes
*
* @author <a href=mailto:sarah@arsdigita.com>Sarah Barwig</a>
* @version $Revision: #9 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
* @version $Revision: 1.2 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
*/
public class CategoryAddForm extends Form {
public static final String versionId =
"$Id: CategoryAddForm.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$Id: CategoryAddForm.java 1628 2007-09-17 08:10:40Z chrisg23 $" +
"$Author: chrisg23 $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger
@ -78,8 +81,24 @@ public class CategoryAddForm extends Form {
m_description.setWrap(TextArea.SOFT);
add(m_description);
Submit submit = new Submit("Create topic");
// Cancel button added cg
// Would have used a saveCancel section but this would make existing
// stylesheets for legacy forums miss the buttons
Submit submit = new Submit(Text.gz("forum.ui.topic.save"));
final Submit cancel = new Submit(Text.gz("forum.ui.cancel"));
add(submit);
add(cancel);
addSubmissionListener(new FormSubmissionListener(){
public void submitted(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (cancel.isSelected(state)){
fireCompletionEvent(state);
throw new FormProcessException("cancelled");
}
}
});
/*
* Listener to process form data. Just adds the categories, then

View File

@ -75,7 +75,7 @@ public class CategoryView extends SimpleContainer
Container categories = new SimpleContainer();
Container linksPanel = new SimpleContainer("forum:topicOptions",
Container linksPanel = new SimpleContainer(Constants.FORUM_XML_PREFIX + ":topicOptions",
Constants.FORUM_XML_NS);
m_addCategoryLink = new ToggleLink(new Label(Text.gz("forum.ui.newTopic")));
m_addCategoryLink.setClassAttr("actionLink");

View File

@ -40,25 +40,28 @@ import java.util.TooManyListenersException;
* @author ron@arsdigita.com
* @author sarah@arsdigita.com
*
* @version $Id: CategoryWidget.java 287 2005-02-22 00:29:02Z sskracic $
* @version $Id: CategoryWidget.java 1628 2007-09-17 08:10:40Z chrisg23 $
*/
public class CategoryWidget extends SingleSelect implements Constants {
public CategoryWidget(ParameterModel categoryParameter) {
super(categoryParameter);
addOption(new Option(TOPIC_NONE.toString(),
new Label(Text.gz("forum.ui.topic.none"))));
try {
addPrintListener(new PrintListener() {
public void prepare(PrintEvent e) {
PageState s = e.getPageState();
final Forum forum = getForum(s);
SingleSelect target = (SingleSelect) e.getTarget();
// Get categories for this forum
if (forum.noCategoryPostsAllowed()) {
target.addOption(new Option(TOPIC_NONE.toString(),
new Label(Text.gz("forum.ui.topic.none"))));
}
final Category root = forum.getRootCategory();
if (root != null) {
SingleSelect target = (SingleSelect) e.getTarget();
addCategories(root, target);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.util.Date;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.xml.Element;
/**
* @author Chris Gilbert <a href="mailto:chris.gilbert@westsussex.gov.uk">chris.gilbert@westsussex.gov.uk</a>
*
* Traverse the post to output all it's attributes and associations for previewing
* (or rather, pretend to - the post isn't persistent until confirm step is complete
* so it can't be traversed. Instead, each step adds some xml to represent the data
* it has gathered
*/
public class ConfirmStep extends SimpleContainer implements Constants {
private PostForm m_container;
private ACSObjectSelectionModel m_post;
public ConfirmStep(ACSObjectSelectionModel post, PostForm container) {
m_container = container;
m_post = post;
}
public void generateXML(PageState state, Element parent) {
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":postConfirm",FORUM_XML_NS);
Date sent;
Party sender;
if (m_container.getContext(state).equals(PostForm.EDIT_CONTEXT)) {
Post post = (Post)m_post.getSelectedObject(state);
sent = post.getSentDate();
sender = post.getFrom();
} else {
sent = new Date();
sender = Kernel.getContext().getParty();
if (sender == null) {
sender = Kernel.getPublicUser();
}
}
content.newChildElement(Post.SENT).setText(sent.toString());
content.newChildElement(Post.SENDER).newChildElement(User.DISPLAY_NAME).setText(sender.getDisplayName());
m_container.generatePostXML(state, content);
}
}

View File

@ -25,12 +25,13 @@ import java.math.BigDecimal;
* XML namespaces, URLs, URL variable names, etc.
*
* @author <a href="mailto:teadams@arsdigita.com">Tracy Adams</a>
* @version $Revision: #8 $ $Date: 2004/08/17 $
* @version $Revision: 1.3 $ $Date: 2006/03/08 15:38:33 $
* @since ACS 4.7
*/
public interface Constants {
static final String FORUM_XML_PREFIX = "forum";
static final String FORUM_XML_NS = "http://www.arsdigita.com/forum/1.0";
static final String FORUM_MODE_VIEW = "view";

View File

@ -37,7 +37,18 @@ import org.apache.log4j.Logger;
*
* @version $Revision #1 $DateTime: 2004/08/17 23:26:27 $
*/
public class EditPostForm extends PostForm {
public class EditPostForm { // extends PostForm {
/*
REMOVED chris Gilbert
Edit step will be different depending whether post is root (category may be changed)
or reply (category cannot be changed)
UI refactored so that root post & reply to post forms are used for creation
and editing
private static Logger s_log = Logger.getLogger(EditPostForm.class);
@ -102,5 +113,5 @@ public class EditPostForm extends PostForm {
}
}
*/
}

View File

@ -61,14 +61,14 @@ class ForumAlertsView extends SimpleContainer implements Constants {
}
private Component forumAlertsSegment() {
SimpleContainer seg = new SimpleContainer("forum:forumAlerts",
SimpleContainer seg = new SimpleContainer(FORUM_XML_PREFIX + ":forumAlerts",
FORUM_XML_NS);
seg.add(forumAlertsForm());
return seg;
}
private Component threadAlertsSegment() {
SimpleContainer seg = new SimpleContainer("forum:threadAlerts",
SimpleContainer seg = new SimpleContainer(FORUM_XML_PREFIX + ":threadAlerts",
FORUM_XML_NS);
seg.add(threadAlertsForm());
return seg;

View File

@ -18,32 +18,36 @@
*/
package com.arsdigita.forum.ui;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.ModalContainer;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.security.UserContext;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.forum.ui.admin.ModerationView;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.xml.Element;
import com.arsdigita.util.UncheckedWrapperException;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.log4j.Logger;
import java.io.IOException;
import com.arsdigita.bebop.ModalContainer;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.ui.admin.ModerationView;
import com.arsdigita.forum.ui.admin.PermissionsView;
import com.arsdigita.forum.ui.admin.SetupView;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.kernel.security.UserContext;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
/**
* The Bebop Page which provides the complete UI for the bboard application
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
*
* @version $Revision: #11 $ $Author: sskracic $ $Date: 2004/08/17 $
* @version $Revision: 1.3 $ $Author: chrisg23 $ $Date: 2006/03/09 13:48:15 $
*/
public class ForumComponent extends ModalContainer implements Constants {
@ -53,32 +57,39 @@ public class ForumComponent extends ModalContainer implements Constants {
public static final String MODE_TOPICS = "topics";
public static final String MODE_ALERTS = "alerts";
public static final String MODE_MODERATION = "moderation";
public static final String MODE_PERMISSIONS = "permissions";
public static final String MODE_SETUP = "setup";
private StringParameter m_mode;
/**
* Constructs the bboard use interface
*/
private SetupView m_setupView;
private ModerationView m_moderationView;
private ForumAlertsView m_alertsView;
private CategoryView m_topicView;
private ForumUserView m_userView;
private PermissionsView m_permissionsView;
public ForumComponent() {
super("forum:forum", FORUM_XML_NS);
super(FORUM_XML_PREFIX + ":forum", FORUM_XML_NS);
m_mode = new StringParameter("mode");
m_setupView = new SetupView();
m_moderationView = new ModerationView();
m_alertsView = new ForumAlertsView();
m_topicView = new CategoryView();
m_userView = new ForumUserView();
m_permissionsView = new PermissionsView();
add(m_setupView);
add(m_moderationView);
add(m_alertsView);
add(m_topicView);
add(m_userView);
add(m_permissionsView);
setDefaultComponent(m_userView);
}
@ -94,31 +105,56 @@ public class ForumComponent extends ModalContainer implements Constants {
super.respond(state);
Party party = Kernel.getContext().getParty();
Forum forum = ForumContext.getContext(state).getForum();
String mode = (String)state.getControlEventValue();
state.setValue(m_mode, mode);
setVisible(state, party, forum, mode);
}
protected void setVisible(
PageState state,
Party party,
Forum forum,
String mode) {
PermissionDescriptor forumAdmin =
new PermissionDescriptor(PrivilegeDescriptor.ADMIN, forum, party);
if (MODE_TOPICS.equals(mode)) {
if (Forum.getConfig().topicCreationByAdminOnly()) {
if (party == null) {
UserContext.redirectToLoginPage(state.getRequest());
}
PermissionService.assertPermission(forumAdmin);
}
setVisibleComponent(state, m_topicView);
} else if (MODE_ALERTS.equals(mode)) {
if (Kernel.getContext().getParty() == null) {
if (party == null) {
UserContext.redirectToLoginPage(state.getRequest());
}
setVisibleComponent(state, m_alertsView);
} else if (MODE_MODERATION.equals(mode)) {
Party party = Kernel.getContext().getParty();
if (party == null) {
UserContext.redirectToLoginPage(state.getRequest());
}
Forum forum = ForumContext.getContext(state).getForum();
PermissionDescriptor permission
= new PermissionDescriptor(PrivilegeDescriptor.ADMIN,
forum,
party);
PermissionService.assertPermission(permission);
PermissionService.assertPermission(forumAdmin);
setVisibleComponent(state, m_moderationView);
} else {
} else if (MODE_PERMISSIONS.equals(mode)) {
if (party == null) {
UserContext.redirectToLoginPage(state.getRequest());
}
PermissionService.assertPermission(forumAdmin);
setVisibleComponent(state, m_permissionsView);
} else if (MODE_SETUP.equals(mode)) {
if (party == null) {
UserContext.redirectToLoginPage(state.getRequest());
}
PermissionService.assertPermission(forumAdmin);
setVisibleComponent(state, m_setupView);
} else if (MODE_THREADS.equals(mode)) {
setVisibleComponent(state, m_userView);
}
}
@ -126,27 +162,45 @@ public class ForumComponent extends ModalContainer implements Constants {
public void generateXML(PageState state,
Element parent) {
Element content = generateParent(parent);
generateModeXML(state, content, MODE_THREADS);
generateModeXML(state, content, MODE_TOPICS);
generateModeXML(state, content, MODE_ALERTS);
Forum forum = ForumContext.getContext(state).getForum();
content.addAttribute("title", forum.getTitle());
content.addAttribute("noticeboard", (new Boolean(forum.isNoticeboard())).toString());
content.addAttribute(
"noticeboard",
(new Boolean(forum.isNoticeboard())).toString());
Party party = Kernel.getContext().getParty();
if (party != null) {
if (party == null) {
party = Kernel.getPublicUser();
}
generateModes(state, content, party, forum);
generateChildrenXML(state, content);
}
protected void generateModes(
PageState state,
Element content,
Party party,
Forum forum) {
PermissionDescriptor permission =
new PermissionDescriptor(PrivilegeDescriptor.ADMIN, forum, party);
generateModeXML(state, content, MODE_THREADS);
if (!Forum.getConfig().topicCreationByAdminOnly()) {
generateModeXML(state, content, MODE_TOPICS);
}
generateModeXML(state, content, MODE_ALERTS);
PermissionDescriptor permission
= new PermissionDescriptor(PrivilegeDescriptor.ADMIN,
forum,
party);
if (PermissionService.checkPermission(permission)) {
generateModeXML(state, content, MODE_MODERATION);
if (Forum.getConfig().showNewTabs()) {
generateModeXML(state, content, MODE_SETUP);
generateModeXML(state, content, MODE_PERMISSIONS);
}
if (Forum.getConfig().topicCreationByAdminOnly()) {
generateModeXML(state, content, MODE_TOPICS);
}
}
generateChildrenXML(state, content);
}
protected void generateModeXML(PageState state,
@ -157,7 +211,8 @@ public class ForumComponent extends ModalContainer implements Constants {
current = MODE_THREADS;
}
Element content = parent.newChildElement("forum:forumMode", FORUM_XML_NS);
Element content =
parent.newChildElement(FORUM_XML_PREFIX + ":forumMode", FORUM_XML_NS);
state.setControlEvent(this, "mode", mode);

View File

@ -1,5 +1,6 @@
forum.ui.name=Name
forum.ui.description=Description
forum.ui.cancel=Cancel
forum.ui.newTopic=New topic
forum.ui.topic.none=None
@ -11,7 +12,7 @@ forum.ui.newPost=New thread
forum.ui.threads.viewAll=View all threads
forum.ui.thread.subscribe=Subscribe to thread
forum.ui.thread.unsubscribe=Unsubscribe to thread
forum.ui.topic.save=Create Topic
forum.ui.moderate.label=Moderation:
forum.ui.moderate.switch.on=Turn on
@ -27,3 +28,26 @@ forum.ui.noticeboard.status.on=On (replying disabled)
forum.ui.noticeboard.status.off=Off (replying enabled)
forum.ui.noticeboard.expiry_after=Expires after (in days)
forum.ui.noticeboard.change_expiry=Update
forum.ui.settings.moderated=Moderated
forum.ui.settings.noticeboard=Noticeboard (Disable Replying)
forum.ui.settings.allowFiles=Allow File Attachments
forum.ui.settings.allowImages=Allow images in posts
forum.ui.settings.autosubscribe=Automatically subscribe thread starters
forum.ui.settings.noCategoryPosts=Allow posts with no topic
forum.ui.settings.anonymousPosts=Allow anonymous posts
forum.ui.settings.save=Save Changes
forum.ui.settings.introduction=Introduction
forum.ui.settings.title=Forum Title
forum.ui.validation.subject_null=Please enter a subject
forum.ui.validation.body_null=Please enter a message
forum.ui.validation.body_too_long=Your message is too long, only 4000 characters can be stored
forum.ui.validation.image_file_null=Please use the browse button above to find an image to add
forum.ui.validation.image_description_null=Please enter a description for the image (This will be displayed to users who cannot see the image)
forum.ui.validation.image_description_too_long=Your description is too long, only 4000 characters can be stored
forum.ui.validation.file_null=Please use the browse button above to find a file to add
forum.ui.validation.file_description_too_long=Your description is too long, only 4000 characters can be stored
forum.ui.validation.image_not_uploaded=To add the specified image, use the Add Image button before leaving this page. If you don't want to add the image, click Next or Previous.
forum.ui.validation.file_not_uploaded=To add the specified file, use the Add File button before leaving this page. If you don't want to add the file, click Next or Previous.
forum.ui.validation.introduction_too_long=Your introduction is too long, only 4000 characters can be stored

View File

@ -30,7 +30,13 @@ import com.arsdigita.bebop.ToggleLink;
import com.arsdigita.bebop.event.ActionEvent;
import com.arsdigita.bebop.event.ActionListener;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.toolbox.ui.SecurityContainer;
import org.apache.log4j.Logger;
@ -39,7 +45,7 @@ import org.apache.log4j.Logger;
*
* @author Kevin Scaldeferri (kevin@arsdigita.com)
*
* @version $Revision: #8 $ $Author: sskracic $ $DateTime: 2004/08/17 23:26:27 $
* @version $Revision: 1.8 $ $Author: chrisg23 $ $DateTime: 2004/08/17 23:26:27 $
*/
public class ForumUserView extends SimpleContainer
implements Constants {
@ -47,7 +53,7 @@ public class ForumUserView extends SimpleContainer
private static Logger s_log = Logger.getLogger(ForumUserView.class);
private Component m_forumView;
private Component m_forumPost;
private PostForm m_forumPost;
private ModalContainer m_mode;
@ -78,6 +84,7 @@ public class ForumUserView extends SimpleContainer
*/
p.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
s_log.debug("create link pressed");
PageState s = e.getPageState();
/*
if ("t".equals((String)s.getValue(m_newPostParam))) {
@ -87,6 +94,7 @@ public class ForumUserView extends SimpleContainer
*/
if (m_newTopicLink.isSelected(s)) {
m_forumPost.setContext(s, PostForm.NEW_CONTEXT);
m_mode.setVisibleComponent(s, m_forumPost);
} else {
m_mode.setVisibleComponent(s, m_forumView);
@ -106,10 +114,21 @@ public class ForumUserView extends SimpleContainer
Container forums = new SimpleContainer();
Container forumOptions = new SimpleContainer(
"forum:forumOptions", Constants.FORUM_XML_NS);
FORUM_XML_PREFIX + ":forumOptions", Constants.FORUM_XML_NS);
m_newTopicLink = new ToggleLink(new Label(Text.gz("forum.ui.newPost")));
m_newTopicLink.setClassAttr("actionLink");
forumOptions.add(m_newTopicLink);
// chris.gilbert@westsussex.gov.uk - security container added
SecurityContainer sc = new SecurityContainer(m_newTopicLink) {
protected boolean canAccess(Party party, PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
PermissionDescriptor createThread = new PermissionDescriptor(PrivilegeDescriptor.get(Forum.CREATE_THREAD_PRIVILEGE), forum, party);
return PermissionService.checkPermission(createThread);
}
};
forumOptions.add(sc);
forums.add(forumOptions);
// list of categories
@ -124,8 +143,8 @@ public class ForumUserView extends SimpleContainer
return forums;
}
private Component createForumPost() {
Form editForm = new NewPostForm();
private PostForm createForumPost() {
PostForm editForm = new RootPostForm();
editForm.addCompletionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ForumUserView.this

View File

@ -0,0 +1,349 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.FormData;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.FormStep;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.RequestLocal;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormValidationListener;
import com.arsdigita.bebop.event.ParameterEvent;
import com.arsdigita.bebop.form.FileUpload;
import com.arsdigita.bebop.form.Submit;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.parameters.FileSizeValidationListener;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringInRangeValidationListener;
import com.arsdigita.dispatcher.MultipartHttpServletRequest;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.PostImageAttachment;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.mimetypes.MimeType;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.xml.Element;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* Wizard step for adding images, and displaying list of images
* already added
*/
public class ImagesStep
extends FormStep
implements Constants, FormProcessListener, FormValidationListener, FormInitListener {
private static Logger s_log = Logger.getLogger(ImagesStep.class);
private ACSObjectSelectionModel m_post;
private ImagesTable m_images;
private FileUpload m_upload;
private TextArea m_description;
private Submit m_addImage;
private PostForm m_container;
private ArrayParameter m_newImages = new ArrayParameter("images");
private ArrayParameter m_existingImages = new ArrayParameter("oldImages");
public ImagesStep(ACSObjectSelectionModel post, PostForm container) {
super(
"postImages",
new SimpleContainer(
FORUM_XML_PREFIX + ":postFormImages",
FORUM_XML_NS));
m_post = post;
m_container = container;
m_images = new ImagesTable(m_newImages, m_existingImages, this);
add(m_images);
m_upload = new FileUpload("image", true);
m_upload
.addValidationListener(new NotEmptyValidationListener(
Text
.gz("forum.ui.validation.image_file_null")) {
public void validate(ParameterEvent e) {
if (m_addImage.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
m_upload
.addValidationListener(new FileSizeValidationListener(
Forum
.getConfig()
.getMaxImageSize()) {
public void validate(ParameterEvent e) {
if (m_addImage.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
add(m_upload);
m_description = new TextArea("imageDescription");
m_description.setCols(20);
m_description.setRows(5);
m_description
.addValidationListener(new NotEmptyValidationListener(
Text
.gz("forum.ui.validation.image_description_null")) {
public void validate(ParameterEvent e) {
if (m_addImage.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
m_description
.addValidationListener(new StringInRangeValidationListener(
0,
4000,
Text.gz("forum.ui.validation.image_description_too_long")) {
public void validate(ParameterEvent e)
throws FormProcessException {
if (m_addImage.isSelected(e.getPageState())) {
super.validate(e);
}
}
});
add(m_description);
m_addImage = new Submit("Add Image");
add(m_addImage);
addInitListener(this);
addProcessListener(this);
addValidationListener(this);
}
public void process(FormSectionEvent e) throws FormProcessException {
s_log.debug("process event fired");
FormData data = e.getFormData();
PageState state = e.getPageState();
if (m_addImage.isSelected(state)) {
PostImageAttachment a;
s_log.debug("adding file");
try {
String fileName = (String) m_upload.getValue(state);
s_log.debug("filename is " + fileName);
File file =
(
(MultipartHttpServletRequest) e
.getPageState()
.getRequest())
.getFile(
m_upload.getName());
s_log.debug("uploaded file is " + file.getName());
MimeType mimeType = MimeType.guessMimeTypeFromFile(fileName);
s_log.debug(
"Upload mime type is instance of " + mimeType.getClass());
s_log.debug(
"mime type is "
+ (mimeType == null ? null : mimeType.getMimeType()));
a = new PostImageAttachment();
a.loadFromFile(fileName, file, null);
a.setDescription((String) m_description.getValue(state));
a.setMimeType(mimeType);
BigDecimal id = a.getID();
String[] current = (String[]) state.getValue(m_newImages);
if (current == null) {
current = new String[] { id.toString()};
} else {
List images = Arrays.asList(current);
current = new String[images.size() + 1];
Iterator it = images.iterator();
int i = 0;
while (it.hasNext()) {
current[i++] = (String) it.next();
}
current[i] = id.toString();
}
state.setValue(m_newImages, current);
s_log.debug("File Uploaded");
m_description.setValue(state, null);
} catch (Exception ex) {
throw new FormProcessException(ex);
}
}
}
/**
* Prevent users navigating away from this page unintentionally without
* adding an uploaded image first
*/
public void validate(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (!m_addImage.isSelected(state)
&& StringUtils.isNotBlank((String) m_upload.getValue(state))) {
throw new FormProcessException(
(String) Text
.gz("forum.ui.validation.image_not_uploaded")
.localize());
}
}
/**
* @param post
* @param state
*/
public void attachImages(Post post, PageState state) {
List newImages = Collections.EMPTY_LIST;
String[] newImagesArray = (String[]) state.getValue(m_newImages);
if (newImagesArray != null) {
newImages = new ArrayList(Arrays.asList(newImagesArray));
}
String[] existing = (String[]) state.getValue(m_existingImages);
if (existing != null) {
for (int i = 0; i < existing.length; i++) {
if (newImages.contains(existing[i])) {
// no change to this one
newImages.remove(existing[i]);
} else {
// file has been deleted in edit
PostImageAttachment image =
new PostImageAttachment(new BigDecimal(existing[i]));
post.removeImage(image);
}
}
}
Iterator it = newImages.iterator();
while (it.hasNext()) {
// new files added
PostImageAttachment image =
new PostImageAttachment(new BigDecimal((String) it.next()));
post.addImage(image);
}
state.setValue(m_newImages, null);
state.setValue(m_existingImages, null);
}
public void register(Page p) {
super.register(p);
p.addGlobalStateParam(m_existingImages);
p.addGlobalStateParam(m_newImages);
}
public void init(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (m_container
.getContext(state)
.equals(PostForm.EDIT_CONTEXT)) {
Post editPost = (Post) m_post.getSelectedObject(state);
DataAssociationCursor files = editPost.getImages();
if (files.size() > 0) {
String[] fileIDs =
new String[new Long(files.size()).intValue()];
int i = 0;
while (files.next()) {
PostImageAttachment image =
(PostImageAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
fileIDs[i++] = image.getID().toString();
}
state.setValue(m_existingImages, fileIDs);
state.setValue(m_newImages, fileIDs);
}
}
}
public DataCollection getCurrentImages(PageState state) {
String[] currentArray = (String[]) state.getValue(m_newImages);
if (currentArray != null && currentArray.length != 0) {
List current =
Arrays.asList((String[]) state.getValue(m_newImages));
DataCollection images =
SessionManager.getSession().retrieve(
PostImageAttachment.BASE_DATA_OBJECT_TYPE);
images.addFilter("id in :files").set("files", current);
return images;
}
return null;
}
/**
* generate xml for the confirmation step (unfortunately we can't just
* traverse the post because it hasn't been created at this point)
* @param state
* @param p
*/
public void generatePostXML(PageState state, Element p) {
DataCollection files = getCurrentImages(state);
if (files == null) {
return;
}
while (files.next()) {
PostImageAttachment image =
(PostImageAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
DomainObjectXMLRenderer xr =
new DomainObjectXMLRenderer(p.newChildElement("images"));
xr.setWrapRoot(false);
xr.setWrapAttributes(true);
xr.setWrapObjects(false);
xr.walk(image, ConfirmStep.class.getName());
}
}
protected void clearParameters (PageState state) {
state.setValue(m_existingImages, null);
state.setValue(m_newImages, null);
}
}

View File

@ -0,0 +1,184 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.ControlLink;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.table.TableModel;
import com.arsdigita.cms.FileAsset;
import com.arsdigita.cms.dispatcher.Utilities;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.PostFileAttachment;
import com.arsdigita.forum.PostImageAttachment;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.messaging.MessagePart;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
/**
* Semantic table with delete link. This could be extended to provide
* additional links eg move up/move down
*
* @author Chris Gilbert <a href="mailto:chris.gilbert@westsussex.gov.uk">chris.gilbert@westsussex.gov.uk</a>
* @version $Revision: 1.4 $ $DateTime: 2004/08/17 23:15:09 $
**/
public class ImagesTable extends SimpleComponent implements Constants {
// I have not implemented an edit link or a move up/down link although the data model
// is able to manage this, it seemed like a lot of work for a minor requirement for a forum
// currently, files are listed in the order they are added.
// to add extra links, define new keys for the control event and check key value in the
// respond method (see ThreadDisplay for example code)
//
// only issue is that when editing a post, the editor cannot change the description easily
// (though they could download the file to their PC, delete the existing one and recreate it)
private static Logger s_log = Logger.getLogger(ImagesTable.class);
protected static final String ACTION_DELETE = "delete";
private ArrayParameter m_newImages;
// ie when post is being edited
private ArrayParameter m_existingImages;
private ImagesStep m_parent;
public ImagesTable(
ArrayParameter newImages,
ArrayParameter existingImages,
ImagesStep parent) {
m_newImages = newImages;
m_existingImages = existingImages;
m_parent = parent;
}
/**
* if image has been added during this session then delete it completely.
* If we are editing a post and we delete an image that was in the original
* post then just flag the image as deleted in case the editing is cancelled
*/
public void respond(PageState state) throws ServletException {
super.respond(state);
String key = state.getControlEventName();
String value = state.getControlEventValue();
if (ACTION_DELETE.equals(key)) {
List existing = Collections.EMPTY_LIST;
String[] existingArray =
(String[]) state.getValue(m_existingImages);
if (existingArray != null) {
existing = Arrays.asList(existingArray);
}
if (!existing.contains(value)) {
// this has been added during edit
OID oid =
new OID(
PostImageAttachment.BASE_DATA_OBJECT_TYPE,
new BigDecimal(value));
DomainObjectFactory.newInstance(oid).delete();
}
List newImages =
new ArrayList(
Arrays.asList((String[]) state.getValue(m_newImages)));
newImages.remove(value);
String[] current = new String[newImages.size()];
Iterator it = newImages.iterator();
int i = 0;
while (it.hasNext()) {
current[i++] = (String) it.next();
}
state.setValue(m_newImages, current);
}
}
public void generateXML(PageState state, Element p) {
Element mainElement =
p.newChildElement(
FORUM_XML_PREFIX + ":attachedImages",
FORUM_XML_NS);
DataCollection images = m_parent.getCurrentImages(state);
if (images == null) {
return;
}
while (images.next()) {
PostImageAttachment image =
(PostImageAttachment) DomainObjectFactory.newInstance(
images.getDataObject());
Element imageElement =
mainElement.newChildElement(
FORUM_XML_PREFIX + ":image",
FORUM_XML_NS);
imageElement.addAttribute("name", image.getName());
imageElement.addAttribute("src", Utilities.getAssetURL(image));
imageElement.addAttribute("caption", image.getDescription());
generateActionXML(state, imageElement, image);
}
}
private void generateActionXML(
PageState state,
Element parent,
PostImageAttachment image) {
//separate method so that if future links require some logic
//to decide whether to display, it can be implemented here
parent.addAttribute("deleteLink", makeURL(state, ACTION_DELETE, image));
}
protected String makeURL(
PageState state,
String action,
PostImageAttachment image) {
state.setControlEvent(this, action, image.getID().toString());
String url = null;
try {
url = state.stateAsURL();
} catch (IOException ex) {
throw new UncheckedWrapperException("cannot create url", ex);
}
state.clearControlEvent();
return url;
}
}

View File

@ -23,6 +23,7 @@ import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.messaging.Message;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.xml.Element;
@ -33,12 +34,14 @@ import org.apache.log4j.Logger;
class MessageView extends SimpleComponent implements Constants {
private static final Logger s_log = Logger.getLogger(MessageView.class);
private ReplyToPostForm m_container;
private ACSObjectSelectionModel m_postModel;
private Post m_post;
/** For dynamically selected message views */
public MessageView(ACSObjectSelectionModel postModel) {
public MessageView(ACSObjectSelectionModel postModel, ReplyToPostForm container) {
m_postModel = postModel;
m_container = container;
}
public MessageView(Post post) {
@ -55,12 +58,18 @@ class MessageView extends SimpleComponent implements Constants {
public void generateXML(PageState state,
Element parent) {
Post post = m_post;
Message post = m_post;
if (m_post == null) {
post = (Post)m_postModel.getSelectedObject(state);
}
if (m_container.getContext(state).equals(ReplyToPostForm.EDIT_CONTEXT)) {
// post in postmodel is the reply being edited, not the parent
post = post.getParent();
}
Element messageEl = parent.newChildElement("forum:message",
Element messageEl = parent.newChildElement(FORUM_XML_PREFIX + ":message",
FORUM_XML_NS);
exportAttributes(messageEl);

View File

@ -1,110 +0,0 @@
/*
* Copyright (C) 2002-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.categorization.Category;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.Forum;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.web.RedirectSignal;
import com.arsdigita.web.URL;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
/**
* @see com.arsdigita.forum.Post.java
* @see com.arsdigita.forum.EditPostForm.java
*/
class NewPostForm extends PostForm {
public static final String versionId =
"$Id: NewPostForm.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(NewPostForm.class);
private CategoryWidget m_category;
public NewPostForm() {
super("newPostForm");
setupComponent();
}
protected Container dataEntryStep() {
Container initial = super.dataEntryStep();
m_category = new CategoryWidget(new BigDecimalParameter("topic"));
initial.add(m_category);
return initial;
}
protected Post getPost(PageState state,
boolean create) {
if (!create) {
return null;
}
Post post = Post.create(ForumContext.getContext(state).getForum());
post.setFrom(Kernel.getContext().getParty());
return post;
}
protected void initWidgets(PageState state,
Post post) {
super.initWidgets(state, post);
}
protected void processWidgets(PageState state,
Post post) {
super.processWidgets(state,
post);
post.clearCategories();
BigDecimal categoryID = (BigDecimal)m_category.getValue(state);
if (categoryID != null &&
!categoryID.equals(Constants.TOPIC_NONE)) {
post.mapCategory(new Category(categoryID));
}
ForumContext ctx = ForumContext.getContext(state);
Forum forum = ctx.getForum();
if (forum.isModerated() &&
!ctx.canModerate()) {
post.setStatus(Post.PENDING);
} else {
post.setStatus(Post.APPROVED);
}
// XXX we shouldn't have to save the post yet
// just to create a subscription :-(
post.save();
post.createThreadSubscription();
throw new RedirectSignal(URL.here(state.getRequest(), "/"), true);
}
}

View File

@ -18,10 +18,10 @@
*/
package com.arsdigita.forum.ui;
import com.arsdigita.bebop.Container;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.FormStep;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.Wizard;
@ -29,20 +29,17 @@ import com.arsdigita.bebop.event.FormCancelListener;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringLengthValidationListener;
import com.arsdigita.bebop.event.FormSubmissionListener;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.ThreadSubscription;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.security.UserContext;
import com.arsdigita.toolbox.ui.TextTypeWidget;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.xml.Element;
import com.arsdigita.util.HtmlToText;
import com.arsdigita.util.MessageType;
import org.apache.log4j.Logger;
/**
* Class PostForm
@ -50,150 +47,235 @@ import org.apache.log4j.Logger;
* @author Jon Orris (jorris@arsdigita.com)
*
* @version $Revision #1 $DateTime: 2004/08/17 23:26:27 $
*
* rewritten Chris Gilbert.
*
* Abstract form for behaviour shared by all types of post. Different types of
* post (new or reply) have different text screens, retrieved using
* the abstract getTextStep method and also may behave
* differently in final processing (final meaning on the last step
* of the wizard) by overriding the processWidgets method.
*
* They all share the remainder of the steps (currently add files, add images and preview)
* and processing relating to shared behaviour
*/
public abstract class PostForm extends Wizard implements Constants {
public static final String versionId =
"$Id: PostForm.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
"$Id: PostForm.java 1628 2007-09-17 08:10:40Z chrisg23 $"
+ "$Author: chrisg23 $"
+ "$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger(PostForm.class);
private TextField m_subject;
private TextArea m_body;
private TextTypeWidget m_bodyType;
private PostTextStep m_textStep;
private ImagesStep m_attachImages;
private AttachedFilesStep m_attachFiles;
private ConfirmStep m_confirm;
private ACSObjectSelectionModel m_post;
/**
* context is used because when editing, we might use the root post
* form (with topic selection) or the reply to post form (without
* topic selection). context allows form to behave appropriately
*
*/
private StringParameter m_context = new StringParameter("context");
public static final String NEW_CONTEXT = "new";
public static final String REPLY_CONTEXT = "reply";
public static final String EDIT_CONTEXT = "edit";
/**
*
* Form used for new thread - no existing post object
*
*/
public PostForm(String name) {
super(name, new SimpleContainer());
setMethod(POST);
this(name, null);
}
protected void setupComponent() {
add(dataEntryStep());
add(confirmStep());
/**
* Used when editing an existing post
* @param name
* @param post
*/
public PostForm(String name, ACSObjectSelectionModel post) {
super(name, new SimpleContainer(), Forum.getConfig().quickFinishAllowed(), true);
// note that encoding must be multipart/form-data,and method must be post
// in order for attachments to be uploaded. Ensure that these properties
// are carried through in XSL if not simply applying the default bebop
// templates
setEncType("multipart/form-data");
setMethod(POST);
//setMethod(GET);
m_post = post;
addInitListener(new PostInitListener());
addProcessListener(new PostProcessListener());
addCancelListener(new PostCancelListener());
}
protected void setSubject(PageState state,
String text) {
m_subject.setValue(state, text);
public void setContext(PageState state, String context) {
state.setValue(m_context, context);
}
protected Container dataEntryStep() {
FormStep initial = new FormStep(
"initial",
new SimpleContainer("forum:postForm", FORUM_XML_NS));
public String getContext(PageState state) {
return (String) state.getValue(m_context);
}
m_subject = new TextField(new StringParameter("subject"));
m_subject.addValidationListener(new NotEmptyValidationListener());
m_subject.addValidationListener(new StringLengthValidationListener(250));
m_subject.setSize(60);
initial.add(m_subject);
protected void setupComponent() {
m_body = new TextArea(new StringParameter("message"),
8, 60, TextArea.SOFT);
m_body.addValidationListener(new NotEmptyValidationListener());
m_body.addValidationListener(new StringLengthValidationListener(4000));
initial.add(m_body);
m_textStep = getTextStep(m_post);
add(m_textStep);
m_attachImages = new ImagesStep(m_post, this);
add(m_attachImages);
m_attachFiles = new AttachedFilesStep(m_post, this);
add(m_attachFiles);
m_confirm = new ConfirmStep(m_post, this);
add(m_confirm);
m_bodyType = new TextTypeWidget(new StringParameter("bodyType"),
MessageType.TEXT_PLAIN);
initial.add(m_bodyType);
addInitListener(new PostInitListener());
addCancelListener(new PostCancelListener());
addSubmissionListener(new PostSubmissionListener());
addProcessListener(new PostProcessListener());
return initial;
}
protected Container confirmStep() {
SimpleContainer postContainer = new SimpleContainer
("forum:postConfirm", FORUM_XML_NS) {
public void register(Page p) {
super.register(p);
public void generateXML(PageState state,
Element parent) {
Element content = generateParent(parent);
p.addGlobalStateParam(m_context);
Element subject = content.newChildElement("subject");
subject.setText((String)m_subject.getValue(state));
Element body = content.newChildElement("body");
body.setText(HtmlToText.generateHTMLText(
(String)m_body.getValue(state),
(String)m_bodyType.getValue(state)));
}
};
return postContainer;
}
/**
* return text step appropriate for the type of post - reply doesn't give the option
* to choose a topic
* @param post
* @return
*/
protected abstract PostTextStep getTextStep(ACSObjectSelectionModel m_post);
protected abstract Post getPost(PageState state,
boolean create);
/**
* potentially create a new post object (unless we are editing one) Subclasses
* should create a post in whatever way is appropriate (eg a reply form
* will need to tie the new post to the one that is being replied to)
* @param state
* @return
*/
protected abstract Post getPost(PageState state);
protected void initWidgets(PageState state,
Post post) {
if (post != null) {
m_subject.setValue(state, post.getSubject());
m_body.setValue(state, post.getBody());
m_bodyType.setValue(state, post.getBodyType());
}
}
protected void processWidgets(PageState state,
Post post) {
post.setSubject((String)m_subject.getValue(state));
post.setBody((String)m_body.getValue(state),
(String)m_bodyType.getValue(state));
}
//
//
// FORM EVENT LISTENERS. NOTE THAT INDIVIDUAL STEP LISTENERS ARE
// NOTIFIED AFTER THE MAIN FORM LISTENERS - BE AWARE
//
//
//
private class PostInitListener implements FormInitListener {
public void init(FormSectionEvent e) {
s_log.debug("init called on parent form");
PageState state = e.getPageState();
if ( Kernel.getContext().getParty() == null ) {
if (Kernel.getContext().getParty() == null
&& !ForumContext
.getContext(state)
.getForum()
.anonymousPostsAllowed()) {
UserContext.redirectToLoginPage(state.getRequest());
}
initWidgets(state,
getPost(state, false));
}
}
private class PostSubmissionListener implements FormSubmissionListener {
private class PostProcessListener implements FormProcessListener {
public void process(FormSectionEvent e)
throws FormProcessException {
/**
* potentially skip steps of the wizard if the forum does not
* allow images or file attachments
*/
public void submitted(FormSectionEvent e) {
s_log.debug("page submitted");
PageState state = e.getPageState();
ForumContext ctx = ForumContext.getContext(state);
Forum forum = ctx.getForum();
if (!forum.allowImageUploads()) {
hideStep(1, state);
}
if (!forum.allowFileAttachments()) {
hideStep(2, state);
}
}
}
final PageState state = e.getPageState();
private class PostCancelListener implements FormCancelListener {
public void cancel(FormSectionEvent e) throws FormProcessException {
Post post = getPost(state, true);
processWidgets(state, post);
post.sendNotifications();
post.sendModeratorAlerts();
PageState state = e.getPageState();
clearParameters(state);
fireCompletionEvent(state);
}
}
private class PostProcessListener implements FormProcessListener {
public void process(FormSectionEvent e) throws FormProcessException {
s_log.debug("process called on Parent Form");
final PageState state = e.getPageState();
Post post = getPost(state);
//m_post.setSelectedObject(state, post);
post.setStatus(state);
m_textStep.setText(post, state);
post.save();
m_attachFiles.attachFiles(post, state);
m_attachImages.attachImages(post, state);
// sort out notifications
if (getContext(state).equals(NEW_CONTEXT)) {
s_log.debug("new thread - create subscription");
ThreadSubscription sub = post.createThreadSubscription();
ForumContext ctx = ForumContext.getContext(state);
Forum forum = ctx.getForum();
if (forum.autoSubscribeThreadStarter()) {
User threadStarter = (User) Kernel.getContext().getParty();
if (threadStarter != null
&& !threadStarter.equals(Kernel.getPublicUser())) {
s_log.debug("auto subscribing current user");
sub.subscribe(threadStarter);
}
}
}
post.sendNotifications((String) state.getValue(m_context));
post.sendModeratorAlerts();
clearParameters(state);
fireCompletionEvent(state);
/* XXX: This is the right thing to do, however it results in
the wizard not being reset. I haven't tracked down why.
state.clearControlEvent();
try {
throw new RedirectSignal( state.stateAsURL(), true );
} catch( IOException ex ) {
throw new UncheckedWrapperException( ex );
}
*/
}
public Post getSelectedPost(PageState state) {
if (m_post == null) {
return null;
} else {
return (Post) m_post.getSelectedObject(state);
}
}
private class PostCancelListener implements FormCancelListener {
public void cancel(FormSectionEvent e)
throws FormProcessException {
/**
* required for confirmation step. Post hasn't been created so we
* cannot traverse a domain object. Instead, the form is responsible
* for retrieving a representation of the post as it will appear when saved
* @param state
* @param content
*/
protected void generatePostXML(PageState state, Element content) {
m_textStep.generatePostXML(state, content);
m_attachFiles.generatePostXML(state, content);
m_attachImages.generatePostXML(state, content);
PageState state = e.getPageState();
fireCompletionEvent(state);
}
private void clearParameters(PageState state) {
state.setValue(m_context, null);
m_attachImages.clearParameters(state);
m_attachFiles.clearParameters(state);
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.text.DateFormat;
import java.util.Date;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.Bebop;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.FormStep;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.form.DHTMLEditor;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.NotEmptyValidationListener;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.util.BebopConstants;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.toolbox.ui.TextTypeWidget;
import com.arsdigita.util.MessageType;
import com.arsdigita.xml.Element;
/**
* @author Chris Gilbert <a href="mailto:chris.gilbert@westsussex.gov.uk">chris.gilbert@westsussex.gov.uk</a>
*
* Shared behaviour for text step
*/
public abstract class PostTextStep extends FormStep implements Constants {
private static Logger s_log = Logger.getLogger(PostTextStep.class);
private PostForm m_container;
private static final String FCK_FORUM_CONFIG =
"/assets/fckeditor/config/fckconfig_forum.js";
private TextField m_subject;
private TextArea m_body;
private TextTypeWidget m_bodyType;
private ACSObjectSelectionModel m_post;
/**
* create a text step with title and message fields and message type selection list
* TO DO - add config parameter to conditionally replace latter 2 with a single FCKEditor instance
* @param post
* @param container
*/
public PostTextStep(ACSObjectSelectionModel post, PostForm container) {
super(
"postText",
new SimpleContainer(FORUM_XML_PREFIX + ":postForm", FORUM_XML_NS));
m_container = container;
m_post = post;
m_subject = new TextField(new StringParameter("subject"));
m_subject.addValidationListener(
new NotEmptyValidationListener(
Text.gz("forum.ui.validation.subject_null")));
m_subject.setMaxLength(250);
m_subject.setSize(60);
add(m_subject);
if (Forum.getConfig().useWysiwygEditor()) {
m_body = new DHTMLEditor("message");
m_body.setRows(8);
m_body.setCols(60);
m_body.setMetaDataAttribute("width", "450");
m_body.setMetaDataAttribute("height", "300");
m_body.setWrap(DHTMLEditor.SOFT);
if (Bebop
.getConfig()
.getDHTMLEditor()
.equals(BebopConstants.BEBOP_FCKEDITOR)) {
((DHTMLEditor) m_body).setConfig(
new DHTMLEditor.Config("forum", FCK_FORUM_CONFIG));
} else {
// remove this so end users cannot browse through back end folder system
((DHTMLEditor) m_body).hideButton("insertlink");
}
} else {
m_body = new TextArea(new StringParameter("message"),
8, 60, TextArea.SOFT);
}
// otherwise, standard HTMLArea config - may need to change this if anyone is still
// using html area
m_body.addValidationListener(
new NotEmptyValidationListener(
Text.gz("forum.ui.validation.body_null")));
add(m_body);
m_bodyType = new TextTypeWidget(new StringParameter("bodyType"),
MessageType.TEXT_PLAIN);
add(m_bodyType);
addInitListener(new InitTextStepListener());
}
/**
* allow subclasses to initialise subject field
* @param state
* @param text
*/
protected void setSubject(PageState state, String text) {
m_subject.setValue(state, text);
}
/**
* allow subclasses to initialise message and message type fields
* @param state
* @param body
* @param bodyType
*/
protected void setBody(PageState state, String body, String bodyType) {
m_body.setValue(state, body);
m_bodyType.setValue(state, bodyType);
}
protected void initWidgets(PageState state, Post post) {
StringBuffer body = new StringBuffer();
if (m_container.getContext(state).equals(PostForm.EDIT_CONTEXT)) {
if (Forum.getConfig().useWysiwygEditor()) {
body.append("<p>\n<i>\n");
body.append(
"Edited on "
+ DateFormat.getDateInstance(DateFormat.SHORT).format(
new Date())
+ " by "
+ ((User) Kernel.getContext().getParty()).getName());
body.append("\n</i>\n</p>\n\n<br/>\n\n");
} else {
body.append("\n");
body.append(
"Edited on "
+ DateFormat.getDateInstance(DateFormat.SHORT).format(
new Date())
+ " by "
+ ((User) Kernel.getContext().getParty()).getName());
body.append("\n\n");
}
}
body.append(post.getBody());
setSubject(state, post.getSubject());
setBody(state, body.toString(), post.getBodyType());
}
/*
*
*
* If a delete link is used, the parameter that tells the wizard not to
* reinitialize steps is ALSO lost, and so init listeners are fired again
*
*/
private class InitTextStepListener implements FormInitListener {
public void init(FormSectionEvent e) throws FormProcessException {
s_log.debug("Init called on Text Step");
PageState state = e.getPageState();
if (Forum.getConfig().useWysiwygEditor()) {
m_bodyType.setValue(state, MessageType.TEXT_HTML);
m_bodyType.setVisible(state, false);
}
if (m_post != null) {
Post post = (Post) m_post.getSelectedObject(state);
initWidgets(state, post);
}
}
}
/**
*
*/
public void setText(Post post, PageState state) {
post.setSubject((String) m_subject.getValue(state));
post.setBody((String) m_body.getValue(state), (String) m_bodyType.getValue(state));
}
/**
* @param state
* @param content
*/
public void generatePostXML(PageState state, Element content) {
Element subject = content.newChildElement("subject");
subject.setText((String) m_subject.getValue(state));
Element body = content.newChildElement("body");
body.setText((String) m_body.getValue(state));
}
}

View File

@ -19,12 +19,17 @@
package com.arsdigita.forum.ui;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Forum;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import org.apache.log4j.Logger;
@ -37,79 +42,51 @@ import org.apache.log4j.Logger;
*/
public class ReplyToPostForm extends PostForm {
public static final String versionId =
"$Id: ReplyToPostForm.java 755 2005-09-02 13:42:47Z sskracic $" +
"$Author: sskracic $" +
"$DateTime: 2004/08/17 23:26:27 $";
"$Id: ReplyToPostForm.java 1628 2007-09-17 08:10:40Z chrisg23 $"
+ "$Author: chrisg23 $"
+ "$DateTime: 2004/08/17 23:26:27 $";
private static final Logger s_log = Logger.getLogger
(ReplyToPostForm.class);
private static final Logger s_log = Logger.getLogger(ReplyToPostForm.class);
private ACSObjectSelectionModel m_parent;
public ReplyToPostForm(ACSObjectSelectionModel parent) {
super("replyPostForm");
m_parent = parent;
public ReplyToPostForm(ACSObjectSelectionModel post) {
super("replyPostForm", post);
setupComponent();
}
public void register(Page p) {
super.register(p);
p.addGlobalStateParam(m_parent.getStateParameter());
}
protected Post getPost(PageState state,
boolean create) {
if (create) {
Post post = (Post)((Post)m_parent.getSelectedObject(state)).replyTo();
post.setFrom(Kernel.getContext().getParty());
return post;
}
return null;
}
protected Container dataEntryStep() {
Container entryStep = super.dataEntryStep();
MessageView replyTo = new MessageView(m_parent);
entryStep.add(replyTo);
return entryStep;
protected PostTextStep getTextStep(ACSObjectSelectionModel post) {
return new ReplyToPostTextStep(post, this);
}
protected void initWidgets(PageState state,
Post post) {
super.initWidgets(state, post);
/* (non-Javadoc)
* @see com.arsdigita.forum.ui.PostForm#getPost(com.arsdigita.bebop.PageState)
*/
protected Post getPost(PageState state) {
Post reply;
if (getContext(state).equals(PostForm.REPLY_CONTEXT)) {
// post model contains the parent post, reply hasn't been created yet
Post parent = getSelectedPost(state);
reply = (Post) parent.replyTo();
Party party = Kernel.getContext().getParty();
if (party == null) {
// anonymous posts MUST be allowed if we have reached here
party = Kernel.getPublicUser();
}
Post parent = (Post)m_parent.getSelectedObject(state);
String prefix = "Re:";
String subject = parent.getSubject();
reply.setFrom(party);
if (subject.length() < 3 ||
prefix.equalsIgnoreCase(subject.substring(0,3))) {
setSubject(state, subject);
} else {
setSubject(state, prefix + " " + subject);
// reply is the post in the post model
reply = getSelectedPost(state);
}
}
return reply;
protected void processWidgets(PageState state,
Post post) {
super.processWidgets(state, post);
ForumContext ctx = ForumContext.getContext(state);
Forum forum = ctx.getForum();
if (forum.isModerated() &&
!ctx.canModerate()) {
post.setStatus(Post.PENDING);
} else {
post.setStatus(Post.APPROVED);
}
post.setRefersTo(forum);
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.PageState;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
/**
* @author chris.gilbert@westsussex.gov.uk
* reply to post text step automatically completes
* the subject field
*
* (nb other forums eg FirstClass allocate numbers to long
* chains of replies, so instead of having a tree with all
* posts having subject 're: original post' they have varied
* subjects 're:(2) original post'. If we wanted to implement
* similar, this is the place to do it
*
*/
public class ReplyToPostTextStep extends PostTextStep {
private static Logger s_log = Logger.getLogger(ReplyToPostTextStep.class);
private ReplyToPostForm m_container;
public ReplyToPostTextStep(
ACSObjectSelectionModel post,
ReplyToPostForm container) {
super(post, container);
MessageView inReplyTo = new MessageView(post, container);
m_container = container;
add(inReplyTo);
}
protected void initWidgets(PageState state, Post post) {
if (m_container.getContext(state).equals(ReplyToPostForm.EDIT_CONTEXT)) {
super.initWidgets(state, post);
} else {
s_log.debug("init called");
s_log.debug("parent is " + post);
String prefix = "Re:";
String subject = post.getSubject();
if (subject.length() < 3
|| prefix.equalsIgnoreCase(subject.substring(0, 3))) {
setSubject(state, subject);
} else {
setSubject(state, prefix + " " + subject);
}
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui;
import java.math.BigDecimal;
import org.apache.log4j.Logger;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.categorization.Category;
import com.arsdigita.categorization.CategoryCollection;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
/**
* @author chris.gilbert@westsussex.gov.uk
*
* root post manages the category(topic) in
* addition to subject & body
*/
public class RootPostTextStep extends PostTextStep {
private static Logger s_log = Logger.getLogger(RootPostTextStep.class);
private CategoryWidget m_category;
public RootPostTextStep(ACSObjectSelectionModel post, PostForm container) {
super(post, container);
m_category = new CategoryWidget(new BigDecimalParameter("postTopic"));
add(m_category);
}
public void setText(Post post, PageState state) {
super.setText(post, state);
post.clearCategories();
// need to save post after setting mandatory fields
// (subject, body, status) and before mapping categories
post.save();
BigDecimal categoryID = (BigDecimal) m_category.getValue(state);
if (categoryID != null && !categoryID.equals(Constants.TOPIC_NONE)) {
post.mapCategory(new Category(categoryID));
}
}
protected void initWidgets(PageState state, Post post) {
super.initWidgets(state, post);
if (post != null) {
CategoryCollection categories = post.getCategories();
if (categories.next()) {
Category cat = categories.getCategory();
m_category.setValue(state, cat.getID());
} else {
m_category.setValue(state, Constants.TOPIC_NONE);
}
categories.close();
} else {
m_category.setValue(state, null);
}
}
}

View File

@ -57,11 +57,11 @@ public class ThreadAlertsList extends SimpleComponent
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement(
"forum:threadAlertList", FORUM_XML_NS);
FORUM_XML_PREFIX + ":threadAlertList", FORUM_XML_NS);
exportAttributes(content);
DomainCollection subs = ThreadSubscription.
getSubsForUser(Kernel.getContext().getParty());
getSubsForUser(Kernel.getContext().getParty(), state);
while (subs.next()) {
ThreadSubscription sub = (ThreadSubscription)subs.getDomainObject();
@ -73,7 +73,7 @@ public class ThreadAlertsList extends SimpleComponent
public Element generateAlertXML(ThreadSubscription sub) {
Element subEl = new Element(
"forum:threadAlert", FORUM_XML_NS);
FORUM_XML_PREFIX + ":threadAlert", FORUM_XML_NS);
ParameterMap map = new ParameterMap();
map.setParameter(THREAD_PARAM, sub.getThreadReal().getID());

View File

@ -19,6 +19,7 @@
package com.arsdigita.forum.ui;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.ThreadSubscription;
import com.arsdigita.forum.ui.admin.RejectionForm;
@ -59,8 +60,8 @@ public class ThreadComponent extends ModalContainer implements Constants {
// References to sub-components for event access.
private Container m_threadView;
private Form m_editForm;
private Form m_replyForm;
private PostForm m_rootForm;
private PostForm m_replyForm;
private Form m_rejectForm;
private static final Logger s_log
@ -83,15 +84,18 @@ public class ThreadComponent extends ModalContainer implements Constants {
private void initComponents() {
// Add the thread components to the modal container and maintain
// references for event manipulation purposes.
m_editForm = new EditPostForm(m_postModel);
s_log.debug("creating edit post form");
m_rootForm = new RootPostForm(m_postModel);
m_replyForm = new ReplyToPostForm(m_postModel);
s_log.debug("creating reply to post form");
s_log.debug("creating reject form");
m_rejectForm = new RejectionForm(m_postModel);
addForm(m_editForm);
addForm(m_rootForm);
addForm(m_replyForm);
addForm(m_rejectForm);
m_threadView = new SimpleContainer();
Container linksPanel = new SimpleContainer("forum:threadOptions",
Container linksPanel = new SimpleContainer(FORUM_XML_PREFIX + ":threadOptions",
Constants.FORUM_XML_NS);
// Offer links to return to index or control alerts.
@ -119,11 +123,21 @@ public class ThreadComponent extends ModalContainer implements Constants {
}
public void makeEditFormVisible(PageState state) {
setVisibleComponent(state, m_editForm);
s_log.debug("making edit form visible");
Post post = (Post)m_postModel.getSelectedObject(state);
if (post.getRoot() == null) {
m_rootForm.setContext(state, ReplyToPostForm.EDIT_CONTEXT);
setVisibleComponent(state, m_rootForm);
} else {
m_replyForm.setContext(state, ReplyToPostForm.EDIT_CONTEXT);
setVisibleComponent(state, m_replyForm);
}
}
public void makeReplyFormVisible(PageState state) {
s_log.debug("making reply form invisible");
s_log.debug("making reply form visible");
m_replyForm.setContext(state, PostForm.REPLY_CONTEXT);
setVisibleComponent(state, m_replyForm);
}
@ -135,12 +149,14 @@ public class ThreadComponent extends ModalContainer implements Constants {
* Creates the component for viewing a thread.
*/
private final void addForm(Form form) {
private final void addForm(final Form form) {
add(form);
form.addCompletionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
s_log.debug("FORM ACTION COMPLETED");
makeListViewVisible(e.getPageState());
PageState ps = e.getPageState();
// ps.reset(form);
makeListViewVisible(ps);
}
});
@ -148,7 +164,8 @@ public class ThreadComponent extends ModalContainer implements Constants {
public void cancel(FormSectionEvent e) {
s_log.debug("fire cancel listener");
PageState ps = e.getPageState();
makeListViewVisible(e.getPageState());
// ps.reset(form);
makeListViewVisible(ps);
}
});
}

View File

@ -18,47 +18,43 @@
*/
package com.arsdigita.forum.ui;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.parameters.IntegerParameter;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.domain.DomainCollection;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.FilterFactory;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.OID;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.web.URL;
import com.arsdigita.web.Web;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.web.RedirectSignal;
import com.arsdigita.xml.Element;
import com.arsdigita.xml.XML;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException;
import java.io.IOException;
import java.math.BigDecimal;
import javax.servlet.ServletException;
import org.apache.log4j.Logger;
import java.io.IOException;
import javax.servlet.ServletException;
import com.arsdigita.bebop.Page;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleComponent;
import com.arsdigita.bebop.parameters.IntegerParameter;
import com.arsdigita.domain.DomainCollection;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainObjectXMLRenderer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Post;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.FilterFactory;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.web.ParameterMap;
import com.arsdigita.web.RedirectSignal;
import com.arsdigita.web.URL;
import com.arsdigita.web.Web;
import com.arsdigita.xml.Element;
import com.arsdigita.xml.XML;
public class ThreadDisplay extends SimpleComponent implements Constants {
@ -67,8 +63,7 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
private IntegerParameter m_pageNumber =
new IntegerParameter(PAGINATOR_PARAM);
private int m_pageSize = 10;
private int m_pageSize = Forum.getConfig().getThreadPageSize();
private static final String ACTION_EDIT = "edit";
private static final String ACTION_DELETE = "delete";
@ -152,7 +147,7 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
} else if (ACTION_APPROVE.equals(key)) {
post.setStatus(Post.APPROVED);
post.save();
post.sendNotifications();
post.sendNotifications(null);
} else if (ACTION_REJECT.equals(key)) {
m_post.setSelectedObject(state, post);
m_threadComponent.makeRejectFormVisible(state);
@ -208,7 +203,7 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement("forum:threadDisplay",
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":threadDisplay",
FORUM_XML_NS);
exportAttributes(content);
@ -249,7 +244,7 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
while (messages.next()) {
Post message = (Post)messages.getDomainObject();
Element messageEl = content.newChildElement("forum:message",
Element messageEl = content.newChildElement(FORUM_XML_PREFIX + ":message",
FORUM_XML_NS);
generateActionXML(state, messageEl, message);
@ -291,12 +286,17 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
Party party = Kernel.getContext().getParty();
if (party == null) {
party = Kernel.getPublicUser();
}
if (post.canEdit(party)) {
parent.addAttribute("editURL",
makeURL(state, ACTION_EDIT, post));
}
if (!ctx.getForum().isNoticeboard()) {
PermissionDescriptor canRespond = new PermissionDescriptor(PrivilegeDescriptor.get(Forum.RESPOND_TO_THREAD_PRIVILEGE), Kernel.getContext().getResource(), party);
if (!ctx.getForum().isNoticeboard() && PermissionService.checkPermission(canRespond)) {
parent.addAttribute("replyURL",
makeURL(state, ACTION_REPLY, post));
}
@ -325,7 +325,7 @@ public class ThreadDisplay extends SimpleComponent implements Constants {
long begin,
long end,
long objectCount) {
Element paginator = parent.newChildElement("forum:paginator", FORUM_XML_NS);
Element paginator = parent.newChildElement(FORUM_XML_PREFIX + ":paginator", FORUM_XML_NS);
URL here = Web.getContext().getRequestURL();
ParameterMap params = new ParameterMap(here.getParameterMap());

View File

@ -77,7 +77,7 @@ public class ThreadList extends SimpleComponent implements Constants {
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement("forum:threadList", FORUM_XML_NS);
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":threadList", FORUM_XML_NS);
ThreadCollection threads = getThreads(state);
@ -113,7 +113,7 @@ public class ThreadList extends SimpleComponent implements Constants {
while (threads.next()) {
MessageThread thread = threads.getMessageThread();
Element threadEl = content.newChildElement("forum:thread", FORUM_XML_NS);
Element threadEl = content.newChildElement(FORUM_XML_PREFIX + ":thread", FORUM_XML_NS);
ParameterMap map = new ParameterMap();
map.setParameter(THREAD_PARAM, thread.getID());
@ -139,7 +139,7 @@ public class ThreadList extends SimpleComponent implements Constants {
long begin,
long end,
long objectCount) {
Element paginator = parent.newChildElement("forum:paginator", FORUM_XML_NS);
Element paginator = parent.newChildElement(FORUM_XML_PREFIX + ":paginator", FORUM_XML_NS);
URL here = Web.getContext().getRequestURL();
ParameterMap params = new ParameterMap(here.getParameterMap());

View File

@ -49,7 +49,7 @@ public class TopicList extends SimpleComponent implements Constants {
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement("forum:topicList",
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":topicList",
FORUM_XML_NS);
exportAttributes(content);
@ -63,7 +63,7 @@ public class TopicList extends SimpleComponent implements Constants {
DataQuery unCategory = forum.getUnCategory();
while (unCategory.next()) {
Element noTopic = content.newChildElement("forum:noTopicSummary",
Element noTopic = content.newChildElement(FORUM_XML_PREFIX + ":noTopicSummary",
FORUM_XML_NS);
Element id = noTopic.newChildElement("id");
@ -81,7 +81,7 @@ public class TopicList extends SimpleComponent implements Constants {
public void generateQueryXML(Element parent,
DataQuery query) {
while (query.next()) {
Element content = parent.newChildElement("forum:topicSummary",
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":topicSummary",
FORUM_XML_NS);
Iterator keys = s_catProps.iterator();

View File

@ -40,7 +40,7 @@ public class TopicSelector extends SimpleComponent implements Constants {
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement("forum:topicSelector",
Element content = parent.newChildElement(FORUM_XML_PREFIX + ":topicSelector",
FORUM_XML_NS);
URL url = URL.request(state.getRequest(), null);
@ -60,7 +60,7 @@ public class TopicSelector extends SimpleComponent implements Constants {
while (cursor.next()) {
Category c = new Category(cursor.getDataObject());
Element topicEl = content.newChildElement("forum:topic", FORUM_XML_NS);
Element topicEl = content.newChildElement(FORUM_XML_PREFIX + ":topic", FORUM_XML_NS);
DomainObjectXMLRenderer xr = new DomainObjectXMLRenderer(topicEl);
xr.setWrapRoot(false);
xr.setWrapAttributes(true);

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.kernel.Group;
import com.arsdigita.util.Assert;
import org.apache.log4j.Logger;
public class AdminEditPane extends SimpleContainer {
private Forum m_forum;
private static final Logger s_log = Logger.getLogger(AdminEditPane.class);
public AdminEditPane() {
GroupMemberDisplay members = new GroupMemberDisplay() {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
Group g = forum.getAdminGroup();
Assert.exists(g, Group.class);
return g;
}
};
members.setIdAttr("Forum-Admin");
add(members);
Form form = new Form("adminUserPicker",
new BoxPanel(BoxPanel.VERTICAL));
form.add(new GroupMemberPicker("admin") {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
return forum.getAdminGroup();
}
});
form.setIdAttr("adminMemberUserPicker");
add(form);
}
}

View File

@ -21,17 +21,20 @@ package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.PageState;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.PartyCollection;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.OID;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
import java.math.BigDecimal;
public abstract class GroupMemberDisplay extends MembersDisplay {
public abstract class GroupMemberDisplay extends MembersDisplay implements Constants {
protected abstract Group getGroup(PageState ps);
@ -64,6 +67,13 @@ public abstract class GroupMemberDisplay extends MembersDisplay {
Group group = getGroup(ps);
group.removeMemberOrSubgroup(party);
group.save();
}
public void generateXML(PageState state, Element parent) {
Element container = parent.newChildElement(FORUM_XML_PREFIX + ":memberList", FORUM_XML_NS);
container.addAttribute("group", getGroup(state).getName());
super.generateXML(state, container);
}
}

View File

@ -52,6 +52,19 @@ public abstract class GroupMemberPicker extends UserPicker {
private Option m_groups;
public GroupMemberPicker() {
super();
}
/**
* Constructor gives the picker widget
* a request parameter based on the context
* hence allowing several pickers on one page
* without them getting their parameters
* mixed up
*/
public GroupMemberPicker(String context) {
super(context);
}
protected void addWidgets() {

View File

@ -65,7 +65,7 @@ public class ModeratorEditPane extends SimpleContainer {
Form form = new Form("userPicker",
new BoxPanel(BoxPanel.VERTICAL));
form.add(new GroupMemberPicker() {
form.add(new GroupMemberPicker("moderator") {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
return forum.getModerationGroup();

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.ColumnPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.SimpleContainer;
import org.apache.log4j.Logger;
/**
* A component that allows users and groups to be added to
* a choice of access groups.
*
* Note, as privileges cascade, any user only needs
* to be in one group (though it doesn't matter if
* they are put into more than one
*/
public class PermissionsView extends SimpleContainer {
private static final Logger s_log = Logger.getLogger
(PermissionsView.class);
private AdminEditPane m_adminPane;
private ModeratorEditPane m_moderatorPane;
private ThreadCreatorEditPane m_creatorPane;
private ThreadResponderEditPane m_responderPane;
private ReaderEditPane m_readerPane;
public PermissionsView() {
m_adminPane = new AdminEditPane();
add(m_adminPane);
m_moderatorPane = new ModeratorEditPane();
add(m_moderatorPane);
m_creatorPane = new ThreadCreatorEditPane();
add(m_creatorPane);
m_responderPane = new ThreadResponderEditPane();
add(m_responderPane);
m_readerPane = new ReaderEditPane();
add(m_readerPane);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.kernel.Group;
import com.arsdigita.util.Assert;
import org.apache.log4j.Logger;
public class ReaderEditPane extends SimpleContainer {
private Forum m_forum;
private static final Logger s_log = Logger.getLogger(ReaderEditPane.class);
public ReaderEditPane() {
GroupMemberDisplay members = new GroupMemberDisplay() {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
Group g = forum.getReadGroup();
Assert.exists(g, Group.class);
return g;
}
};
members.setIdAttr("Forum-Readers");
add(members);
Form form = new Form("readerUserPicker",
new BoxPanel(BoxPanel.VERTICAL));
form.add(new GroupMemberPicker("reader") {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
return forum.getReadGroup();
}
});
form.setIdAttr("readerMemberUserPicker");
add(form);
}
}

View File

@ -18,6 +18,9 @@
*/
package com.arsdigita.forum.ui.admin;
import javax.mail.MessagingException;
import javax.mail.SendFailedException;
import com.arsdigita.bebop.Container;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.FormStep;
@ -25,22 +28,28 @@ import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.Wizard;
import com.arsdigita.bebop.event.ParameterEvent;
import com.arsdigita.bebop.event.PrintEvent;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.PrintListener;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.EmailValidationListener;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.Post;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.kernel.ui.ACSObjectSelectionModel;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.Party;
import com.arsdigita.mail.Mail;
import com.arsdigita.messaging.Message;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.messaging.ThreadedMessage;
import com.arsdigita.notification.Notification;
import com.arsdigita.util.HtmlToText;
import com.arsdigita.util.StringUtils;
import org.apache.log4j.Logger;
@ -49,8 +58,8 @@ public class RejectionForm extends Wizard implements Constants {
private static final Logger s_log = Logger.getLogger(RejectionForm.class);
private ACSObjectSelectionModel m_postModel;
private Notification m_notification;
private Label m_recipientLabel;
private TextField m_recipient;
private TextArea m_textArea;
protected final static String ALERT_BLURB
@ -58,6 +67,8 @@ public class RejectionForm extends Wizard implements Constants {
protected final static String SEPARATOR
= "\n\n" + StringUtils.repeat('-',20) + "\n\n";
protected final static String HTML_SEPARATOR
= "<hr>";
public RejectionForm(ACSObjectSelectionModel postModel) {
super("postRejectionForm");
@ -72,8 +83,26 @@ public class RejectionForm extends Wizard implements Constants {
FormStep form = new FormStep("initial",
new BoxPanel(BoxPanel.VERTICAL));
m_recipientLabel = new Label("Recipient Email (Optional)");
form.add(m_recipientLabel);
m_recipient = new TextField("recipient");
m_recipient.addValidationListener(new EmailValidationListener() {
/* (non-Javadoc)
* @see com.arsdigita.bebop.parameters.EmailValidationListener#validate(com.arsdigita.bebop.event.ParameterEvent)
*/
public void validate(ParameterEvent e) throws FormProcessException {
if (!StringUtils.emptyString(m_recipient.getValue(e.getPageState()))) {
super.validate(e);
}
}
});
m_recipient.setMetaDataAttribute("label","Recipient email(optional)" );
form.add(m_recipient);
form.add(new Label("Message"));
m_textArea = new TextArea("bodyText");
m_textArea.setMetaDataAttribute("label", "Message");
form.add(m_textArea);
return form;
@ -86,7 +115,8 @@ public class RejectionForm extends Wizard implements Constants {
PageState state = e.getPageState();
Post post = (Post)m_postModel.getSelectedObject(state);
Label l = (Label) e.getTarget();
l.setLabel((String)getHeader(post));
l.setOutputEscaping(false);
l.setLabel((String)getHeader(post));
}
});
@ -96,8 +126,9 @@ public class RejectionForm extends Wizard implements Constants {
Post post = (Post)m_postModel.getSelectedObject(state);
Label l = (Label) e.getTarget();
l.setOutputEscaping(false);
String body = StringUtils.quoteHtml(getBody(post, state));
l.setLabel("<pre>" + body + "</pre>");
//String body = StringUtils.quoteHtml(getBody(post, state));
//l.setLabel("<pre>" + body + "</pre>");
l.setLabel(getBody(post, state));
}
});
@ -106,7 +137,9 @@ public class RejectionForm extends Wizard implements Constants {
PageState state = e.getPageState();
Post post = (Post)m_postModel.getSelectedObject(state);
Label l = (Label) e.getTarget();
l.setLabel((String)getSignature());
l.setOutputEscaping(false);
l.setLabel(Mail.getConfig().sendHTMLMessageAsHTMLEmail() ? getHTMLSignature() : StringUtils.replace(getSignature(), "\n", "<br>"));
}
});
@ -122,6 +155,13 @@ public class RejectionForm extends Wizard implements Constants {
public void init(FormSectionEvent event) throws FormProcessException {
PageState state = event.getPageState();
m_textArea.setValue( state, "");
m_recipient.setValue(state, "");
if (!ForumContext.getContext(state).getForum().anonymousPostsAllowed()) {
// optional recipient field is only relevent if post was made
// anonymously
m_recipientLabel.setVisible(state, false);
m_recipient.setVisible(state, false);
}
}
}
@ -129,23 +169,31 @@ public class RejectionForm extends Wizard implements Constants {
private String getHeader(Post post) {
StringBuffer header = new StringBuffer();
header.append("Forum : ");
header.append(post.getForum().getDisplayName()).append("\n");
header.append(post.getForum().getDisplayName()).append("<br>");
header.append("Subject : ");
header.append(post.getSubject()).append("\n\n");
header.append(post.getSubject()).append("<br><br>");
return header.toString();
}
private String getBody(Post post, PageState state) {
StringBuffer body = new StringBuffer();
body.append("Your message has been rejected by the moderator.\n");
body.append("<br><br>Your message has been rejected by the moderator.<br><br>");
body.append("The moderator has given the following reasons:\n\n");
body.append("The moderator has given the following reasons:<br><br>");
body.append((String)m_textArea.getValue(state));
body.append("\n\n");
body.append("<br><br>");
body.append("The content of the message follows:<br><br>");
body.append("Subject: " + post.getSubject() + "<br>");
body.append(post.getBody() + "<br><br><br>");
// additional comments cg. Need to make this whole email configurable - see notifications. Much better
String instructions = Forum.getConfig().getRejectionMessage();
if (instructions != null) {
body.append(instructions);
body.append("<br><br>");
}
body.append("The content of the message follows:\n\n");
body.append("Subject: " + post.getSubject() + "\n");
body.append(post.getBody() + "\n\n\n");
return body.toString();
}
@ -157,6 +205,13 @@ public class RejectionForm extends Wizard implements Constants {
return sig.toString();
}
private String getHTMLSignature() {
StringBuffer sig = new StringBuffer();
sig.append(HTML_SEPARATOR);
sig.append(ALERT_BLURB);
return sig.toString();
}
private class RejectionProcessListener implements FormProcessListener {
public void process(FormSectionEvent event) throws FormProcessException {
PageState state = event.getPageState();
@ -189,35 +244,80 @@ public class RejectionForm extends Wizard implements Constants {
fireCompletionEvent(state);
}
/**
* if optional email address has been entered on the form, send rejection
* notice to that address. Otherwise, sent to post author (unless anonymous post)
*
* @param post
* @param state
*/
private void sendNotice(Post post, PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
Party noticeSender = forum.getModerationGroup();
if (noticeSender == null ) {
noticeSender = post.getModerator();
}
StringBuffer rejectionNoticeBody = new StringBuffer();
rejectionNoticeBody.append(getHeader(post));
rejectionNoticeBody.append(getBody(post, state));
StringBuffer nonHTMLRejectionNotice = new StringBuffer(new HtmlToText().convert(rejectionNoticeBody.toString()));
rejectionNoticeBody.append(getHTMLSignature());
nonHTMLRejectionNotice.append(getSignature());
if (!StringUtils.emptyString(m_recipient.getValue(state) )) {
Mail mailMessage = new Mail();
mailMessage.setTo((String)m_recipient.getValue(state));
mailMessage.setFrom(noticeSender.getPrimaryEmail().getEmailAddress());
mailMessage.setSubject("Moderation notice " + post.getForum().getDisplayName());
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
rejectionNoticeBody.append(getHTMLSignature());
mailMessage.setBody(rejectionNoticeBody.toString(), nonHTMLRejectionNotice.toString());
} else {
mailMessage.setBody(nonHTMLRejectionNotice.toString());
}
try {
mailMessage.send();
} catch (SendFailedException e) {
s_log.error("Couldn't send forum post rejection message for post " + post.getID(), e);
} catch (MessagingException e) {
s_log.error("Couldn't send forum post rejection message for post " + post.getID(), e);
}
return;
} else if (!post.getFrom().equals(Kernel.getPublicUser())) {
Notification notice = new Notification();
notice.setTo(post.getFrom());
notice.setHeader(getHeader(post));
Message message = new Message();
Party noticeSender = forum.getModerationGroup();
if (noticeSender == null ) {
noticeSender = post.getModerator();
}
message.setFrom(noticeSender);
message.setSubject("Moderation notice "
+ post.getForum().getDisplayName());
message.setBody(getBody(post, state), Message.TEXT_PLAIN);
if (Mail.getConfig().sendHTMLMessageAsHTMLEmail()) {
notice.setHeader(getHeader(post));
message.setBody(getBody(post, state), Message.TEXT_HTML);
notice.setSignature(getHTMLSignature());
} else {
notice.setHeader(new HtmlToText().convert(getHeader(post)));
message.setBody(new HtmlToText().convert(getBody(post, state)), Message.TEXT_PLAIN);
notice.setSignature(getSignature());
}
notice.setMessage(message);
notice.setSignature(getSignature());
s_log.debug("sending notification" + message +
"\n to: " + post.getFrom().getName() );
notice.save();
}
}
}
}
}

View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.arsdigita.bebop.ColumnPanel;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SaveCancelSection;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormSubmissionListener;
import com.arsdigita.bebop.form.CheckboxGroup;
import com.arsdigita.bebop.form.Option;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.parameters.IntegerParameter;
import com.arsdigita.bebop.parameters.StringInRangeValidationListener;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.forum.ui.Constants;
import com.arsdigita.forum.ui.Text;
import com.arsdigita.web.Application;
import org.apache.log4j.Logger;
/**
* form that allows forum admin to set options
* that apply to this forum instance
*/
public class SetupView
extends Form
implements FormInitListener, FormSubmissionListener, FormProcessListener, Constants {
private static final Logger s_log = Logger.getLogger(SetupView.class);
// values for checkboxes
private static final String MODERATED = "moderated";
private static final String NOTICEBOARD = "noticeboard";
private static final String ALLOW_FILES = "filesAllowed";
private static final String ALLOW_IMAGES = "imagesallowed";
private static final String AUTOSUBSCRIBE_THREAD_STARTERS = "autosubscribe";
private static final String NO_CATEGORY_POSTS_ALLOWED = "nocategory";
private static final String ANONYMOUS_POSTS_ALLOWED = "anonymous";
private CheckboxGroup m_settings;
private TextField m_expiry;
private SaveCancelSection m_saveCancel;
private TextArea m_introduction;
private TextField m_title;
public SetupView() {
super("setupForm", new SimpleContainer("forum:setup", FORUM_XML_NS));
m_settings = new CheckboxGroup("settings");
m_settings.addOption(
new Option(
MODERATED,
(String) Text.gz("forum.ui.settings.moderated").localize()));
m_settings.addOption(
new Option(
NOTICEBOARD,
(String) Text.gz("forum.ui.settings.noticeboard").localize()));
m_settings.addOption(
new Option(
ALLOW_FILES,
(String) Text.gz("forum.ui.settings.allowFiles").localize()));
m_settings.addOption(
new Option(
ALLOW_IMAGES,
(String) Text.gz("forum.ui.settings.allowImages").localize()));
m_settings.addOption(
new Option(
AUTOSUBSCRIBE_THREAD_STARTERS,
(String) Text
.gz("forum.ui.settings.autosubscribe")
.localize()));
m_settings.addOption(
new Option(
NO_CATEGORY_POSTS_ALLOWED,
(String) Text
.gz("forum.ui.settings.noCategoryPosts")
.localize()));
m_settings.addOption(
new Option(
ANONYMOUS_POSTS_ALLOWED,
(String) Text
.gz("forum.ui.settings.anonymousPosts")
.localize()));
m_expiry = new TextField(new IntegerParameter("expiry"));
m_expiry.setMetaDataAttribute("label", (String)Text.gz("forum.ui.noticeboard.expiry_after").localize());
m_saveCancel = new SaveCancelSection();
m_saveCancel.getSaveButton().setButtonLabel(Text.gz("forum.ui.settings.save"));
m_introduction = new TextArea("introduction", 8, 60, TextArea.SOFT);
m_introduction.addValidationListener(new StringInRangeValidationListener(0, 4000, Text.gz("forum.ui.validation.introduction_too_long")));
m_introduction.setMetaDataAttribute("label", (String)Text.gz("forum.ui.settings.introduction").localize());
m_title = new TextField("title");
m_title.setMetaDataAttribute("label", (String)Text.gz("forum.ui.settings.title").localize());
m_title.setSize(70);
add(m_title);
add(m_introduction);
add(m_settings);
add(m_expiry);
add(m_saveCancel);
addInitListener(this);
addSubmissionListener(this);
addProcessListener(this);
}
public void init(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
Forum forum = ForumContext.getContext(state).getForum();
Set settingsSet = new HashSet();
if (forum.isModerated()) {
settingsSet.add(MODERATED);
}
if (forum.isNoticeboard()) {
settingsSet.add(NOTICEBOARD);
}
if (forum.allowFileAttachments()) {
settingsSet.add(ALLOW_FILES);
}
if (forum.allowImageUploads()) {
settingsSet.add(ALLOW_IMAGES);
}
if (forum.autoSubscribeThreadStarter()) {
settingsSet.add(AUTOSUBSCRIBE_THREAD_STARTERS);
}
if (forum.noCategoryPostsAllowed()) {
settingsSet.add(NO_CATEGORY_POSTS_ALLOWED);
}
if (forum.anonymousPostsAllowed()) {
settingsSet.add(ANONYMOUS_POSTS_ALLOWED);
}
m_settings.setValue(state, settingsSet.toArray());
m_expiry.setValue(state, new Integer(forum.getExpireAfter()));
m_introduction.setValue(state, forum.getIntroduction());
m_title.setValue(state, forum.getTitle());
}
public void submitted(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
if (m_saveCancel.getCancelButton().isSelected(state)) {
s_log.debug("cancelled");
throw new FormProcessException("cancelled");
}
}
public void process(FormSectionEvent e) throws FormProcessException {
PageState state = e.getPageState();
Forum forum = ForumContext.getContext(state).getForum();
String[] settingsArray = (String[]) m_settings.getValue(state);
List settings = Collections.EMPTY_LIST;
if (settingsArray != null) {
settings = Arrays.asList(settingsArray);
}
forum.setModerated(settings.contains(MODERATED));
forum.setNoticeboard(settings.contains(NOTICEBOARD));
// could remove any existing files but i don't think that would be desirable
forum.setAllowFileAttachments(settings.contains(ALLOW_FILES));
forum.setAllowImageUploads(settings.contains(ALLOW_IMAGES));
forum.setAutoSubscribeThreadCreator(settings.contains(AUTOSUBSCRIBE_THREAD_STARTERS));
forum.setNoCategoryPostsAllowed(settings.contains(NO_CATEGORY_POSTS_ALLOWED));
forum.setAnonymousPostsAllowed(settings.contains(ANONYMOUS_POSTS_ALLOWED));
forum.setTitle((String)m_title.getValue(state));
forum.setIntroduction((String)m_introduction.getValue(state));
Integer expiry = (m_expiry.getValue(state) == null) ? new Integer(0) : (Integer) m_expiry.getValue(state);
int newExpiry = expiry.intValue();
if (forum.getExpireAfter() != newExpiry) {
forum.setExpireAfter(newExpiry);
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.kernel.Group;
import com.arsdigita.util.Assert;
import org.apache.log4j.Logger;
public class ThreadCreatorEditPane extends SimpleContainer {
private Forum m_forum;
private static final Logger s_log = Logger.getLogger(ThreadCreatorEditPane.class);
public ThreadCreatorEditPane() {
GroupMemberDisplay members = new GroupMemberDisplay() {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
Group g = forum.getThreadCreateGroup();
Assert.exists(g, Group.class);
return g;
}
};
add(members);
Form form = new Form("threadCreateUserPicker",
new BoxPanel(BoxPanel.VERTICAL));
form.add(new GroupMemberPicker("creator") {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
return forum.getThreadCreateGroup();
}
});
form.setIdAttr("threadCreateMemberUserPicker");
add(form);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.ui.admin;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Form;
import com.arsdigita.bebop.BoxPanel;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumContext;
import com.arsdigita.kernel.Group;
import com.arsdigita.util.Assert;
import org.apache.log4j.Logger;
public class ThreadResponderEditPane extends SimpleContainer {
private Forum m_forum;
private static final Logger s_log = Logger.getLogger(ThreadResponderEditPane.class);
public ThreadResponderEditPane() {
GroupMemberDisplay members = new GroupMemberDisplay() {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
Group g = forum.getThreadResponderGroup();
Assert.exists(g, Group.class);
return g;
}
};
add(members);
Form form = new Form("threadReplyUserPicker",
new BoxPanel(BoxPanel.VERTICAL));
form.add(new GroupMemberPicker("responder") {
public Group getGroup(PageState state) {
Forum forum = ForumContext.getContext(state).getForum();
return forum.getThreadResponderGroup();
}
});
form.setIdAttr("threadReplyMemberUserPicker");
add(form);
}
}

View File

@ -57,8 +57,23 @@ public abstract class UserPicker extends FormSection
private TextField m_name;
private List m_userList;
private Paginator m_pg;
// used to differentiate betwen multiple instances on a page (else all get initialised
// with the same value)
private String m_context;
public UserPicker() {
this("");
}
/**
* Constructor gives the picker widget
* a request parameter based on the context
* hence allowing several pickers on one page
* without them getting their parameters
* mixed up
*/
public UserPicker(String context) {
m_context = context;
addWidgets();
addDisplay();
@ -67,12 +82,12 @@ public abstract class UserPicker extends FormSection
}
protected void addWidgets() {
m_nameParam = new StringParameter("name");
m_nameParam = new StringParameter(m_context + "name");
BoxPanel attrs = new BoxPanel( BoxPanel.HORIZONTAL );
Label nameLab = new Label( "Name" );
m_name = new TextField("name1");
m_name = new TextField(m_context + "name1");
attrs.add( nameLab );
attrs.add( m_name );

View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.upgrade;
import org.apache.commons.cli.CommandLine;
import org.apache.log4j.Logger;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.ForumSubscription;
import com.arsdigita.forum.ThreadCollection;
import com.arsdigita.forum.ThreadSubscription;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.GroupCollection;
import com.arsdigita.messaging.MessageThread;
import com.arsdigita.packaging.Program;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.web.ApplicationType;
/**
* @author cgyg9330
*
* Tidy up existing Forum groups
*
*
*/
public class CreateContainerGroups extends Program {
public CreateContainerGroups() {
super("CreateGroups", "1.0.0", "");
}
private static Logger s_log = Logger.getLogger(CreateContainerGroups.class);
public void doRun(CommandLine cmdLine) {
try {
final Session session = SessionManager.getSession();
final TransactionContext tc = session.getTransactionContext();
DataCollection types = session.retrieve(ApplicationType.BASE_DATA_OBJECT_TYPE);
types.addEqualsFilter("objectType", Forum.BASE_DATA_OBJECT_TYPE);
if (types.next()) {
ApplicationType forumApps = (ApplicationType)DomainObjectFactory.newInstance(types.getDataObject());
if (forumApps.getGroup() == null) {
forumApps.createGroup();
types.close();
s_log.debug("created app type group");
System.out.println("created app type group");
} else {
s_log.debug("Forum app type group exists");
System.out.println("Forum app type group exists");
}
}
DataCollection existingForums =
session.retrieve(Forum.BASE_DATA_OBJECT_TYPE);
while (existingForums.next()) {
Forum forum =
(Forum) DomainObjectFactory.newInstance(
existingForums.getDataObject());
s_log.debug("****************" + forum.getTitle());
System.out.println("****************" + forum.getTitle());
Group existingGroup = forum.getGroup();
if (existingGroup == null) {
tc.beginTxn();
forum.createGroup();
Group container = forum.getGroup();
forum.getAdminGroup().setName(forum.getTitle() + " Administrators");
forum.getModerationGroup().setName(forum.getTitle() + " Moderators");
forum.getThreadCreateGroup().setName(forum.getTitle() + " Thread Creators");
forum.getThreadResponderGroup().setName(forum.getTitle() + " Thread Responders");
forum.getReadGroup().setName(forum.getTitle() + " Readers");
container.addSubgroup(forum.getAdminGroup());
container.addSubgroup(forum.getModerationGroup());
container.addSubgroup(forum.getThreadCreateGroup());
container.addSubgroup(forum.getThreadResponderGroup());
container.addSubgroup(forum.getReadGroup());
DataCollection subscriptions = forum.getSubscriptions();
while (subscriptions.next()) {
ForumSubscription forumSubscription =
(ForumSubscription) DomainObjectFactory.newInstance(
subscriptions.getDataObject());
Group subscriptionGroup = forumSubscription.getGroup();
subscriptionGroup.setName(
forumSubscription.getSubscriptionGroupName());
container.addSubgroup(subscriptionGroup);
}
GroupCollection subgroups = container.getSubgroups();
subgroups.addEqualsFilter(
"name",
Forum.THREAD_SUBSCRIPTION_GROUPS_NAME);
if (subgroups.size() == 0) {
Group threadSubscriptions = new Group();
threadSubscriptions.setName(
Forum.THREAD_SUBSCRIPTION_GROUPS_NAME);
container.addSubgroup(threadSubscriptions);
ThreadCollection threads = forum.getThreads();
while (threads.next()) {
MessageThread thread = threads.getMessageThread();
ThreadSubscription subscription =
ThreadSubscription.getThreadSubscription(thread);
if (subscription != null) {
Group group = subscription.getGroup();
group.setName(subscription.getSubscriptionGroupName(forum));
threadSubscriptions.addSubgroup(group);
}
}
}
tc.commitTxn();
} else {
System.out.println(
"group exists for "
+ forum.getTitle()
+ " - skipping to next forum");
}
}
} catch (Throwable e) {
e.printStackTrace();
s_log.error("error occured", e);
}
}
public static final void main(final String[] args) {
new CreateContainerGroups().run(args);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2007 Chris Gilbert. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.forum.upgrade;
import org.apache.commons.cli.CommandLine;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.forum.Forum;
import com.arsdigita.forum.portlet.MyForumsPortlet;
import com.arsdigita.kernel.Group;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.packaging.Program;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.portal.PortletType;
/**
* @author cgyg9330
*
* Used for upgrade from legacy forum
*/
public class CreateGroupsAndPortletType extends Program {
public CreateGroupsAndPortletType() {
super("CreateGroups", "1.0.0", "");
}
public void doRun(CommandLine cmdLine) {
new KernelExcursion() {
public void excurse() {
setEffectiveParty(Kernel.getSystemParty());
final Session session = SessionManager.getSession();
final TransactionContext tc = session.getTransactionContext();
tc.beginTxn();
DataCollection existingForums =
session.retrieve(Forum.BASE_DATA_OBJECT_TYPE);
while (existingForums.next()) {
Forum forum =
(Forum) DomainObjectFactory.newInstance(
existingForums.getDataObject());
Group admin = new Group();
admin.setName(forum.getTitle() + " Administrators");
forum.setAdminGroup(admin);
Group threadCreators = new Group();
threadCreators.setName(forum.getTitle() + " Thread Creators");
forum.setThreadCreatorGroup(threadCreators);
threadCreators.addMember(Kernel.getPublicUser());
Group threadResponders = new Group();
threadResponders.setName(forum.getTitle() + " Thread Responders");
forum.setThreadResponderGroup(threadResponders);
Group threadReaders = new Group();
threadReaders.setName(forum.getTitle() + " Readers");
forum.setReaderGroup(threadReaders);
}
PortletType type = PortletType
.createPortletType("My Forums",
PortletType.WIDE_PROFILE,
MyForumsPortlet.BASE_DATA_OBJECT_TYPE);
type.setDescription("Lists forums that user has access to, with last posting date");
tc.commitTxn();
}
}.run();
}
public static final void main(final String[] args) {
new CreateGroupsAndPortletType().run(args);
}
}

View File

@ -1,395 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>bebop-define</shortname>
<uri>http://www.arsdigita.com/bebop-define/tld/1.0</uri>
<info>this is a tag library for defining Bebop pages via JSP.</info>
<tag>
<name>page</name>
<tagclass>com.arsdigita.bebop.jsp.DefinePage</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefinePageExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>title</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>application</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>master</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>pageClass</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>cache</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>component</name>
<tagclass>com.arsdigita.bebop.jsp.DefineComponentImpl</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineComponentExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>classname</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>form</name>
<tagclass>com.arsdigita.bebop.jsp.DefineForm</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineFormExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>encType</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>method</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>action</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>onSubmit</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>onReset</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>link</name>
<tagclass>com.arsdigita.bebop.jsp.DefineLink</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>url</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>name</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>image</name>
<tagclass>com.arsdigita.bebop.jsp.DefineImage</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>src</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>alt</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>width</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>height</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>border</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>name</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>tabbedPane</name>
<tagclass>com.arsdigita.bebop.jsp.DefineTabbedPane</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>tab</name>
<tagclass>com.arsdigita.bebop.jsp.DefineTab</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>label</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>text</name>
<tagclass>com.arsdigita.bebop.jsp.DefineText</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineWidgetExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>size</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>maxlength</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>password</name>
<tagclass>com.arsdigita.bebop.jsp.DefinePassword</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineWidgetExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>size</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>maxlength</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>textArea</name>
<tagclass>com.arsdigita.bebop.jsp.DefineTextArea</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineWidgetExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>rows</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>cols</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>wrap</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>submit</name>
<tagclass>com.arsdigita.bebop.jsp.DefineSubmit</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>label</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>bundle</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>option</name>
<tagclass>com.arsdigita.bebop.jsp.DefineOption</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>selected</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>bundle</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>radioGroup</name>
<tagclass>com.arsdigita.bebop.jsp.DefineRadioGroup</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>checkboxGroup</name>
<tagclass>com.arsdigita.bebop.jsp.DefineCheckboxGroup</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>vertical</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>select</name>
<tagclass>com.arsdigita.bebop.jsp.DefineSelect</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>onChange</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>multipleSelect</name>
<tagclass>com.arsdigita.bebop.jsp.DefineMultipleSelect</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>slave</name>
<tagclass>com.arsdigita.bebop.jsp.DefineSlave</tagclass>
<bodycontent>empty</bodycontent>
</tag>
<tag>
<name>list</name>
<tagclass>com.arsdigita.bebop.jsp.DefineList</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineListExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>table</name>
<tagclass>com.arsdigita.bebop.jsp.DefineTable</tagclass>
<teiclass>com.arsdigita.bebop.jsp.DefineTableExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>

View File

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>bebop-define</shortname>
<uri>http://www.arsdigita.com/bebop-define/tld/1.0</uri>
<info>this is a tag library for showing components from Bebop pages
inside of a JSP.</info>
<tag>
<name>page</name>
<tagclass>com.arsdigita.bebop.jsp.ShowPage</tagclass>
<teiclass>com.arsdigita.bebop.jsp.ShowPageExtraInfo</teiclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>pageClass</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>master</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>all</name>
<tagclass>com.arsdigita.bebop.jsp.ShowAll</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>component</name>
<tagclass>com.arsdigita.bebop.jsp.ShowComponent</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>form</name>
<tagclass>com.arsdigita.bebop.jsp.ShowForm</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>list</name>
<tagclass>com.arsdigita.bebop.jsp.ShowList</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>listItem</name>
<tagclass>com.arsdigita.bebop.jsp.ShowListItem</tagclass>
<bodycontent>empty</bodycontent>
</tag>
<tag>
<name>table</name>
<tagclass>com.arsdigita.bebop.jsp.ShowTable</tagclass>
<bodycontent>JSP</bodycontent>
<attribute>
<name>name</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>thead</name>
<tagclass>com.arsdigita.bebop.jsp.ShowTableHeader</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>tbody</name>
<tagclass>com.arsdigita.bebop.jsp.ShowTableBody</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>row</name>
<tagclass>com.arsdigita.bebop.jsp.ShowTableRow</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>col</name>
<tagclass>com.arsdigita.bebop.jsp.ShowListItem</tagclass>
<bodycontent>empty</bodycontent>
</tag>
<tag>
<name>slave</name>
<tagclass>com.arsdigita.bebop.jsp.ShowSlave</tagclass>
<bodycontent>JSP</bodycontent>
</tag>
</taglib>

View File

@ -23,15 +23,4 @@
<url-pattern>/main/*</url-pattern>
</servlet-mapping>
<!-- TAGLIBS -->
<taglib>
<taglib-uri>/WEB-INF/bebop-show.tld</taglib-uri>
<taglib-location>/WEB-INF/bebop-show.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/bebop-define.tld</taglib-uri>
<taglib-location>/WEB-INF/bebop-define.tld</taglib-location>
</taglib>
</web-app>

View File

@ -479,10 +479,10 @@
<td width="100%"><xsl:apply-templates select="bebop:select[@name='bodyType']"/></td>
</tr>
<xsl:choose>
<xsl:when test="bebop:select[@name='topic']">
<xsl:when test="bebop:select[@name='postTopic']">
<tr class="even">
<td>Topic:</td>
<td width="100%"><xsl:apply-templates select="bebop:select[@name='topic']"/></td>
<td width="100%"><xsl:apply-templates select="bebop:select[@name='postTopic']"/></td>
</tr>
</xsl:when>
<xsl:when test="forum:message">