1 //
  2 // The Antville Project
  3 // http://code.google.com/p/antville
  4 //
  5 // Copyright 2001-2007 by The Antville People
  6 //
  7 // Licensed under the Apache License, Version 2.0 (the ``License'');
  8 // you may not use this file except in compliance with the License.
  9 // You may obtain a copy of the License at
 10 //
 11 //    http://www.apache.org/licenses/LICENSE-2.0
 12 //
 13 // Unless required by applicable law or agreed to in writing, software
 14 // distributed under the License is distributed on an ``AS IS'' BASIS,
 15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16 // See the License for the specific language governing permissions and
 17 // limitations under the License.
 18 //
 19 // $Revision$
 20 // $LastChangedBy$
 21 // $LastChangedDate$
 22 // $URL$
 23 //
 24 
 25 /**
 26  * @fileOverview Defines the extensions of Helma’s built-in 
 27  * HopObject prototype.
 28  */
 29 
 30 /**
 31  * 
 32  * @param {HopObject} collection
 33  */
 34 HopObject.remove = function(collection) {
 35    var item;
 36    while (collection.size() > 0) {
 37       item = collection.get(0);
 38       if (item.constructor.remove) {
 39          item.constructor.remove.call(item, item);
 40       }
 41    }
 42    return;
 43 }
 44 
 45 /**
 46  * 
 47  * @param {String} name
 48  * @param {HopObject} collection
 49  */
 50 HopObject.getFromPath = function(name, collection) {
 51    if (name) {
 52       var site;
 53       if (name.contains("/")) {
 54          var parts = name.split("/");
 55          site = root.get(parts[0]);
 56          name = parts[1];
 57       } else {
 58          site = res.handlers.site;
 59       }
 60       if (site && site.getPermission("main")) {
 61          return site[collection].get(name);
 62       }
 63    }
 64    return null;
 65 }
 66 
 67 /**
 68  * Helma’s built-in HopObject with Antville’s extensions.
 69  * @name HopObject
 70  * @constructor
 71  */
 72 
 73 /**
 74  * 
 75  * @param {Object} values
 76  */
 77 HopObject.prototype.map = function(values) {
 78    for (var i in values) {
 79       this[i] = values[i];
 80    }
 81    return;
 82 }
 83 
 84 /**
 85  * 
 86  */
 87 HopObject.prototype.onRequest = function() {
 88    // Checking if we are on the correct host to prevent at least some XSS issues
 89    if (req.action !== "notfound" && req.action !== "error" && this.href().startsWith("http://") && 
 90          !this.href().toLowerCase().startsWith(req.servletRequest.scheme + 
 91          "://" + req.servletRequest.serverName.toLowerCase())) {   
 92       res.redirect(this.href(req.action === "main" ? String.EMPTY : req.action));
 93    }
 94 
 95    User.autoLogin();
 96    res.handlers.membership = User.getMembership();
 97    
 98    if (User.getCurrentStatus() === User.BLOCKED) {
 99       User.logout();
100       res.status = 401;
101       res.writeln(gettext("Sorry, your account has been blocked."));
102       res.writeln(gettext("Please contact the maintainer of this site for further information."));
103       res.stop();
104    }
105    
106    if (res.handlers.site.status === Site.BLOCKED && 
107          !User.require(User.PRIVILEGED)) {
108       res.status = 401;
109       res.writeln(gettext("Sorry, this site has been blocked."));
110       res.writeln(gettext("Please contact the maintainer of this site for further information."));
111       res.stop();
112    }
113 
114    if (!this.getPermission(req.action)) {
115       if (!session.user) {
116          User.setLocation(root.href() + req.path);
117          res.message = gettext("Please login first.");
118          res.redirect(res.handlers.site.members.href("login"));
119       }
120       User.getLocation();
121       res.status = 401;
122       res.write(gettext("Sorry, you are not allowed to access this part of the site."));
123       res.stop();
124    }
125    
126    res.handlers.layout = res.handlers.site.layout || new Layout;
127    res.skinpath = res.handlers.layout.getSkinPath();
128 
129    res.meta.values = {};
130    res.handlers.site.renderSkinAsString("Site#values");
131    return;
132 }
133 
134 /**
135  * @returns Boolean
136  */
137 HopObject.prototype.getPermission = function() {
138    return true;
139 }
140 
141 HopObject.prototype.delete_action = function() {
142    if (req.postParams.proceed) {
143       try {
144          var str = this.toString();
145          var parent = this._parent;
146          var url = this.constructor.remove.call(this, this) || parent.href();
147          res.message = gettext("{0} was successfully deleted.", str);
148          res.redirect(User.getLocation() || url);
149       } catch(ex) {
150          res.message = ex;
151          app.log(ex);
152       }
153    }
154 
155    res.data.action = this.href(req.action);
156    res.data.title = gettext("Confirm deletion of {0}", this);
157    res.data.body = this.renderSkinAsString("$HopObject#confirm", {
158       text: gettext('You are about to delete {0}.', this)
159    });
160    res.handlers.site.renderSkin("Site#page");
161    return;
162 }
163 
164 /**
165  * @returns {Object}
166  */
167 HopObject.prototype.touch = function() {
168    return this.map({
169       modified: new Date,
170       modifier: session.user
171    });
172 }
173 
174 /**
175  * 
176  */
177 HopObject.prototype.log = function() {
178    var entry = new LogEntry(this, "main");
179    app.data.entries.push(entry);
180    return;
181 }
182 
183 /**
184  * 
185  * @param {String} action
186  */
187 HopObject.prototype.notify = function(action) {
188    var site = res.handlers.site;
189    if (site.notificationMode === Site.NOBODY) {
190       return;
191    }
192    switch (action) {
193       case "comment":
194       action = "create"; break;
195    }
196    var membership;
197    var currentMembership = res.handlers.membership;
198    for (var i=0; i<site.members.size(); i+=1) {
199       res.handlers.membership = membership = site.members.get(i);
200       if (membership.require(site.notificationMode)) {
201          sendMail(root.email, membership.creator.email,
202                gettext("Notification of changes at site {0}", site.title),
203                this.renderSkinAsString("$HopObject#notify_" + action));
204       }
205    }
206    res.handlers.membership = currentMembership;
207    return;
208 }
209 
210 /**
211  * @returns {Tag[]}
212  */
213 HopObject.prototype.getTags = function() {
214    var tags = [];
215    if (typeof this.tags === "object") {
216       this.tags.list().forEach(function(item) {
217          item.tag && tags.push(item.tag.name);
218       });
219    }
220    return tags;
221 }
222 
223 /**
224  * 
225  * @param {Tag[]|String} tags
226  */
227 HopObject.prototype.setTags = function(tags) {
228    if (typeof this.tags !== "object") {
229       return String.EMPTY;
230    }
231 
232    if (!tags) {
233       tags = [];
234    } else if (tags.constructor === String) {
235       tags = tags.split(/\s*,\s*/);
236    }
237    
238    var diff = {};
239    var tag;
240    for (var i in tags) {
241       // Trim and remove troublesome characters  (like ../.. etc.)
242       // We call getAccessName with a virgin HopObject to allow most names
243       tag = tags[i] = this.getAccessName.call(new HopObject, File.getName(tags[i]));
244       if (tag && diff[tag] == null) {
245          diff[tag] = 1;
246       }
247    }
248    this.tags.forEach(function() {
249       if (!this.tag) {
250          return;
251       }
252       diff[this.tag.name] = (tags.indexOf(this.tag.name) < 0) ? this : 0;
253    });
254    
255    for (var tag in diff) {
256       switch (diff[tag]) {
257          case 0:
258          // Do nothing (tag already exists)
259          break;
260          case 1:
261          // Add tag to story
262          this.addTag(tag);
263          break;
264          default:
265          // Remove tag
266          this.removeTag(diff[tag]);
267       }
268    }
269    return;
270 }
271 
272 /**
273  * 
274  * @param {String} name
275  */
276 HopObject.prototype.addTag = function(name) {
277    this.tags.add(new TagHub(name, this, session.user));
278    return;
279 }
280 
281 /**
282  * 
283  * @param {String} tag
284  */
285 HopObject.prototype.removeTag = function(tag) {
286    var parent = tag._parent;
287    if (parent.size() === 1) {
288       parent.remove();
289    }
290    tag.remove();
291    return;
292 }
293 
294 /**
295  * 
296  * @param {Object} param
297  * @param {String} name
298  */
299 HopObject.prototype.skin_macro = function(param, name) {
300    if (!name) {
301       return;
302    }
303    if (name.contains("#")) {
304       this.renderSkin(name);
305    } else {
306       var prototype = this._prototype || "Global";
307       this.renderSkin(prototype + "#" + name);
308    }
309    return;
310 }
311 
312 /**
313  * 
314  * @param {Object} param
315  * @param {String} name
316  */
317 HopObject.prototype.input_macro = function(param, name) {
318    param.name = name;
319    param.id = name;
320    param.value = this.getFormValue(name);
321    return html.input(param);
322 }
323 
324 /**
325  * 
326  * @param {Object} param
327  * @param {String} name
328  */
329 HopObject.prototype.textarea_macro = function(param, name) {
330    param.name = name;
331    param.id = name;
332    param.value = this.getFormValue(name);
333    return html.textArea(param);
334 }
335 
336 /**
337  * 
338  * @param {Object} param
339  * @param {String} name
340  */
341 HopObject.prototype.select_macro = function(param, name) {
342    param.name = name;
343    param.id = name;
344    var options = this.getFormOptions(name);
345    if (options.length < 2) {
346       param.disabled = "disabled";
347    }
348    return html.dropDown(param, options, this.getFormValue(name));
349 }
350 
351 /**
352  * 
353  * @param {Object} param
354  * @param {String} name
355  */
356 HopObject.prototype.checkbox_macro = function(param, name) {
357    param.name = name;
358    param.id = name;
359    var options = this.getFormOptions(name);
360    if (options.length < 2) {
361       param.disabled = "disabled";
362    }
363    param.value = String((options[1] || options[0]).value);
364    param.selectedValue = String(this.getFormValue(name));
365    var label = param.label;
366    delete param.label;
367    html.checkBox(param);
368    if (label) {
369       html.element("label", label, {"for": name});
370    }
371    return;
372 }
373 
374 /**
375  * 
376  * @param {Object} param
377  * @param {String} name
378  */
379 HopObject.prototype.radiobutton_macro = function(param, name) {
380    param.name = name;
381    param.id = name;
382    var options = this.getFormOptions(name);
383    if (options.length < 2) {
384       param.disabled = "disabled";
385    }
386    param.value = String(options[0].value);
387    param.selectedValue = String(this.getFormValue(name));
388    var label = param.label;
389    delete param.label;
390    html.radioButton(param);
391    if (label) {
392       html.element("label", label, {"for": name});
393    }
394    return;
395 }
396 
397 /**
398  * 
399  * @param {Object} param
400  * @param {String} name
401  */
402 HopObject.prototype.upload_macro = function(param, name) {
403    param.name = name;
404    param.id = name;
405    param.value = this.getFormValue(name);
406    renderSkin("$Global#upload", param);
407    return;
408 }
409 
410 /**
411  * 
412  * @param {Object} param
413  * @param {HopObject} [handler]
414  */
415 HopObject.prototype.macro_macro = function(param, handler) {
416    var ctor = this.constructor;
417    if ([Story, Image, File, Poll].indexOf(ctor) > -1) {
418       res.encode("<% ");
419       res.write(handler || ctor.name.toLowerCase());
420       res.write(String.SPACE);
421       res.write(quote(this.name || this._id));
422       res.encode(" %>");
423    }
424    return;
425 }
426 
427 /**
428  * 
429  */
430 HopObject.prototype.kind_macro = function() {
431    var type = this.constructor.name.toLowerCase();
432    switch (type) {
433       default:
434       res.write(gettext(type));
435       break;
436    }
437    return;
438 }
439 
440 /**
441  * 
442  * @param {String} name
443  * @returns {Number|String}
444  */
445 HopObject.prototype.getFormValue = function(name) {
446    if (req.isPost()) {
447       return req.postParams[name];
448    } else {
449       var value = this[name] || req.queryParams[name] || String.EMPTY;
450       return value instanceof HopObject ? value._id : value;
451    }
452 }
453 
454 /**
455  * @returns {Object[]}
456  */
457 HopObject.prototype.getFormOptions = function() {
458    return [{value: true, display: "enabled"}];
459 }
460 
461 /**
462  * @returns {HopObject}
463  */
464 HopObject.prototype.self_macro = function() {
465    return this;
466 }
467 
468 /**
469  * 
470  */
471 HopObject.prototype.type_macro = function() {
472    return res.write(this.constructor.name);
473 }
474 
475 /**
476  * 
477  * @param {Object} param
478  * @param {String} url
479  * @param {String} text
480  */
481 HopObject.prototype.link_macro = function(param, url, text) {
482    url || (url = ".");
483    var action = url.split(/#|\?/)[0];
484    if (this.getPermission(action)) {
485       text || (text = (action === "." ? this._id : action).capitalize());
486       renderLink.call(global, param, url, gettext(text), this);
487    }
488    return;
489 }
490 
491 /**
492  * 
493  * @param {Object} param
494  * @param {String} format
495  */
496 HopObject.prototype.created_macro = function(param, format) {
497    if (this.isPersistent()) {
498       format || (format = param.format);
499       res.write(formatDate(this.created, format));
500    }
501    return;
502 }
503 
504 /**
505  * 
506  * @param {Object} param
507  * @param {String} format
508  */
509 HopObject.prototype.modified_macro = function(param, format) {
510    if (this.isPersistent()) {
511       format || (format = param.format);
512       res.write(formatDate(this.modified, format));
513    }
514    return;
515 }
516 
517 /**
518  * 
519  * @param {Object} param
520  * @param {String} mode
521  */
522 HopObject.prototype.creator_macro = function(param, mode) {
523    if (!this.creator || this.isTransient()) {
524       return;
525    }
526    mode || (mode = param.as);
527    if (mode === "link" && this.creator.url) {
528       html.link({href: this.creator.url}, this.creator.name);
529    } else if (mode === "url") {
530       res.write(this.creator.url);
531    } else {
532       res.write(this.creator.name);
533    } return;
534 }
535 
536 /**
537  * 
538  * @param {Object} param
539  * @param {String} mode
540  */
541 HopObject.prototype.modifier_macro = function(param, mode) {
542    if (!this.modifier || this.isTransient()) {
543       return;
544    }
545    mode || (mode = param.as);
546    if (mode === "link" && this.modifier.url) {
547       html.link({href: this.modifier.url}, this.modifier.name);
548    } else if (mode === "url") {
549       res.write(this.modifier.url);
550    } else {
551       res.write(this.modifier.name);
552    }
553    return;
554 }
555 
556 /**
557  * @returns {String}
558  */
559 HopObject.prototype.getTitle = function() {
560    return this.title || this.__name__.capitalize();
561 }
562 
563 /**
564  * @returns {String}
565  */
566 HopObject.prototype.toString = function() {
567    return this.constructor.name + " #" + this._id;
568 }
569 
570 /**
571  * 
572  * @param {String} text
573  * @param {Object} param
574  * @param {String} action
575  * @returns {String}
576  */
577 HopObject.prototype.link_filter = function(text, param, action) {
578    action || (action = ".");
579    res.push();
580    renderLink(param, action, text, this);
581    return res.pop();
582 }
583 
584 /**
585  * 
586  * @param {String} name
587  */
588 HopObject.prototype.handleMetadata = function(name) {
589    this.__defineGetter__(name, function() {
590       return this.metadata.get(name);
591    });
592    this.__defineSetter__(name, function(value) {
593       return this.metadata.set(name, value);
594    });
595    this[name + "_macro"] = function(param) {
596       var value;
597       if (value = this[name]) {
598          res.write(value);
599       }
600       return;
601    };
602    return;
603 }
604