1 // The Antville Project 2 // http://code.google.com/p/antville 3 // 4 // Copyright 2007-2011 by Tobi Schäfer. 5 // 6 // Copyright 2001–2007 Robert Gaggl, Hannes Wallnöfer, Tobi Schäfer, 7 // Matthias & Michael Platzer, Christoph Lincke. 8 // 9 // Licensed under the Apache License, Version 2.0 (the ``License''); 10 // you may not use this file except in compliance with the License. 11 // You may obtain a copy of the License at 12 // 13 // http://www.apache.org/licenses/LICENSE-2.0 14 // 15 // Unless required by applicable law or agreed to in writing, software 16 // distributed under the License is distributed on an ``AS IS'' BASIS, 17 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 // See the License for the specific language governing permissions and 19 // limitations under the License. 20 // 21 // $Revision$ 22 // $LastChangedBy$ 23 // $LastChangedDate$ 24 // $URL$ 25 26 /** 27 * @fileOverview Defines the Comment prototype. 28 */ 29 30 markgettext("Comment"); 31 markgettext("comment"); 32 33 /** 34 * @see defineConstants 35 */ 36 Comment.getStatus = defineConstants(Comment, markgettext("deleted"), 37 markgettext("pending"), markgettext("readonly"), markgettext("public")); 38 39 /** 40 * Convenience method for easily adding a new comment to an existing story or comment. 41 * @param {Object} data 42 * @param {Story|Comment} parent The story or comment this comment belongs to. 43 * @returns {Comment} 44 */ 45 Comment.add = function(data, parent) { 46 HopObject.confirmConstructor(Comment); 47 var story = parent.story || parent; 48 var comment = new Comment; 49 comment.name = String.EMPTY; 50 comment.site = parent.site; 51 comment.story = story; 52 comment.parent = parent; 53 comment.parent_type = parent._prototype; // FIXME: Set correct parent_type (Helma bug?) 54 comment.status = Story.PUBLIC; 55 comment.creator = comment.modifier = session.user; 56 comment.created = comment.modified = new Date; 57 comment.update(data); 58 story.comments.add(comment); // Force addition to aggressively cached collection 59 parent.add(comment); 60 return comment; 61 } 62 63 /** 64 * @returns {String} 65 */ 66 Comment.remove = function(options) { 67 if (this.constructor !== Comment) { 68 return; 69 } 70 // Remove all comments of this comment’s creator if corresponding option is set 71 if (options && options.mode === "user" && options.confirm === "1") { 72 var membership = Membership.getByName(this.creator.name, this.site); 73 // Not using HopObject.remove() because it will comepletely remove all comments 74 membership.comments.forEach(function() { 75 Comment.remove.call(this); 76 }) 77 } else { 78 // Mark comment as deleted if not already done so or if there are child comments 79 if (this.size() > 0 && this.status !== Comment.DELETED) { 80 this.status = Comment.DELETED; 81 this.deleteMetadata(); 82 this.touch(); 83 return this.href(); 84 } 85 // Completely remove comment and its children otherwise 86 while (this.size() > 0) { 87 Comment.remove.call(this.get(0)); 88 } 89 // Explicitely remove comment from aggressively cached collections: 90 (this.parent || this).removeChild(this); 91 this.story.comments.removeChild(this); 92 this.deleteMetadata(); 93 this.remove(); 94 } 95 return this.parent.href(); 96 } 97 98 /** 99 * @name Comment 100 * @constructor 101 * @property {Comment[]} _children 102 * @property {String} name 103 * @property {Story|Comment} parent 104 * @property {Story} story 105 * @extends Story 106 */ 107 Comment.prototype.constructor = function() { 108 HopObject.confirmConstructor.call(this); 109 return this; 110 } 111 112 /** 113 * 114 * @param {Object} action 115 * @returns {Boolean} 116 */ 117 Comment.prototype.getPermission = function(action) { 118 switch (action) { 119 case ".": 120 case "main": 121 if (this.status === Comment.DELETED) { 122 return false; 123 } 124 // Break statement missing here by purpose! 125 case "comment": 126 return this.site.commentMode === Site.ENABLED && 127 this.story.getPermission(action) && 128 this.status !== Comment.PENDING; 129 case "delete": 130 return this.story.getPermission.call(this, "delete"); 131 case "edit": 132 return this.status !== Comment.DELETED && 133 this.story.getPermission.call(this, "delete"); 134 } 135 return false; 136 } 137 138 /** 139 * 140 * @param {Object} action 141 * @returns {String} 142 */ 143 Comment.prototype.href = function(action) { 144 var buffer = []; 145 switch (action) { 146 case null: 147 case undefined: 148 case "": 149 case ".": 150 case "main": 151 buffer.push(this.story.href(), "#", this._id); 152 break; 153 default: 154 buffer.push(this.story.comments.href(), this._id, "/", action); 155 } 156 return buffer.join(String.EMPTY); 157 } 158 159 Comment.prototype.edit_action = function() { 160 if (req.postParams.save) { 161 try { 162 this.update(req.postParams); 163 delete session.data.backup; 164 res.message = gettext("The comment was successfully updated.");; 165 res.redirect(this.story.href() + "#" + this._id); 166 } catch (ex) { 167 res.message = ex; 168 app.log(ex); 169 } 170 } 171 172 res.handlers.parent = this.parent; 173 res.data.action = this.href(req.action); 174 res.data.title = gettext("Edit Comment"); 175 res.data.body = this.renderSkinAsString("Comment#edit"); 176 this.site.renderSkin("Site#page"); 177 return; 178 } 179 180 /** 181 * 182 * @param {Object} data 183 */ 184 Comment.prototype.update = function(data) { 185 if (!data.title && !data.text) { 186 throw Error(gettext("Please enter at least something into the “title” or “text” field.")); 187 } 188 // Get difference to current content before applying changes 189 var delta = this.getDelta(data); 190 this.title = data.title; 191 this.text = data.text; 192 this.setMetadata(data); 193 194 if (this.story.commentMode === Story.MODERATED) { 195 this.status = Comment.PENDING; 196 } else if (delta > 50) { 197 this.modified = new Date; 198 if (this.story.status !== Story.CLOSED) { 199 this.site.modified = this.modified; 200 } 201 // We need persistence for adding the callback 202 this.isTransient() && this.persist(); 203 res.handlers.site.callback(this); 204 // Notification is sent in Story.comment_action() 205 } 206 this.clearCache(); 207 this.modifier = session.user; 208 return; 209 } 210 211 /** 212 * @returns {String} 213 */ 214 Comment.prototype.getConfirmText = function() { 215 var size = this.size() + 1; 216 if (this.status === Comment.DELETED && size > 1) { 217 return gettext("You are about to delete a comment thread consisting of {0} postings.", 218 size); 219 } 220 return gettext("You are about to delete a comment by user {0}.", 221 this.creator.name); 222 } 223 224 /** 225 * 226 * @param {String} name 227 * @returns {HopObject} 228 */ 229 Comment.prototype.getMacroHandler = function(name) { 230 if (name === "related") { 231 var membership = Membership.getByName(this.creator.name, this.site); 232 if (!membership || membership.comments.size() < 2 || this.status === Comment.DELETED) { 233 return {}; // Work-around for issue 88 234 } 235 return membership.comments; 236 } 237 return null; 238 } 239 240 /** 241 * 242 */ 243 Comment.prototype.text_macro = function() { 244 if (this.status === Comment.DELETED) { 245 res.write("<em>"); 246 res.write(this.modifier === this.creator ? 247 gettext("This comment was removed by the author.") : 248 gettext("This comment was removed.")); 249 res.writeln("</em>"); 250 } else { 251 res.write(this.text); 252 } 253 return; 254 } 255 256 /** 257 * 258 */ 259 Comment.prototype.creator_macro = function() { 260 return this.status === Comment.DELETED ? null : 261 HopObject.prototype.creator_macro.apply(this, arguments); 262 } 263 264 /** 265 * 266 */ 267 Comment.prototype.modifier_macro = function() { 268 return this.status === Comment.DELETED ? null : 269 HopObject.prototype.modifier_macro.apply(this, arguments); 270 } 271 272 /** 273 * 274 * @param {Object} param 275 * @param {Object} action 276 * @param {Object} text 277 */ 278 Comment.prototype.link_macro = function(param, action, text) { 279 return HopObject.prototype.link_macro.call(this, param, action, text); 280 } 281