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  * @returns {String}
 41  */
 42 Comment.remove = function(options) {
 43    if (this.constructor !== Comment) {
 44       return;
 45    }
 46    // Remove all comments of this comment’s creator if corresponding option is set
 47    if (options && options.mode === "user" && options.confirm === "1") {
 48       var membership = Membership.getByName(this.creator.name, this.site);
 49       // Not using HopObject.remove() because it will comepletely remove all comments
 50       membership.comments.forEach(function() {
 51          Comment.remove.call(this);
 52       })
 53    } else {
 54       // Mark comment as deleted if not already done so or if there are child comments
 55       if (this.size() > 0 && this.status !== Comment.DELETED) {
 56          this.status = Comment.DELETED;
 57          this.deleteMetadata();
 58          this.touch();
 59          return this.href();
 60       }
 61       // Completely remove comment and its children otherwise
 62       while (this.size() > 0) {
 63          Comment.remove.call(this.get(0));
 64       }
 65       // Explicitely remove comment from aggressively cached collections:
 66       (this.parent || this).removeChild(this);
 67       this.story.comments.removeChild(this);
 68       this.deleteMetadata();
 69       this.remove();
 70    }
 71    return this.parent.href();
 72 }
 73 
 74 /**
 75  * @name Comment
 76  * @constructor
 77  * @param {Object} parent
 78  * @property {Comment[]} _children
 79  * @property {String} name
 80  * @property {Story|Comment} parent
 81  * @property {Story} story
 82  * @extends Story
 83  */
 84 Comment.prototype.constructor = function(parent) {
 85    this.name = String.EMPTY;
 86    this.site = parent.site;
 87    this.story = parent.story || parent;
 88    this.parent = parent;
 89    // FIXME: Correct parent_type (Helma bug?)
 90    this.parent_type = parent._prototype;
 91    this.status = Story.PUBLIC;
 92    this.creator = this.modifier = session.user;
 93    this.created = this.modified = new Date;
 94    return this;
 95 }
 96 
 97 /**
 98  * 
 99  * @param {Object} action
100  * @returns {Boolean}
101  */
102 Comment.prototype.getPermission = function(action) {
103    switch (action) {
104       case ".":
105       case "main":
106       if (this.status === Comment.DELETED) {
107          return false;
108       }
109       // Break statement missing here by purpose!
110       case "comment":
111       return this.site.commentMode === Site.ENABLED &&
112             this.story.getPermission(action) && 
113             this.status !== Comment.PENDING;
114       case "delete":
115       return this.story.getPermission.call(this, "delete");
116       case "edit":
117       return this.status !== Comment.DELETED &&
118             this.story.getPermission.call(this, "delete");
119    }
120    return false;
121 }
122 
123 /**
124  * 
125  * @param {Object} action
126  * @returns {String}
127  */
128 Comment.prototype.href = function(action) {
129    var buffer = [];
130    switch (action) {
131       case null:
132       case undefined:
133       case "":
134       case ".":
135       case "main":
136       buffer.push(this.story.href(), "#", this._id);
137       break;
138       default:
139       buffer.push(this.story.comments.href(), this._id, "/", action);
140    }
141    return buffer.join(String.EMPTY);
142 }
143 
144 Comment.prototype.edit_action = function() {
145    if (req.postParams.save) {
146       try {
147          this.update(req.postParams);
148          delete session.data.backup;
149          res.message = gettext("The comment was successfully updated.");;
150          res.redirect(this.story.href() + "#" + this._id);
151       } catch (ex) {
152          res.message = ex;
153          app.log(ex);
154       }
155    }
156    
157    res.handlers.parent = this.parent;
158    res.data.action = this.href(req.action);
159    res.data.title = gettext("Edit Comment");
160    res.data.body = this.renderSkinAsString("Comment#edit");
161    this.site.renderSkin("Site#page");
162    return;
163 }
164 
165 /**
166  * 
167  * @param {Object} data
168  */
169 Comment.prototype.update = function(data) {
170    if (!data.title && !data.text) {
171       throw Error(gettext("Please enter at least something into the “title” or “text” field."));
172    }
173    // Get difference to current content before applying changes
174    var delta = this.getDelta(data);
175    this.title = data.title;
176    this.text = data.text;
177    this.setMetadata(data);
178 
179    if (this.story.commentMode === Story.MODERATED) {
180       this.status = Comment.PENDING;
181    } else if (delta > 50) {
182       this.modified = new Date;
183       if (this.story.status !== Story.CLOSED) { 
184          this.site.modified = this.modified;
185       }
186       // We need persistence for adding the callback
187       this.isTransient() && this.persist();
188       res.handlers.site.callback(this);
189       // Notification is sent in Story.comment_action()
190    }
191    this.clearCache();
192    this.modifier = session.user;
193    return;
194 }
195 
196 /**
197  * @returns {String}
198  */
199 Comment.prototype.getConfirmText = function() {
200    var size = this.size() + 1;
201    if (this.status === Comment.DELETED && size > 1) {
202       return gettext("You are about to delete a comment thread consisting of {0} postings.",
203             size);
204    }
205    return gettext("You are about to delete a comment by user {0}.", 
206          this.creator.name);
207 }
208 
209 /**
210  * 
211  * @param {String} name
212  * @returns {HopObject} 
213  */
214 Comment.prototype.getMacroHandler = function(name) {
215    if (name === "related") {
216       var membership = Membership.getByName(this.creator.name, this.site);
217       if (!membership || membership.comments.size() < 2 || this.status === Comment.DELETED) {
218          return {}; // Work-around for issue 88
219       }
220       return membership.comments;
221    }
222    return null;
223 }
224 
225 /**
226  * 
227  */
228 Comment.prototype.text_macro = function() {
229    if (this.status === Comment.DELETED) {
230       res.write("<em>");
231       res.write(this.modifier === this.creator ? 
232             gettext("This comment was removed by the author.") : 
233             gettext("This comment was removed."));
234       res.writeln("</em>");
235    } else {
236       res.write(this.text);
237    }
238    return;
239 }
240 
241 /**
242  *
243  */
244 Comment.prototype.creator_macro = function() {
245    return this.status === Comment.DELETED ? null :
246          HopObject.prototype.creator_macro.apply(this, arguments);
247 }
248 
249 /**
250  * 
251  */
252 Comment.prototype.modifier_macro = function() {
253    return this.status === Comment.DELETED ? null :
254          HopObject.prototype.modifier_macro.apply(this, arguments);
255 }
256 
257 /**
258  * 
259  * @param {Object} param
260  * @param {Object} action
261  * @param {Object} text
262  */
263 Comment.prototype.link_macro = function(param, action, text) {
264    return HopObject.prototype.link_macro.call(this, param, action, text);
265 }
266