* Erweitert, so daß per Konfiguration eingestellt werden kann, ob ein Autor einen eigenen Beitrag löschen darf, falls es der letzte Eintrag im Thread ist
 * Dateien werden nun als Download Verknüpft, nicht mehr als direkte Ansicht

git-svn-id: https://svn.libreccm.org/ccm/trunk@1407 8810af33-2d31-482b-a856-94f89814c4df
master
quasi 2011-12-28 08:59:05 +00:00
parent d29337b7f1
commit e7f5bd0a98
9 changed files with 458 additions and 422 deletions

View File

@ -731,6 +731,18 @@ public class Forum extends Application {
party)));
}
/**
* checks if the user can delete posts in this forum
*/
public boolean canDelete(Party party) {
return ((getConfig().canAdminEditPosts()
|| getConfig().canAuthorDeletePosts())
&& PermissionService.checkPermission(
new PermissionDescriptor(PrivilegeDescriptor.DELETE,
this,
party)));
}
public boolean canAdminister(Party party) {
return PermissionService.checkPermission(
new PermissionDescriptor(PrivilegeDescriptor.ADMIN,

View File

@ -49,6 +49,7 @@ public class ForumConfig extends AbstractConfig {
private Parameter m_adminEditPosts;
private Parameter m_authorEditPosts;
private Parameter m_authorDeletePosts;
private Parameter m_digestUserEmail;
private Parameter m_replyHostName;
private Parameter m_disablePageCaching;
@ -74,6 +75,10 @@ public class ForumConfig extends AbstractConfig {
"com.arsdigita.forum.author_can_edit_posts",
Parameter.REQUIRED,
Boolean.TRUE);
m_authorDeletePosts = new BooleanParameter(
"com.arsdigita.forum.author_can_delete_posts",
Parameter.REQUIRED,
Boolean.TRUE);
m_replyHostName = new StringParameter(
"com.arsdigita.forum.reply_host_name",
Parameter.OPTIONAL,
@ -134,6 +139,7 @@ public class ForumConfig extends AbstractConfig {
register(m_digestUserEmail);
register(m_adminEditPosts);
register(m_authorEditPosts);
register(m_authorDeletePosts);
register(m_replyHostName);
register(m_adapters);
register(m_disablePageCaching);
@ -162,6 +168,10 @@ public class ForumConfig extends AbstractConfig {
return ((Boolean)get(m_authorEditPosts)).booleanValue();
}
boolean canAuthorDeletePosts() {
return ((Boolean)get(m_authorDeletePosts)).booleanValue();
}
public String getDigestUserEmail() {
String email = (String)get(m_digestUserEmail);
if (email == null) {

View File

@ -24,10 +24,15 @@ 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.purpose=Whether authors can edit their posts
com.arsdigita.forum.author_can_edit_posts.format=[boolean]
com.arsdigita.forum.author_can_edit_posts.example=true|false
com.arsdigita.forum.author_can_delete_posts.title=Authors can delete posts
com.arsdigita.forum.author_can_delete_posts.purpose=Whether authors can delete their posts as long it is the last in the thread
com.arsdigita.forum.author_can_delete_posts.format=[boolean]
com.arsdigita.forum.author_can_delete_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]

View File

@ -157,6 +157,16 @@ public final class ForumContext {
return m_canAdminister;
}
/**
*
* @return
*/
public boolean canDelete(Post post) {
Party party = Kernel.getContext().getParty();
return post.canDelete(party);
}
/**
*
* @return

View File

@ -114,39 +114,31 @@ import com.arsdigita.util.Assert;
* @author Nobuko Asakai (nasakai@redhat.com)
*/
public class Post extends ThreadedMessage {
private static final Logger s_log = Logger.getLogger(Post.class);
private static final Logger s_log = Logger.getLogger(Post.class);
/** PDL property for marking the approval state of a message, one
* of 'approved', 'rejected', 'reapprove', 'supressed' */
public static final String STATUS = "status";
/** ID of the administrator who last changed the status of a
* 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";
public static final String REJECTED = "rejected";
public static final String REAPPROVE = "reapprove";
public static final String SUPPRESSED = "suppressed";
public static final String POST_STATUS_SUBQUERY =
"com.arsdigita.forum.threadModerationStatus";
private Party m_moderator;
// referred to afterSave method
private boolean m_wasNew;
@ -217,10 +209,10 @@ public class Post extends ThreadedMessage {
BigDecimal id = getID();
// XXX this isn't really the host we want
setRFCMessageID(id + ".bboard@" +
Forum.getConfig().getReplyHostName());
setReplyTo(getRefersTo() + ".bboard@" +
Forum.getConfig().getReplyHostName());
setRFCMessageID(id + ".bboard@"
+ Forum.getConfig().getReplyHostName());
setReplyTo(getRefersTo() + ".bboard@"
+ Forum.getConfig().getReplyHostName());
super.beforeSave();
@ -239,8 +231,8 @@ 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 " +
forum.getOID());
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
@ -281,6 +273,7 @@ public class Post extends ThreadedMessage {
*/
public void sendNotifications(final String context) {
KernelExcursion ex = new KernelExcursion() {
protected void excurse() {
setEffectiveParty(Kernel.getSystemParty());
doSendNotifications(context);
@ -298,6 +291,7 @@ public class Post extends ThreadedMessage {
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());
doSendModeratorAlerts();
@ -305,8 +299,8 @@ 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)");
s_log.debug("not sending moderator alerts because the post "
+ "was pre-approved (created by an approver)");
}
}
@ -318,13 +312,11 @@ public class Post extends ThreadedMessage {
DataCollection subscriptions = forum.getSubscriptions();
while (subscriptions.next()) {
ForumSubscription subscription = (ForumSubscription)
DomainObjectFactory.newInstance(
ForumSubscription subscription = (ForumSubscription) DomainObjectFactory.newInstance(
subscriptions.getDataObject());
s_log.debug("notification to " + subscription.getOID());
subscription.sendNotification(Post.this, Forum.getConfig()
.deleteNotifications());
subscription.sendNotification(Post.this, Forum.getConfig().deleteNotifications());
}
s_log.debug("Sending thread level subsriptions");
@ -344,8 +336,8 @@ public class Post extends ThreadedMessage {
}
} else {
s_log.debug("Not sending notifications because the " +
"message is not approved");
s_log.debug("Not sending notifications because the "
+ "message is not approved");
}
}
@ -361,16 +353,14 @@ public class Post extends ThreadedMessage {
DataCollection alerts = forum.getModerationAlerts();
while (alerts.next()) {
ModerationAlert alert
= (ModerationAlert)
DomainObjectFactory.newInstance(
ModerationAlert alert = (ModerationAlert) DomainObjectFactory.newInstance(
alerts.getDataObject());
s_log.debug("Processing moderation alert " + alert.getOID());
alert.sendNotification(this, Forum.getConfig().deleteNotifications());
}
} else {
s_log.debug("Not sending moderator alerts because the " +
"forum is not moderated");
s_log.debug("Not sending moderator alerts because the "
+ "forum is not moderated");
}
}
@ -381,7 +371,6 @@ public class Post extends ThreadedMessage {
*
* @param forum the Forum that contains this post.
*/
public void setForum(Forum forum) {
setRefersTo(forum);
}
@ -399,12 +388,10 @@ public class Post extends ThreadedMessage {
*
* @param category the Category for this post.
*/
public void mapCategory(Category category)
throws PersistenceException {
if (isNew()) {
throw new PersistenceException
("Post must be persistent to map categories");
throw new PersistenceException("Post must be persistent to map categories");
}
category.addChild(this);
category.save();
@ -413,7 +400,6 @@ public class Post extends ThreadedMessage {
/**
* Clears categories for this post. Used when editing a post
*/
public void clearCategories() {
DataCollection categories =
SessionManager.getSession().retrieve(
@ -504,20 +490,34 @@ public class Post extends ThreadedMessage {
|| getForum().canEdit(party);
}
/**
* Determines if the User has permission to delete this Post.
* Note that you probably don't want to use this over and
* over for a list of messages because the permission check
* on the forum is not cached.
*/
public boolean canDelete(Party party) {
Party author = getFrom(); //determin sender / author of message
// cg added - for anonymous posts, don't allow editing, else everyone
// could edit everyone else's posts
return (!author.equals(Kernel.getPublicUser())
&& Forum.getConfig().canAuthorDeletePosts()
&& author.equals(party))
|| getForum().canDelete(party);
}
public void setStatus(String status) {
Assert.isTrue(
(status.equals(APPROVED)
|| status.equals(REJECTED)
|| status.equals(REAPPROVE)
|| status.equals(SUPPRESSED)
|| status.equals(PENDING)
),
|| status.equals(PENDING)),
"The status must be one of " + APPROVED
+ ", " + REJECTED
+ ", " + REAPPROVE
+ ", " + SUPPRESSED
+ ", the input was " + status
);
+ ", the input was " + status);
set(STATUS, status);
}
@ -566,8 +566,7 @@ public class Post extends ThreadedMessage {
if (m_moderator == null) {
DataObject moderatorData = (DataObject) get(MODERATOR);
if (moderatorData != null) {
m_moderator = (Party) DomainObjectFactory.newInstance
(moderatorData);
m_moderator = (Party) DomainObjectFactory.newInstance(moderatorData);
}
}
return m_moderator;
@ -575,7 +574,6 @@ public class Post extends ThreadedMessage {
// 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() {
s_log.debug("Post - before delete " + getID());
@ -648,8 +646,7 @@ public class Post extends ThreadedMessage {
public DataAssociationCursor getImages() {
DataAssociationCursor images =
((DataAssociation) get(Post.IMAGE_ATTACHMENTS))
.getDataAssociationCursor();
((DataAssociation) get(Post.IMAGE_ATTACHMENTS)).getDataAssociationCursor();
images.addOrder(PostImageAttachment.IMAGE_ORDER);
return images;
}
@ -671,7 +668,6 @@ public class Post extends ThreadedMessage {
// 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();
@ -687,14 +683,12 @@ public class Post extends ThreadedMessage {
public DataAssociationCursor getFiles() {
DataAssociationCursor files =
((DataAssociation) get(Post.FILE_ATTACHMENTS))
.getDataAssociationCursor();
((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.
@ -705,5 +699,4 @@ public class Post extends ThreadedMessage {
replies.addEqualsFilter(STATUS, APPROVED);
}
}

View File

@ -18,16 +18,14 @@
*/
package com.arsdigita.forum;
import com.arsdigita.cms.ContentItem;
import com.arsdigita.cms.Asset;
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;
import com.arsdigita.web.Web;
import com.arsdigita.web.WebConfig;
/**
* @author chris.gilbert@westsussex.gov.uk
@ -62,6 +60,15 @@ public class PostFileAttachmentURLFinder implements URLFinder {
*
*/
public String find(OID oid) throws NoValidURLException {
return s_assetFinder.find(oid);
WebConfig config = Web.getConfig();
StringBuilder url = new StringBuilder();
url.append(config.getDispatcherServletPath());
url.append(config.getDispatcherContextPath());
url.append("/cms-service/download/asset/?asset_id=");
url.append(oid.get(Asset.ID).toString());
return url.toString();
// return s_assetFinder.find(oid);
}
}

View File

@ -356,7 +356,7 @@ public class AttachedFilesStep
while(files.next()) {
PostFileAttachment file = (PostFileAttachment) DomainObjectFactory.newInstance(
files.getDataObject());
DomainObjectXMLRenderer xr = new DomainObjectXMLRenderer(p.newChildElement(FORUM_XML_PREFIX + "files"));
DomainObjectXMLRenderer xr = new DomainObjectXMLRenderer(p.newChildElement(FORUM_XML_PREFIX + ":files", FORUM_XML_NS));
xr.setWrapRoot(false);
xr.setWrapAttributes(true);
xr.setWrapObjects(false);

View File

@ -69,17 +69,14 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
private static final Logger s_log =
Logger.getLogger(DiscussionPostsList.class);
private IntegerParameter m_pageNumber =
new IntegerParameter(PAGINATOR_PARAM);
private int m_pageSize = Forum.getConfig().getThreadPageSize();
private static final String ACTION_EDIT = "edit";
private static final String ACTION_DELETE = "delete";
private static final String ACTION_REPLY = "reply";
private static final String ACTION_APPROVE = "approve";
private static final String ACTION_REJECT = "reject";
private DiscussionThreadSimpleView m_threadMessagesPanel;
private ACSObjectSelectionModel m_post;
@ -95,7 +92,6 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
m_post = post;
}
/**
*
* @param p
@ -128,22 +124,26 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
m_post.setSelectedObject(state, post);
m_threadMessagesPanel.makeEditFormVisible(state);
} else if (ACTION_DELETE.equals(key)) {
Assert.isTrue(ctx.canAdminister(), "can administer forums");
// Assert.isTrue(ctx.canDelete(post), "can administer forums");
MessageThread thread = ctx.getMessageThread();
ThreadedMessage root = thread.getRootMessage();
if (s_log.isDebugEnabled()) {
s_log.debug("message: " + post.getOID() +
" root: " + root.getOID() +
" thread: " + thread.getOID());
s_log.debug("message: " + post.getOID()
+ " root: " + root.getOID()
+ " thread: " + thread.getOID());
}
if (ctx.getForum().isModerated()) {
if (!ctx.canModerate()) {
Assert.isTrue(ctx.canAdminister(), "can administer forums");
post.setStatus(Post.SUPPRESSED);
post.save();
} else if (post.equals(root)) {
Assert.isTrue(ctx.canDelete(post), "can delete posts");
s_log.debug("Deleting entire thread");
post.delete();
@ -151,10 +151,14 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
URL url = URL.there(state.getRequest(), forum, null);
throw new RedirectSignal(url, true);
} else {
Assert.isTrue(ctx.canDelete(post), "can delete posts");
s_log.debug("Deleting message");
post.delete();
}
} else if (post.equals(root)) {
Assert.isTrue(ctx.canDelete(post), "can delete posts");
s_log.debug("Deleting entire thread");
post.delete();
@ -162,6 +166,8 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
URL url = URL.there(state.getRequest(), forum, null);
throw new RedirectSignal(url, true);
} else {
Assert.isTrue(ctx.canDelete(post), "can delete posts");
s_log.debug("Deleting message");
post.delete();
}
@ -199,8 +205,7 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
BigDecimal rootID = context.getMessageThread().
getRootMessage().getID();
DataCollection messages = SessionManager.getSession().retrieve
(Post.BASE_DATA_OBJECT_TYPE);
DataCollection messages = SessionManager.getSession().retrieve(Post.BASE_DATA_OBJECT_TYPE);
// Hide replies if we're in noticeboard mode
if (forum.isNoticeboard()) {
@ -210,21 +215,14 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
FilterFactory ff = messages.getFilterFactory();
messages.addFilter(
ff.or()
.addFilter(ff.and()
.addFilter(ff.equals("root", null))
.addFilter(ff.equals("id", rootID)))
.addFilter(ff.equals("root", rootID)));
ff.or().addFilter(ff.and().addFilter(ff.equals("root", null)).addFilter(ff.equals("id", rootID))).addFilter(ff.equals("root", rootID)));
messages.addOrderWithNull("sortKey", "---", true);
// Add a filter to only show approved messages
if (forum.isModerated() && !forum.canModerate(party)) {
messages.addFilter(ff.or()
.addFilter(ff.equals("status", Post.APPROVED))
.addFilter(ff.equals("sender.id", party == null ?
null : party.getID()))
);
messages.addFilter(ff.or().addFilter(ff.equals("status", Post.APPROVED)).addFilter(ff.equals("sender.id", party == null
? null : party.getID())));
}
return new DomainCollection(messages);
@ -238,15 +236,14 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
@Override
public void generateXML(PageState state,
Element parent) {
Element content = parent.newChildElement(FORUM_XML_PREFIX +
":threadDisplay",
Element content = parent.newChildElement(FORUM_XML_PREFIX
+ ":threadDisplay",
FORUM_XML_NS);
exportAttributes(content);
Forum forum = ForumContext.getContext(state).getForum();
content.addAttribute("forumTitle", forum.getTitle());
content.addAttribute("noticeboard", (new Boolean(forum.isNoticeboard())).
toString());
content.addAttribute("noticeboard", (new Boolean(forum.isNoticeboard())).toString());
DomainCollection messages = getMessages(state);
Integer page = (Integer) state.getValue(m_pageNumber);
@ -284,7 +281,8 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
Element messageEl = content.newChildElement(FORUM_XML_PREFIX + ":message",
FORUM_XML_NS);
generateActionXML(state, messageEl, message);
generateActionXML(state, messageEl, message,
(messages.getPosition() == messages.size()) ? true : false );
DomainObjectXMLRenderer xr = new DomainObjectXMLRenderer(messageEl);
xr.setWrapRoot(false);
@ -299,39 +297,42 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
/**
*
* @param state
* @param parent
* @param post
* @param parent Parent XML element to add any additional elements
* @param post Current post to generate action links for
* @param isLast Post is last in list
*/
protected void generateActionXML(PageState state,
Element parent,
Post post) {
Post post,
boolean isLast) {
ForumContext ctx = ForumContext.getContext(state);
String status = post.getStatus();
if (ctx.canModerate()) {
if (!status.equals(Post.REJECTED) &&
!status.equals(Post.SUPPRESSED) ) {
parent.addAttribute("rejectURL",
makeURL(state, ACTION_REJECT, post));
}
}
if (ctx.canModerate() &&
!post.getStatus().equals(post.APPROVED)) {
parent.addAttribute("approveURL",
makeURL(state, ACTION_APPROVE, post));
}
if (ctx.canAdminister()) {
parent.addAttribute("deleteURL",
makeURL(state, ACTION_DELETE, post));
}
Party party = Kernel.getContext().getParty();
if (party == null) {
party = Kernel.getPublicUser();
}
String status = post.getStatus();
if (ctx.canModerate()) {
if (!status.equals(Post.REJECTED)
&& !status.equals(Post.SUPPRESSED)) {
parent.addAttribute("rejectURL",
makeURL(state, ACTION_REJECT, post));
}
}
if (ctx.canModerate()
&& !post.getStatus().equals(post.APPROVED)) {
parent.addAttribute("approveURL",
makeURL(state, ACTION_APPROVE, post));
}
if (ctx.canAdminister() || (post.canDelete(party) && isLast)) {
parent.addAttribute("deleteURL",
makeURL(state, ACTION_DELETE, post));
}
if (post.canEdit(party)) {
parent.addAttribute("editURL",
makeURL(state, ACTION_EDIT, post));
@ -341,8 +342,7 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
PrivilegeDescriptor.get(Forum.RESPOND_TO_THREAD_PRIVILEGE),
Kernel.getContext().getResource(), party);
if (!ctx.getForum().isNoticeboard() && PermissionService.
checkPermission(canRespond)) {
if (!ctx.getForum().isNoticeboard() && PermissionService.checkPermission(canRespond)) {
parent.addAttribute("replyURL",
makeURL(state, ACTION_REPLY, post));
}
@ -371,8 +371,8 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
long begin,
long end,
long objectCount) {
Element paginator = parent.newChildElement(FORUM_XML_PREFIX +
":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());
@ -395,5 +395,4 @@ public class DiscussionPostsList extends SimpleComponent implements Constants {
paginator.addAttribute("objectEnd", XML.format(new Long(end)));
paginator.addAttribute("objectCount", XML.format(new Long(objectCount)));
}
}