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