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