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