      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