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 global variables and functions. 27 */ 28 29 app.addRepository(app.dir + "/../lib/rome-1.0.jar"); 30 app.addRepository(app.dir + "/../lib/jdom.jar"); 31 app.addRepository(app.dir + "/../lib/itunes-0.4.jar"); 32 33 app.addRepository("modules/core/Global.js"); 34 app.addRepository("modules/core/HopObject.js"); 35 app.addRepository("modules/core/Number.js"); 36 app.addRepository("modules/core/Filters.js"); 37 38 app.addRepository("modules/helma/Image.js"); 39 app.addRepository("modules/helma/Html.js"); 40 app.addRepository("modules/helma/Http.js"); 41 app.addRepository("modules/helma/Mail.js"); 42 app.addRepository("modules/helma/Zip.js"); 43 44 app.addRepository("modules/jala/code/Date.js"); 45 app.addRepository("modules/jala/code/HopObject.js"); 46 app.addRepository("modules/jala/code/ListRenderer.js"); 47 app.addRepository("modules/jala/code/Utilities.js"); 48 49 app.addRepository(app.dir + "/../i18n/messages.en.js"); 50 app.addRepository(app.dir + "/../i18n/messages.de.js"); 51 app.addRepository("modules/jala/code/I18n.js"); // Pls. test before moving around! 52 53 // FIXME: Overwriting some String patterns and methods with more reliable ones 54 // Regex contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ 55 // licensed unter MIT license - http://www.opensource.org/licenses/mit-license.php 56 /** 57 * @name String 58 * @namespace 59 */ 60 /** @constant */ 61 String.EMAILPATTERN = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i; 62 /** @constant */ 63 String.URLPATTERN = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; 64 65 // FIXME: Be careful with property names of app.data; 66 // they inherit all properties from HopObject! 67 68 /** 69 * @name app.data 70 * @namespace 71 */ 72 /** @name app.data.entries */ 73 app.data.entries || (app.data.entries = []); 74 /** @name app.data.mails */ 75 app.data.mails || (app.data.mails = []); 76 /** @name app.data.requests */ 77 app.data.requests || (app.data.requests = {}); 78 /** @name app.data.callbacks */ 79 app.data.callbacks || (app.data.callbacks = []); 80 /** @name app.data.exports */ 81 app.data.exports || (app.data.exports = []); 82 /** @name app.data.imports */ 83 app.data.imports || (app.data.imports = []); 84 85 /** 86 * @name helma.Mail 87 * @namespace 88 */ 89 /** 90 * Extend the Mail prototype with a method that simply adds a mail object 91 * to an application-wide array (mail queue). 92 * @returns {Number} The number of mails waiting in the queue 93 */ 94 helma.Mail.prototype.queue = function() { 95 return app.data.mails.push(this); 96 } 97 98 /** 99 * 100 */ 101 helma.Mail.flushQueue = function() { 102 if (app.data.mails.length > 0) { 103 app.debug("Flushing mail queue, sending " + 104 app.data.mails.length + " messages"); 105 var mail; 106 while (app.data.mails.length > 0) { 107 mail = app.data.mails.pop(); 108 mail.send(); 109 if (mail.status > 0) { 110 app.debug("Error while sending e-mail (status " + mail.status + ")"); 111 mail.writeToFile(getProperty("smtp.dir")); 112 } 113 } 114 } 115 return; 116 } 117 118 jala.i18n.setLocaleGetter(function() { 119 return res.handlers.site.getLocale(); 120 }); 121 122 /** @constant */ 123 var SHORTDATEFORMAT = "yyyy-MM-dd HH:mm"; 124 /** @constant */ 125 var LONGDATEFORMAT = "EEEE, d. MMMM yyyy, HH:mm"; 126 /** @constant */ 127 var SQLDATEFORMAT = "yyyy-MM-dd HH:mm:ss"; 128 129 /** */ 130 var idle = new Function; 131 /** */ 132 var html = new helma.Html(); 133 134 /** */ 135 var rome = new JavaImporter( 136 Packages.com.sun.syndication.feed.synd, 137 Packages.com.sun.syndication.io.SyndFeedOutput, 138 Packages.com.sun.syndication.feed.module.itunes, 139 Packages.com.sun.syndication.feed.module.itunes.types 140 ); 141 142 /** 143 * 144 */ 145 function onStart() { 146 if (typeof root === "undefined") { 147 app.logger.error("Error in database configuration: no root site found."); 148 return; 149 } 150 app.addCronJob("nightly", "*", "*", "*", "*", "5", "0"); 151 // FIXME: Does database exist? 152 /*var db = getDBConnection("antville"); 153 var rows = db.executeRetrieval("select min(id) as id from site"); 154 rows.next(); 155 var id = rows.getColumnItem("id"); 156 //Packages.helma.main.Server.getServer().stopApplication(app.name); 157 rows.release();*/ 158 return; 159 } 160 161 /** 162 * 163 * @param {HopObject} ctor 164 * @returns {Function} 165 */ 166 function defineConstants(ctor /*, arguments */) { 167 var constants = [], name; 168 for (var i=1; i<arguments.length; i+=1) { 169 name = arguments[i].toUpperCase().replace(/\s/g, ""); 170 if (ctor[name]) { 171 //app.logger.warn("Constant already defined: " + ctor.name + "." + name); 172 } 173 ctor[name] = arguments[i]; 174 constants.push({ 175 value: arguments[i], 176 display: arguments[i] 177 }); 178 } 179 return function() { 180 return constants; 181 }; 182 } 183 184 /** 185 * Disable a macro with the idle function 186 * @param {HopObject} ctor 187 * @param {String} name 188 * @returns {Function} 189 */ 190 function disableMacro(ctor, name) { 191 return ctor.prototype[name + "_macro"] = idle; 192 } 193 194 /** 195 * @returns {Number} The period in milliseconds the scheduler will be 196 * called again. 197 */ 198 function scheduler() { 199 Root.commitEntries(); 200 Root.commitRequests(); 201 Root.invokeCallbacks(); 202 Root.updateHealth(); 203 Root.exportImport(); 204 helma.Mail.flushQueue(); 205 return 5000; 206 } 207 208 /** 209 * 210 */ 211 function nightly() { 212 Root.purgeReferrers(); 213 //Admin.purgeDatabase(); 214 return; 215 } 216 217 /** 218 * Renders a string depending on the comparison of two values. If the first 219 * value equals the second value, the first result will be returned; the 220 * second result otherwise. 221 * <p>Example: <code><% if <% macro %> is "value" then "yes!" else "no :(" %></code> 222 * </p> 223 * Note that any value or result can be a macro, too. Thus, this can be used as 224 * a simple implementation of an if-then-else statement by using Helma macros 225 * only. 226 * @param {Object} param The default Helma macro parameter object 227 * @param {String} firstValue The first value 228 * @param {String} _is_ Syntactic sugar; should be "is" for legibility 229 * @param {String} secondValue The second value 230 * @param {String} _then_ Syntactic sugar; should be "then" for legibility 231 * @param {String} firstResult The first result, ie. the value that will be 232 * returned if the first value equals the second one 233 * @param {String} _else_ Syntactic sugar; should be "else" for legibility 234 * @param {String} secondResult The second result, ie. the value that will be 235 * returned if the first value does not equal the second one 236 * @returns {String} The resulting value 237 */ 238 function if_macro(param, firstValue, _is_, secondValue, _then_, firstResult, 239 _else_, secondResult) { 240 return (("" + firstValue) == ("" + secondValue)) ? firstResult : secondResult; 241 } 242 243 /** 244 * 245 * @param {Object} param 246 * @param {String} text 247 */ 248 function gettext_macro(param, text /*, value1, value2, ...*/) { 249 var args = [text]; 250 for (var i=2; i<arguments.length; i+=1) { 251 args.push(arguments[i]); 252 } 253 return gettext.apply(this, args); 254 } 255 256 /** 257 * 258 * @param {Object} param 259 * @param {String} singular 260 * @param {String} plural 261 */ 262 function ngettext_macro(param, singular, plural /*, value1, value2, ...*/) { 263 var args = [singular, plural]; 264 for (var i=3; i<arguments.length; i+=1) { 265 args.push(arguments[i]); 266 } 267 return ngettext.apply(this, args); 268 } 269 270 /** 271 * 272 * @param {Object} param 273 * @param {String} format 274 * @returns {String} The formatted current date string 275 * @see formatDate 276 */ 277 function now_macro(param, format) { 278 return formatDate(new Date, format || param.format); 279 } 280 281 /** 282 * @returns {String} The rendered link element 283 * @see renderLink 284 */ 285 function link_macro() { 286 return renderLink.apply(this, arguments); 287 } 288 289 // FIXME: The definition with "var" is necessary; otherwise the skin_macro() 290 // method won't be overwritten reliably. (Looks like a Helma bug.) 291 /** 292 * 293 * @param {Object} param 294 * @param {String} name 295 * @returns {String} The rendered skin 296 * @see HopObject#skin_macro 297 */ 298 var skin_macro = function(param, name) { 299 return HopObject.prototype.skin_macro.apply(this, arguments); 300 } 301 302 /** 303 * 304 * @param {Object} param 305 * @param {String} delimiter 306 */ 307 function breadcrumbs_macro (param, delimiter) { 308 delimiter || (delimiter = param.separator || " : "); 309 var offset = res.handlers.site === root ? 0 : 1; 310 for (var i=offset; i<path.length-1; i+=1) { 311 html.link({href: path[i].href()}, path[i].getTitle()); 312 res.write(delimiter); 313 } 314 var title = path[i].getTitle(); 315 if (req.action !== "main" && path[i].main_action) { 316 html.link({href: path[i].href()}, title); 317 } else { 318 res.write(title); 319 } 320 return; 321 } 322 323 /** 324 * 325 * @param {Object} param 326 * @param {String} id 327 * @param {String} mode 328 */ 329 function story_macro(param, id, mode) { 330 var story = HopObject.getFromPath(id, "stories"); 331 if (!story || !story.getPermission("main")) { 332 return; 333 } 334 335 switch (mode) { 336 case "url": 337 res.write(story.href()); 338 break; 339 case "link": 340 html.link({href: story.href()}, story.getTitle()); 341 break; 342 default: 343 story.renderSkin("Story#" + (param.skin || "embed")); 344 } 345 return; 346 } 347 348 /** 349 * 350 * @param {Object} param 351 * @param {String} id 352 * @param {String} mode 353 */ 354 function file_macro(param, id, mode) { 355 if (!id) { 356 return; 357 } 358 359 var file; 360 if (id.startsWith("/")) { 361 name = id.substring(1); 362 if (mode === "url") { 363 res.write(root.getStaticUrl(name)); 364 } else { 365 file = root.getStaticFile(name); 366 res.push(); 367 File.prototype.contentLength_macro.call({ 368 contentLength: file.getLength() 369 }); 370 res.handlers.file = { 371 href: root.getStaticUrl(name), 372 name: name, 373 contentLength: res.pop() 374 }; 375 File.prototype.renderSkin("File#main"); 376 } 377 return; 378 } 379 380 file = HopObject.getFromPath(id, "files"); 381 if (!file) { 382 return; 383 } 384 if (mode === "url") { 385 res.write(file.getUrl()); 386 } else { 387 file.renderSkin(param.skin || "File#main"); 388 } 389 return; 390 } 391 392 /** 393 * 394 * @param {Object} param 395 * @param {String} id 396 * @param {String} mode 397 */ 398 function image_macro(param, id, mode) { 399 if (!id) { 400 return; 401 } 402 403 var image; 404 if (id.startsWith("/")) { 405 var name = id.substring(1); 406 image = Images.Default[name] || Images.Default[name + ".gif"]; 407 } else { 408 image = HopObject.getFromPath(id, "images"); 409 if (!image && param.fallback) { 410 image = HopObject.getFromPath(param.fallback, "images"); 411 } 412 } 413 414 if (!image) { 415 return; 416 } 417 418 switch (mode) { 419 case "url": 420 res.write(image.getUrl()); 421 break; 422 case "thumbnail": 423 case "popup": 424 var url = image.getUrl(); 425 html.openTag("a", {href: url}); 426 // FIXME: Bloody popups belong to compatibility layer 427 (mode === "popup") && (param.onclick = 'javascript:openPopup(\'' + 428 url + '\', ' + image.width + ', ' + image.height + '); return false;') 429 image.thumbnail_macro(param); 430 html.closeTag("a"); 431 break; 432 default: 433 image.render_macro(param); 434 } 435 return; 436 } 437 438 /** 439 * 440 * @param {Object} param 441 * @param {String} id 442 * @param {String} mode 443 */ 444 function poll_macro(param, id, mode) { 445 if (!id) { 446 return; 447 } 448 449 var poll = HopObject.getFromPath(id, "polls"); 450 if (!poll) { 451 return; 452 } 453 454 switch (mode) { 455 case "url": 456 res.write(poll.href()); 457 break; 458 case "link": 459 html.link({ 460 href: poll.href(poll.status === Poll.CLOSED ? "result" : "") 461 }, poll.question); 462 break; 463 default: 464 if (poll.status === Poll.CLOSED || mode === "results") 465 poll.renderSkin("$Poll#results"); 466 else { 467 poll.renderSkin("$Poll#main"); 468 } 469 } 470 return; 471 } 472 473 /** 474 * 475 * @param {Object} param 476 * @param {String} id 477 * @param {String} limit 478 */ 479 function list_macro(param, id, limit) { 480 if (!id) { 481 return; 482 } 483 484 var max = Math.min(limit || 25, 50); 485 var collection, skin; 486 if (id === "sites") { 487 collection = root.sites.list(0, max); 488 skin = "Site#preview"; 489 } else if (id === "updates") { 490 collection = root.updates.list(0, limit); 491 skin = "Site#preview"; 492 } else { 493 var site; 494 var parts = id.split("/"); 495 if (parts.length > 1) { 496 type = parts[1]; 497 site = root.sites.get(parts[0]); 498 } else { 499 type = parts[0]; 500 } 501 502 site || (site = res.handlers.site); 503 var filter = function(item, index) { 504 return index < max && item.getPermission("main"); 505 } 506 507 var commentFilter = function(item) { 508 if (item.story.status !== Story.CLOSED && 509 item.site.commentMode !== Site.DISABLED && 510 item.story.commentMode !== Story.CLOSED) { 511 return true; 512 } 513 return false; 514 } 515 516 switch (type) { 517 case "comments": 518 var comments = site.stories.comments; 519 collection = comments.list().filter(filter).filter(commentFilter); 520 skin = "Story#preview"; 521 break; 522 523 case "featured": 524 collection = site.stories.featured.list(0, max); 525 skin = "Story#preview"; 526 break; 527 528 case "images": 529 collection = site.images.list(0, max); 530 skin = "Image#preview"; 531 break; 532 533 case "postings": 534 content = site.stories.union; 535 collection = content.list().filter(filter).filter(function(item) { 536 if (item.constructor === Comment) { 537 return commentFilter(item); 538 } 539 return true; 540 }); 541 skin = "Story#preview"; 542 break; 543 544 case "stories": 545 var stories = site.stories.recent; 546 var counter = 0; 547 collection = stories.list().filter(function(item, index) { 548 return item.constructor === Story && filter(item, counter++); 549 }); 550 skin = "Story#preview"; 551 break; 552 553 case "tags": 554 return site.tags.list_macro(param, param.skin || "$Tag#preview"); 555 break; 556 557 default: 558 break; 559 } 560 } 561 param.skin && (skin = param.skin); 562 for each (var item in collection) { 563 // FIXME: Work-around for "story" handlers in comment skins 564 // (Obsolete as soon as "story" handlers are replaced with "this") 565 if (item.constructor === Comment) { 566 res.handlers.story = item; 567 } 568 item.renderSkin(skin); 569 } 570 return; 571 } 572 573 /** 574 * 575 * @param {Object} param 576 * @param {String} name 577 * @param {String} value 578 */ 579 function value_macro(param, name, value) { 580 if (!name) { 581 return; 582 } 583 name = name.toLowerCase(); 584 if (!value) { 585 res.write(res.meta.values[name]); 586 } else { 587 //res.write("/* set " + name + " to " + value + " */"); 588 res.meta.values[name] = value; 589 } 590 return; 591 } 592 593 /** 594 * 595 * @param {Object} param 596 * @param {String} id 597 */ 598 function randomize_macro(param, id) { 599 var getRandom = function(n) { 600 return Math.floor(Math.random() * n); 601 }; 602 603 var site; 604 if (id === "sites") { 605 site = root.sites.get(getRandom(root.sites.size())); 606 site.renderSkin(param.skin || "Site#preview"); 607 return; 608 } 609 610 var parts = id.split("/"); 611 if (parts.length > 1) { 612 type = parts[1]; 613 site = root.sites.get(parts[0]); 614 } else { 615 type = parts[0]; 616 } 617 site || (site = res.handlers.site); 618 switch (type) { 619 case "stories": 620 var stories = site.stories["public"]; 621 var story = stories.get(getRandom(stories.size())); 622 story && story.renderSkin(param.skin || "Story#preview"); 623 break; 624 case "images": 625 var image = site.images.get(getRandom(site.images.size())); 626 image && image.renderSkin(param.skin || "Image#preview"); 627 break; 628 } 629 return; 630 } 631 632 /** 633 * 634 * @param {Object} param 635 * @param {String} url 636 * @param {String} text 637 * @param {HopObject} handler 638 */ 639 function renderLink(param, url, text, handler) { 640 url || (url = param.url || ""); 641 text || (text = param.text || url); 642 if (!text || (handler && !handler.href)) { 643 return; 644 } 645 delete param.url; 646 delete param.text; 647 param.title || (param.title = String.EMPTY); 648 if (!handler || url.contains(":")) { 649 param.href = url; 650 } else if (url.contains("/") || url.contains("?") || url.contains("#")) { 651 var parts = url.split(/(\/|\?|#)/); 652 param.href = handler.href(parts[0]) + parts.splice(1).join(String.EMPTY); 653 } else { 654 param.href = handler.href(url); 655 } 656 html.link(param, text); 657 return; 658 } 659 660 /** 661 * 662 * @param {String} str 663 * @returns {String|null} The e-mail string if valid, null otherwise 664 */ 665 function validateEmail(str) { 666 if (str) { 667 if (str.isEmail(str)) { 668 return str; 669 } 670 } 671 return null; 672 } 673 674 /** 675 * 676 * @param {String} str 677 * @returns {String|null} The URL string if valid, null otherwise 678 */ 679 function validateUrl(str) { 680 if (str) { 681 if (str.isUrl(str)) { 682 return String(str); 683 } else if (str.contains("@")) { 684 return "mailto:" + str; 685 } else { 686 return null; 687 } 688 } 689 return null; 690 } 691 692 /** 693 * 694 * @param {String} str 695 * @returns {String} The processed string 696 */ 697 function quote(str) { 698 if (/[\W\D]/.test(str)) { 699 str = '"' + str + '"'; 700 } 701 return str; 702 } 703 704 /** 705 * 706 * @param {Number} number 707 * @param {String} pattern 708 * @returns {String} The formatted number string 709 */ 710 function formatNumber(number, pattern) { 711 return Number(number).format(pattern, res.handlers.site.getLocale()); 712 } 713 714 /** 715 * 716 * @param {Date} date 717 * @param {pattern} pattern 718 * @returns {String} The formatted date string 719 */ 720 function formatDate(date, pattern) { 721 if (!date) { 722 return null; 723 } 724 pattern || (pattern = "short"); 725 var site = res.handlers.site; 726 var format = site.metadata.get(pattern.toLowerCase() + "DateFormat"); 727 if (!format) { 728 format = global[pattern.toUpperCase() + "DATEFORMAT"] || pattern; 729 } 730 try { 731 return date.format(format, site.getLocale(), site.getTimeZone()); 732 } catch (ex) { 733 return "[Macro error: Invalid date format]"; 734 } 735 return; 736 } 737 738 /** 739 * Injects the XSLT stylesheet declaration into an XML string until 740 * Mozilla developers will have mercy. 741 * @param {String} xml An XML string 742 * @returns {String} An XML string containing the XSLT stylesheet declaration 743 */ 744 function injectXslDeclaration(xml) { 745 res.push(); 746 renderSkin("Global#xslDeclaration"); 747 return xml.replace(/(\?>\r?\n?)/, "$1" + res.pop()); 748 } 749 750 /** 751 * General mail sending function. Mails will be queued in app.data.mails. 752 * @param {String} sender The sender's e-mail address 753 * @param {Object} recipient The recipient's email addresses 754 * @param {String} subject The e-mail's subject 755 * @param {String} body The body text of the e-mail 756 * @returns {Number} The status code of the underlying helma.Mail instance 757 */ 758 function sendMail(sender, recipient, subject, body) { 759 if (!recipient || !body) { 760 throw Error("Insufficient arguments in method sendMail()"); 761 } 762 var mail = new helma.Mail(getProperty("smtp", "localhost"), getProperty("smtp.port", "25")); 763 mail.setFrom("noreply@antville.org"); 764 if (recipient instanceof Array) { 765 for (var i in recipient) { 766 mail.addBCC(recipient[i]); 767 } 768 } else { 769 mail.addTo(recipient); 770 } 771 mail.setSubject(subject); 772 mail.setText(body); 773 mail.addText(renderSkinAsString("$Global#disclaimer")); 774 mail.queue(); 775 return mail.status; 776 } 777 778 /** 779 * 780 * @param {String} language 781 * @returns {java.util.Locale} The corresponding locale object 782 */ 783 function getLocale(language) { 784 return java.util.Locale[(language || "english").toUpperCase()]; 785 } 786 787 /** 788 * Creates an array of all available Java locales sorted by their names. 789 * @param {String} language The optional language of the locales 790 * @returns {Object[]} A sorted array containing the corresponding locales 791 */ 792 function getLocales(language) { 793 var result = [], locale; 794 var displayLocale = getLocale(language); 795 var locales = java.util.Locale.getAvailableLocales(); 796 for (var i in locales) { 797 locale = locales[i].toString(); 798 result.push({ 799 value: locale, 800 display: locales[i].getDisplayName(displayLocale), 801 }); 802 } 803 result.sort(new String.Sorter("display")); 804 return result; 805 } 806 807 /** 808 * 809 * @param {String} language 810 * @returns {Object[]} A sorted array containing the corresponding timezones 811 */ 812 function getTimeZones(language) { 813 var result = [], timeZone, offset; 814 var locale = getLocale(language); 815 var zones = java.util.TimeZone.getAvailableIDs(); 816 for each (var zone in zones) { 817 timeZone = java.util.TimeZone.getTimeZone(zone); 818 offset = timeZone.getRawOffset(); 819 /*res.debug("zone id: " + zone) 820 res.debug("offset: " + timeZone.getRawOffset()) 821 res.debug("dst savings: " + timeZone.getDSTSavings()) 822 res.debug("old code: " + formatter.format(offset / Date.ONEHOUR)) 823 res.debug("new code: " + (offset / Date.ONEHOUR).format("+00;-00") + ":" + 824 (Math.abs(offset % Date.ONEHOUR) / Date.ONEMINUTE).format("00"))*/ 825 result.push({ 826 value: zone, 827 display: zone + " (UTC" + (offset / Date.ONEHOUR).format("+00;-00") + 828 ":" + (Math.abs(offset % Date.ONEHOUR) / 829 Date.ONEMINUTE).format("00") + ")" 830 }); 831 } 832 result.sort(new String.Sorter("display")); 833 return result; 834 } 835 836 /** 837 * 838 * @param {String} type 839 * @param {String} language 840 * @returns {Array[]} An array containing the corresponding date formats 841 */ 842 function getDateFormats(type, language) { 843 var patterns; 844 if (type === "short") { 845 patterns = [SHORTDATEFORMAT, "yyyy/MM/dd HH:mm", 846 "yyyy.MM.dd, HH:mm", "d. MMMM, HH:mm", "MMMM d, HH:mm", 847 "d. MMM, HH:mm", "MMM d, HH:mm", "EEE, d. MMM, HH:mm", 848 "EEE MMM d, HH:mm", "EEE, HH:mm", "EE, HH:mm", "HH:mm"]; 849 } else if (type === "long") { 850 patterns = [LONGDATEFORMAT, "EEEE, MMMM dd, yyyy, HH:mm", 851 "EE, d. MMM. yyyy, HH:mm", "EE MMM dd, yyyy, HH:mm", 852 "EE yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm", "d. MMMM yyyy, HH:mm", 853 "MMMM d, yyyy, HH:mm", "d. MMM yyyy, HH:mm", "MMM d, yyyy, HH:mm"]; 854 } 855 var result = [], sdf; 856 var locale = getLocale(language); 857 var now = new Date; 858 for each (var pattern in patterns) { 859 sdf = new java.text.SimpleDateFormat(pattern, locale); 860 result.push([encodeForm(pattern), sdf.format(now)]); 861 } 862 return result; 863 } 864 865 /** 866 * 867 * @param {Object} value 868 * @param {Object} param 869 * @param {Object} defaultValue 870 * @returns {Object} The value argument if truthy, the defaultValue argument 871 * otherwise 872 */ 873 function default_filter(value, param, defaultValue) { 874 return value || defaultValue; 875 } 876 877 /** 878 * 879 * @param {Date} value 880 * @param {Object} param 881 * @returns {String} The age string of a date 882 */ 883 function age_filter(value, param) { 884 if (!value || value.constructor !== Date) { 885 return value; 886 } 887 return value.getAge() 888 } 889 890 /** 891 * 892 * @param {String} text 893 * @param {String} param 894 * @param {Object} url 895 * @returns {String} The rendered link element 896 * @see renderLink 897 */ 898 function link_filter(text, param, url) { 899 if (text) { 900 url || (url = text); 901 return renderLink(param, url, text); 902 } 903 return; 904 } 905 906 /** 907 * 908 * @param {Object} string 909 * @param {Object} param 910 * @param {String} pattern 911 * @returns {String} The formatted string 912 */ 913 function format_filter(value, param, pattern) { 914 if (!value && value !== 0) { 915 return; 916 } 917 var f = global["format" + value.constructor.name]; 918 if (f && f.constructor === Function) { 919 return f(value, pattern || param.pattern); 920 } 921 return value; 922 } 923 924 /** 925 * 926 * @param {String} input 927 * @param {Object} param 928 * @param {Number} limit 929 * @param {String} clipping 930 * @param {String} delimiter 931 * @returns {String} The clipped input 932 */ 933 function clip_filter(input, param, limit, clipping, delimiter) { 934 var len = 0; 935 if (input) { 936 len = input.length; 937 input = input.stripTags(); 938 } 939 input || (input = ngettext("({0} character)", "({0} characters)", len)); 940 limit || (limit = 20); 941 clipping || (clipping = "..."); 942 delimiter || (delimiter = "\\s"); 943 return String(input || "").head(limit, clipping, delimiter); 944 } 945 946 // FIXME: 947 /** 948 * 949 * @param {String} rss 950 * @returns {String} The fixed RSS string 951 */ 952 function fixRssText(rss) { 953 var re = new RegExp("<img src\\s*=\\s*\"?([^\\s\"]+)?\"?[^>]*?(alt\\s*=\\s*\"?([^\"]+)?\"?[^>]*?)?>", "gi"); 954 rss = rss.replace(re, "[<a href=\"$1\" title=\"$3\">Image</a>]"); 955 return rss; 956 } 957 958 // FIXME: 959 /** 960 * 961 */ 962 function countUsers() { 963 app.data.activeUsers = new Array(); 964 var l = app.getActiveUsers(); 965 for (var i in l) 966 app.data.activeUsers[app.data.activeUsers.length] = l[i]; 967 l = app.getSessions(); 968 app.data.sessions = 0; 969 for (var i in l) { 970 if (!l[i].user) 971 app.data.sessions++; 972 } 973 app.data.activeUsers.sort(); 974 return; 975 } 976 977 // FIXME: 978 /** 979 * @ignore 980 * @param {Object} src 981 */ 982 function doWikiStuff (src) { 983 // robert, disabled: didn't get the reason for this: 984 // var src= " "+src; 985 if (src == null || !src.contains("<*")) 986 return src; 987 988 // do the Wiki link thing, <*asterisk style*> 989 var regex = new RegExp ("<[*]([^*]+)[*]>"); 990 regex.ignoreCase=true; 991 992 var text = ""; 993 var start = 0; 994 while (true) { 995 var found = regex.exec (src.substring(start)); 996 var to = found == null ? src.length : start + found.index; 997 text += src.substring(start, to); 998 if (found == null) 999 break; 1000 var name = ""+(new java.lang.String (found[1])).trim(); 1001 var item = res.handlers.site.topics.get (name); 1002 if (item == null && name.lastIndexOf("s") == name.length-1) 1003 item = res.handlers.site.topics.get (name.substring(0, name.length-1)); 1004 if (item == null || !item.size()) 1005 text += format(name)+" <small>[<a href=\""+res.handlers.site.stories.href("create")+"?topic="+escape(name)+"\">define "+format(name)+"</a>]</small>"; 1006 else 1007 text += "<a href=\""+item.href()+"\">"+name+"</a>"; 1008 start += found.index + found[1].length+4; 1009 } 1010 return text; 1011 } 1012 1013 // FIXME: Rewrite with jala.ListRenderer? 1014 /** 1015 * 1016 * @param {HopObject|Array} collection 1017 * @param {Function|Skin} funcOrSkin 1018 * @param {Number} itemsPerPage 1019 * @param {Number} pageIdx 1020 * @returns {String} The rendered list 1021 */ 1022 function renderList(collection, funcOrSkin, itemsPerPage, pageIdx) { 1023 var currIdx = 0, item; 1024 var isArray = collection instanceof Array; 1025 var stop = size = isArray ? collection.length : collection.size(); 1026 1027 if (itemsPerPage) { 1028 var totalPages = Math.ceil(size/itemsPerPage); 1029 if (isNaN(pageIdx) || pageIdx > totalPages || pageIdx < 0) { 1030 pageIdx = 0; 1031 } 1032 currIdx = pageIdx * itemsPerPage; 1033 stop = Math.min(currIdx + itemsPerPage, size); 1034 } 1035 1036 var isFunction = (funcOrSkin instanceof Function) ? true : false; 1037 res.push(); 1038 while (currIdx < stop) { 1039 item = isArray ? collection[currIdx] : collection.get(currIdx); 1040 isFunction ? funcOrSkin(item) : item.renderSkin(funcOrSkin); 1041 currIdx += 1; 1042 } 1043 return res.pop(); 1044 } 1045 1046 // FIXME: Rewrite using jala.ListRenderer or rename (eg. renderIndex) 1047 /** 1048 * 1049 * @param {HopObject|Array|Number} collectionOrSize 1050 * @param {String} url 1051 * @param {Number} itemsPerPage 1052 * @param {Number} pageIdx 1053 * @returns {String} The rendered index 1054 */ 1055 function renderPager(collectionOrSize, url, itemsPerPage, pageIdx) { 1056 // Render a single item for the navigation bar 1057 var renderItem = function(text, cssClass, url, page) { 1058 var param = {"class": cssClass}; 1059 if (!url) { 1060 param.text = text; 1061 } else { 1062 if (url.contains("?")) 1063 param.text = html.linkAsString({href: url + "&page=" + page}, text); 1064 else 1065 param.text = html.linkAsString({href: url + "?page=" + page}, text); 1066 } 1067 renderSkin("$Global#pagerItem", param); 1068 return; 1069 } 1070 1071 var maxItems = 10; 1072 var size = 0; 1073 if (collectionOrSize instanceof Array) { 1074 size = collectionOrSize.length; 1075 } else if (collectionOrSize instanceof HopObject) { 1076 size = collectionOrSize.size(); 1077 } else if (!isNaN(collectionOrSize)) { 1078 size = parseInt(collectionOrSize, 10); 1079 } 1080 var lastPageIdx = Math.ceil(size/itemsPerPage)-1; 1081 // If there's just one page no navigation will be rendered 1082 if (lastPageIdx <= 0) { 1083 return null; 1084 } 1085 1086 // Initialize the parameter object 1087 var param = {}; 1088 var pageIdx = parseInt(pageIdx, 10); 1089 // Check if the passed index is correct 1090 if (isNaN(pageIdx) || pageIdx > lastPageIdx || pageIdx < 0) { 1091 pageIdx = 0; 1092 } 1093 param.display = ((pageIdx * itemsPerPage) + 1) + "-" + 1094 (Math.min((pageIdx * itemsPerPage) + itemsPerPage, size)); 1095 param.total = size; 1096 1097 // Render the navigation-bar 1098 res.push(); 1099 (pageIdx > 0) && renderItem("[–]", "pageNavItem", url, pageIdx-1); 1100 var offset = Math.floor(pageIdx / maxItems) * maxItems; 1101 (offset > 0) && renderItem("[..]", "pageNavItem", url, offset-1); 1102 var currPage = offset; 1103 var stop = Math.min(currPage + maxItems, lastPageIdx+1); 1104 while (currPage < stop) { 1105 if (currPage === pageIdx) { 1106 renderItem("[" + (currPage +1) + "]", "pageNavSelItem"); 1107 } else { 1108 renderItem("[" + (currPage +1) + "]", "pageNavItem", url, currPage); 1109 } 1110 currPage += 1; 1111 } 1112 if (currPage < lastPageIdx) { 1113 renderItem("[..]", "pageNavItem", url, offset + maxItems); 1114 } 1115 if (pageIdx < lastPageIdx) { 1116 renderItem("[+]", "pageNavItem", url, pageIdx +1); 1117 } 1118 param.pager = res.pop(); 1119 return renderSkinAsString("$Global#pager", param); 1120 } 1121 1122 /** 1123 * 1124 * @param {String} plural 1125 * @returns {String} The english singular form of the input 1126 */ 1127 function singularize(plural) { 1128 if (plural.endsWith("ies")) { 1129 return plural.substring(0, plural.length-3) + "y"; 1130 } 1131 return plural.substring(0, plural.length-1); 1132 } 1133 1134 /** 1135 * 1136 * @param {String} singular 1137 * @returns {String} The english plural form of the input 1138 */ 1139 function pluralize(singular) { 1140 if (singular.endsWith("y")) { 1141 return singular.substring(0, singular.length-1) + "ies"; 1142 } 1143 return singular + "s"; 1144 } 1145 1146 /** 1147 * 1148 * @param {Number} millis 1149 */ 1150 var wait = function(millis) { 1151 millis || (millis = Date.ONESECOND); 1152 var now = new Date; 1153 while (new Date - now < millis) { 1154 void null; 1155 } 1156 return; 1157 } 1158