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 Site prototype. 27 */ 28 29 this.handleMetadata("archiveMode"); 30 this.handleMetadata("commentMode"); 31 this.handleMetadata("configured"); 32 this.handleMetadata("locale"); 33 this.handleMetadata("longDateFormat"); 34 this.handleMetadata("notificationMode"); 35 this.handleMetadata("notifiedOfBlocking"); 36 this.handleMetadata("notifiedOfDeletion"); 37 this.handleMetadata("closed"); 38 this.handleMetadata("pageSize"); 39 this.handleMetadata("pageMode"); 40 this.handleMetadata("shortDateFormat"); 41 this.handleMetadata("tagline"); 42 this.handleMetadata("timeZone"); 43 this.handleMetadata("title"), 44 this.handleMetadata("callbackMode"); 45 this.handleMetadata("callbackUrl"); 46 this.handleMetadata("spamfilter"); 47 48 /** 49 * @function 50 * @returns {String[]} 51 * @see defineConstants 52 */ 53 Site.getStatus = defineConstants(Site, "blocked", "regular", "trusted"); 54 /** 55 * @function 56 * @returns {String[]} 57 * @see defineConstants 58 */ 59 Site.getModes = defineConstants(Site, "closed", "restricted", "public", "open"); 60 /** 61 * @function 62 * @returns {String[]} 63 * @see defineConstants 64 */ 65 Site.getPageModes = defineConstants(Site, "stories"); //, "days"); 66 /** 67 * @function 68 * @returns {String[]} 69 * @see defineConstants 70 */ 71 Site.getCommentModes = defineConstants(Site, "disabled", "enabled"); 72 /** 73 * @function 74 * @returns {String[]} 75 * @see defineConstants 76 */ 77 Site.getArchiveModes = defineConstants(Site, "closed", "public"); 78 /** 79 * @function 80 * @returns {String[]} 81 * @see defineConstants 82 */ 83 Site.getNotificationModes = defineConstants(Site, "Nobody", 84 "Owner", "Manager", "Contributor", "Subscriber" ); 85 /** 86 * @function 87 * @returns {String[]} 88 * @see defineConstants 89 */ 90 Site.getCallbackModes = defineConstants(Site, "disabled", "enabled"); 91 92 /** 93 * 94 * @param {Site} site 95 */ 96 Site.remove = function(site) { 97 HopObject.remove(site.members); 98 HopObject.remove(site.stories); 99 HopObject.remove(site.images); 100 HopObject.remove(site.files); 101 HopObject.remove(site.polls); 102 site.layout && Layout.remove.call(site.layout); 103 site.getStaticFile().removeDirectory(); 104 site.remove(); 105 // FIXME: There is a problem in the log structure for a deleted site 106 root.admin.log(root, "Removed site " + site.name); 107 return; 108 } 109 110 /** 111 * 112 * @param {String} name 113 * @returns {Site} 114 */ 115 Site.getByName = function(name) { 116 return root.get(name); 117 } 118 119 /** 120 * 121 * @param {String} mode 122 * @returns {Boolean} 123 */ 124 Site.require = function(mode) { 125 var modes = [Site.CLOSED, Site.RESTRICTED, Site.PUBLIC, Site.OPEN]; 126 return modes.indexOf(res.handlers.site.mode) >= modes.indexOf(mode); 127 } 128 129 /** 130 * A Site object is the basic container of Antville. 131 * @name Site 132 * @constructor 133 * @param {String} name A unique identifier also used in the URL of a site 134 * @param {String} title An arbitrary string branding a site 135 * @property {Tag[]} $tags 136 * @property {Archive} archive 137 * @property {String} archiveMode The way the archive of a site is displayed 138 * @property {String} commentMode The way comments of a site are displayed 139 * @property {Date} created The date and time of site creation 140 * @property {User} creator A reference to a user who created a site 141 * @property {Tags} galleries 142 * @property {Files} files 143 * @property {Images} images 144 * @property {Layout} layout 145 * @property {String} locale The place and language settings of a site 146 * @property {String} longDateFormat The long date format string 147 * @property {Members} members 148 * @property {Metadata} metadata 149 * @property {String} mode The access level of a site 150 * @property {Date} modified The date and time when a site was last modified 151 * @property {User} modifier A reference to a user who modified a site 152 * @property {String} notificationMode The way notifications are sent from a site 153 * @property {String} pageMode The way stories of a site are displayed 154 * @property {Number} pageSize The amount of stories to be displayed simultaneously 155 * @property {Polls} polls 156 * @property {String} shortDateFormat The short date format string 157 * @property {String} status The trust level of a site 158 * @property {Stories} stories 159 * @property {String} tagline An arbitrary text describing a site 160 * @property {Tags} tags 161 * @property {String} timeZone The time and date settings of a site 162 * @extends HopObject 163 */ 164 Site.prototype.constructor = function(name, title) { 165 var now = new Date; 166 var locale = root.getLocale(); 167 var user = session.user || new HopObject; 168 169 this.map({ 170 name: name, 171 title: title || name, 172 created: now, 173 creator: user, 174 modified: now, 175 modifier: user, 176 status: user.status === User.PRIVILEGED ? Site.TRUSTED : user.status, 177 mode: Site.CLOSED, 178 tagline: String.EMPTY, 179 callbackEnabled: false, 180 commentMode: Site.OPEN, 181 archiveMode: Site.PUBLIC, 182 notificationMode: Site.DISABLED, 183 pageMode: Site.DAYS, 184 pageSize: 3, 185 locale: locale.toString(), 186 timeZone: root.getTimeZone().getID(), 187 longDateFormat: LONGDATEFORMAT, 188 shortDateFormat: SHORTDATEFORMAT 189 }); 190 191 return this; 192 } 193 194 /** 195 * 196 * @param {String} action 197 * @returns {Boolean} 198 */ 199 Site.prototype.getPermission = function(action) { 200 switch (action) { 201 case "backup.js": 202 case "main.js": 203 case "main.css": 204 case "error": 205 case "notfound": 206 case "robots.txt": 207 case "search": 208 case "search.xml": 209 case "user.js": 210 return true; 211 212 case ".": 213 case "main": 214 case "comments.xml": 215 case "rss.xml": 216 case "rss.xsl": 217 case "stories.xml": 218 return Site.require(Site.PUBLIC) || 219 (Site.require(Site.RESTRICTED) && 220 Membership.require(Membership.CONTRIBUTOR)) || 221 (Site.require(Site.CLOSED) && 222 Membership.require(Membership.OWNER)) || 223 User.require(User.PRIVILEGED); 224 225 case "edit": 226 case "export": 227 case "referrers": 228 return Membership.require(Membership.OWNER) || 229 User.require(User.PRIVILEGED); 230 231 case "subscribe": 232 return Site.require(Site.PUBLIC) && 233 !Membership.require(Membership.SUBSCRIBER); 234 235 case "unsubscribe": 236 if (User.require(User.REGULAR)) { 237 var membership = Membership.getByName(session.user.name); 238 return membership && !membership.require(Membership.OWNER); 239 } 240 } 241 return false; 242 } 243 244 Site.prototype.main_action = function() { 245 res.data.body = this.renderSkinAsString("Site#main"); 246 res.data.title = this.title; 247 this.renderSkin("Site#page"); 248 this.log(); 249 return; 250 } 251 252 Site.prototype.edit_action = function() { 253 if (req.postParams.save) { 254 try { 255 this.update(req.postParams); 256 res.message = gettext("The changes were saved successfully."); 257 res.redirect(this.href(req.action)); 258 } catch (ex) { 259 res.message = ex; 260 app.log(ex); 261 /*writeln("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") 262 var e = new Packages.org.mozilla.javascript.EvaluatorException(ex); 263 e.fillInStackTrace(); 264 res.debug(e.getScriptStackTrace()); 265 res.debug(e.printStackTrace(java.lang.System.out)); 266 var trace = e.getStackTrace(); 267 writeln(trace.toString()); 268 for (var i in ex) 269 app.log(i + ": " + ex[i]);*/ 270 } 271 } 272 273 res.data.action = this.href(req.action); 274 res.data.title = gettext("Preferences of {0}", this.title); 275 res.data.body = this.renderSkinAsString("$Site#edit"); 276 this.renderSkin("Site#page"); 277 return; 278 } 279 280 /** 281 * 282 * @param {String} name 283 * @returns {Object} 284 */ 285 Site.prototype.getFormOptions = function(name) { 286 switch (name) { 287 case "archiveMode": 288 return Site.getArchiveModes(); 289 case "commentMode": 290 return Site.getCommentModes(); 291 case "locale": 292 return getLocales(); 293 case "layout": 294 return this.getLayouts(); 295 case "longDateFormat": 296 return getDateFormats("long"); 297 case "mode": 298 return Site.getModes(); 299 case "notificationMode": 300 return Site.getNotificationModes(); 301 case "pageMode": 302 return Site.getPageModes(); 303 case "status": 304 return Site.getStatus(); 305 case "shortDateFormat": 306 return getDateFormats("short"); 307 case "timeZone": 308 return getTimeZones(); 309 case "callbackMode": 310 return Site.getCallbackModes(); 311 default: 312 return HopObject.prototype.getFormOptions.apply(this, arguments); 313 } 314 } 315 316 /** 317 * 318 * @param {Object} data 319 */ 320 Site.prototype.update = function(data) { 321 if (this.isTransient()) { 322 if (!data.name) { 323 throw Error(gettext("Please enter a name for your new site.")); 324 } else if (data.name.length > 30) { 325 throw Error(gettext("The chosen name is too long. Please enter a shorter one.")); 326 } else if (/(\/|\\)/.test(data.name)) { 327 throw Error(gettext("A site name may not contain any (back)slashes.")); 328 } else if (data.name !== root.getAccessName(data.name)) { 329 throw Error(gettext("There already is a site with this name.")); 330 } 331 this.layout = new Layout(this); 332 this.name = data.name; 333 this.title = data.title || data.name; 334 return; 335 } 336 337 this.map({ 338 title: stripTags(data.title) || this.name, 339 tagline: data.tagline, 340 mode: data.mode || Site.PRIVATE, 341 callbackUrl: data.callbackUrl, 342 callbackMode: data.callbackMode || Site.DISABLED, 343 pageMode: data.pageMode || Site.DAYS, 344 pageSize: parseInt(data.pageSize, 10) || this.pageSize || 3, 345 commentMode: data.commentMode || Site.DISABLED, 346 archiveMode: data.archiveMode || Site.CLOSED, 347 notificationMode: data.notificationMode || Site.DISABLED, 348 timeZone: data.timeZone, 349 longDateFormat: data.longDateFormat, 350 shortDateFormat: data.shortDateFormat, 351 locale: data.locale, 352 spamfilter: data.spamfilter 353 }); 354 355 this.configured = new Date; 356 this.modifier = session.user; 357 this.clearCache(); 358 return; 359 } 360 361 Site.prototype.main_css_action = function() { 362 res.dependsOn(this.modified); 363 res.dependsOn(Skin("Site", "values").getSource()); 364 res.dependsOn(Skin("Site", "stylesheet").getSource()); 365 res.digest(); 366 res.contentType = "text/css"; 367 this.renderSkin("Site#stylesheet"); 368 return; 369 } 370 371 Site.prototype.main_js_action = function() { 372 res.contentType = "text/javascript"; 373 for each (script in ["jquery-1.2.3.min.js", "antville-1.2.js"]) { 374 this.renderSkin("$Site#include", {href: root.getStaticUrl(script)}); 375 } 376 this.renderSkin("$Site#include", {href: this.href("user.js")}); 377 return; 378 } 379 380 Site.prototype.user_js_action = function() { 381 res.contentType = "text/javascript"; 382 res.dependsOn(this.modified); 383 res.dependsOn(Skin("Site", "javascript").getSource()); 384 res.digest(); 385 this.renderSkin("Site#javascript"); 386 return; 387 } 388 389 Site.prototype.backup_js_action = function() { 390 if (req.isPost()) { 391 session.data.backup = {}; 392 for (var key in req.postParams) { 393 session.data.backup[key] = req.postParams[key]; 394 } 395 } 396 return; 397 } 398 399 Site.prototype.rss_xml_action = function() { 400 res.dependsOn(this.modified); 401 res.digest(); 402 res.contentType = "text/xml"; 403 res.write(this.getXml(this.stories.union)); 404 return; 405 } 406 407 Site.prototype.stories_xml_action = function() { 408 res.dependsOn(this.modified); 409 res.digest(); 410 res.contentType = "text/xml"; 411 res.write(this.getXml(this.stories.recent)); 412 return; 413 } 414 415 Site.prototype.comments_xml_action = function() { 416 res.dependsOn(this.modified); 417 res.digest(); 418 res.contentType = "text/xml"; 419 res.write(this.getXml(this.stories.comments)); 420 return; 421 } 422 423 Site.prototype.search_xml_action = function() { 424 return; // FIXME 425 res.contentType = "application/opensearchdescription+xml"; 426 res.write(<OpenSearchDescription xmlns="http://antville.org/"> 427 <ShortName>Antville Search</ShortName> 428 <Description>Search on Antville</Description> 429 <Tags>antville search</Tags> 430 <Image height="16" width="16" type="image/vnd.microsoft.icon">http://www.youtube.com/favicon.ico</Image> 431 <Url type="text/html" template="http://antville.org/search?q={searchTerms}" /> 432 <Query role="example" searchTerms="cat" /> 433 </OpenSearchDescription>); 434 return; 435 } 436 437 /** 438 * 439 * @param {Story[]} collection 440 */ 441 Site.prototype.getXml = function(collection) { 442 collection || (collection = this.stories.recent); 443 var now = new Date; 444 var feed = new rome.SyndFeedImpl(); 445 feed.setFeedType("rss_2.0"); 446 feed.setLink(this.href()); 447 feed.setTitle(this.title); 448 feed.setDescription(this.tagline || String.EMPTY); 449 feed.setLanguage(this.locale.replace("_", "-")); 450 feed.setPublishedDate(now); 451 452 /* 453 var feedInfo = new rome.FeedInformationImpl(); 454 var feedModules = new java.util.ArrayList(); 455 feedModules.add(feedInfo); 456 feed.setModules(feedModules); 457 //feedInfo.setImage(new java.net.URL(this.getProperty("imageUrl"))); 458 feedInfo.setSubtitle(this.tagline); 459 feedInfo.setSummary(this.description); 460 feedInfo.setAuthor(this.creator.name); 461 feedInfo.setOwnerName(this.creator.name); 462 //feedInfo.setOwnerEmailAddress(this.getProperty("email")); 463 */ 464 465 var entry, entryInfo, entryModules; 466 var enclosure, enclosures, keywords; 467 var entries = new java.util.ArrayList(); 468 var description; 469 470 var list = collection.constructor === Array ? 471 collection : collection.list(0, 25); 472 for each (var item in list) { 473 entry = new rome.SyndEntryImpl(); 474 item.title && entry.setTitle(item.title); 475 entry.setLink(item.href()); 476 entry.setAuthor(item.creator.name); 477 entry.setPublishedDate(item.created); 478 if (item.text) { 479 // FIXME: Work-around for "story" handlers in comment skins 480 // (Obsolete as soon as "story" handlers are replaced with "this") 481 //res.handlers.story = item; 482 description = new rome.SyndContentImpl(); 483 //description.setType("text/plain"); 484 // FIXME: Work-around for org.jdom.IllegalDataException caused by some ASCII control characters 485 description.setValue(item.renderSkinAsString("Story#rss").replace(/[\x00-\x1f^\x0a^\x0d]/g, function(c) { 486 return "" + c.charCodeAt(0) + ";"; 487 })); 488 entry.setDescription(description); 489 } 490 entries.add(entry); 491 492 /* 493 entryInfo = new rome.EntryInformationImpl(); 494 entryModules = new java.util.ArrayList(); 495 entryModules.add(entryInfo); 496 entry.setModules(entryModules); 497 498 enclosure = new rome.SyndEnclosureImpl(); 499 enclosure.setUrl(episode.getProperty("fileUrl")); 500 enclosure.setType(episode.getProperty("contentType")); 501 enclosure.setLength(episode.getProperty("filesize") || 0); 502 enclosures = new java.util.ArrayList(); 503 enclosures.add(enclosure); 504 entry.setEnclosures(enclosures); 505 506 entryInfo.setAuthor(entry.getAuthor()); 507 entryInfo.setBlock(false); 508 entryInfo.setDuration(new rome.Duration(episode.getProperty("length") || 0)); 509 entryInfo.setExplicit(false); 510 entryInfo.setKeywords(episode.getProperty("keywords")); 511 entryInfo.setSubtitle(episode.getProperty("subtitle")); 512 entryInfo.setSummary(episode.getProperty("description")); 513 */ 514 } 515 feed.setEntries(entries); 516 517 var output = new rome.SyndFeedOutput(); 518 //output.output(feed, res.servletResponse.writer); return; 519 var xml = output.outputString(feed); 520 // FIXME: Ugly hack for adding PubSubHubbub and rssCloud elements to XML 521 xml = xml.replace("<rss", '<rss xmlns:atom="http://www.w3.org/2005/Atom"'); 522 xml = xml.replace("<channel>", '<channel>\n <cloud domain="rpc.rsscloud.org" port="5337" path="/rsscloud/pleaseNotify" registerProcedure="" protocol="http-post" />'); 523 xml = xml.replace("<channel>", '<channel>\n <atom:link rel="hub" href="' + getProperty("parss.hub") + '"/>'); 524 return xml; //injectXslDeclaration(xml); 525 } 526 527 Site.prototype.rss_xsl_action = function() { 528 res.charset = "UTF-8"; 529 res.contentType = "text/xml"; 530 renderSkin("Global#xslStylesheet"); 531 return; 532 } 533 534 Site.prototype.referrers_action = function() { 535 if (req.data.permanent && this.getPermission("edit")) { 536 var urls = req.data.permanent_array; 537 res.write(this.metadata.get("spamfilter")); 538 for (var i in urls) { 539 res.write("\n"); 540 res.write(urls[i].replace(/\?/g, "\\\\?")); 541 } 542 this.metadata.set("spamfilter", res.pop()); 543 res.redirect(this.href(req.action)); 544 return; 545 } 546 res.data.action = this.href(req.action); 547 res.data.title = gettext("Referrers in the last 24 hours of {0}", this.title); 548 res.data.body = this.renderSkinAsString("$Site#referrers"); 549 this.renderSkin("Site#page"); 550 return; 551 } 552 553 Site.prototype.search_action = function() { 554 var search; 555 if (!(search = req.data.q) || !stripTags(search)) { 556 res.message = gettext("Please enter a query in the search form."); 557 res.data.body = this.renderSkinAsString("Site#search"); 558 } else { 559 // Prepare search string for metadata: Get source and remove 560 // '(new String("..."))' wrapper; finally, double all backslashes 561 search = String(search).toSource().slice(13, -3).replace(/(\\)/g, "$1$1"); 562 var title = '%title:"%' + search + '%"%'; 563 var text = '%text:"%' + search + '%"%'; 564 var sql = new Sql(); 565 sql.retrieve("select id from content where site_id = $0 and " + 566 "prototype = $1 and status <> $2 and (metadata like $3 or " + 567 "metadata like $4) order by created desc limit $5", 568 this._id, "Story", Story.CLOSED, text, title, 25); 569 res.push(); 570 var counter = 0; 571 sql.traverse(function() { 572 var story = Story.getById(this.id); 573 story.renderSkin("Story#result"); 574 counter += 1; 575 }); 576 res.message = ngettext("Found {0} result.", 577 "Found {0} results.", counter); 578 res.data.body = res.pop(); 579 } 580 581 res.data.title = gettext('Search results for "{0}" in site "{1}"', 582 search, this.title); 583 this.renderSkin("Site#page"); 584 return; 585 } 586 587 Site.prototype.subscribe_action = function() { 588 try { 589 var membership = new Membership(session.user, Membership.SUBSCRIBER); 590 this.members.add(membership); 591 res.message = gettext('Successfully subscribed to site {0}.', 592 this.title); 593 } catch (ex) { 594 app.log(ex); 595 res.message = ex.toString(); 596 } 597 res.redirect(this.href()); 598 return; 599 } 600 601 Site.prototype.unsubscribe_action = function() { 602 if (req.postParams.proceed) { 603 try { 604 Membership.remove(Membership.getByName(session.user.name)); 605 res.message = gettext("Successfully unsubscribed from site {0}.", 606 this.title); 607 res.redirect(User.getLocation() || this.href()); 608 } catch (ex) { 609 app.log(ex) 610 res.message = ex.toString(); 611 } 612 } 613 614 User.setLocation(); 615 res.data.title = gettext("Remove subscription to {0}", this.title); 616 res.data.body = this.renderSkinAsString("$HopObject#confirm", { 617 text: gettext('You are about to unsubscribe from site {0}.', this.title) 618 }); 619 this.renderSkin("Site#page"); 620 return; 621 } 622 623 Site.prototype.export_action = function() { 624 var fname = this.name + "-export.zip"; 625 var zip = new helma.File(this.getStaticFile(fname)); 626 if (req.postParams.submit === "export") { 627 if (Exporter.add(this)) { 628 res.message = "Site is queued for export"; 629 zip.remove(); 630 } else { 631 res.message = "Site is already being exported"; 632 } 633 res.redirect(this.href(req.action)); 634 } 635 var param = { 636 fileName: zip.getName(), 637 fileUrl: zip.exists() ? this.getStaticUrl(zip.getName()) : null, 638 fileDate: new Date(zip.lastModified()) 639 } 640 res.data.body = this.renderSkinAsString("$Site#export", param); 641 this.renderSkin("Site#page"); 642 return; 643 } 644 645 Site.prototype.robots_txt_ction = function() { 646 res.contentType = "text/plain"; 647 this.renderSkin("Site#robots"); 648 return; 649 } 650 651 /** 652 * 653 * @param {String} name 654 * @returns {HopObject} 655 */ 656 Site.prototype.getMacroHandler = function(name) { 657 switch (name) { 658 case "archive": 659 case "files": 660 case "galleries": 661 case "images": 662 case "layout": 663 case "members": 664 case "polls": 665 case "stories": 666 case "tags": 667 return this[name]; 668 default: 669 return null; 670 } 671 } 672 673 /** 674 * 675 */ 676 Site.prototype.stories_macro = function() { 677 if (this.stories.featured.size() < 1) { 678 this.renderSkin("Site#welcome"); 679 if (session.user) { 680 if (session.user === this.creator) { 681 session.user.renderSkin("$User#welcome"); 682 } 683 if (this === root && User.require(User.PRIVILEGED)) { 684 this.admin.renderSkin("$Admin#welcome"); 685 } 686 } 687 } else { 688 this.archive.renderSkin("Archive#main"); 689 } 690 return; 691 } 692 693 /** 694 * 695 * @param {Object} param 696 */ 697 Site.prototype.calendar_macro = function(param) { 698 if (this.archiveMode !== Site.PUBLIC) { 699 return; 700 } 701 var calendar = new jala.Date.Calendar(this.archive); 702 //calendar.setAccessNameFormat("yyyy/MM/dd"); 703 calendar.setHrefFormat("/yyyy/MM/dd/"); 704 calendar.setLocale(this.getLocale()); 705 calendar.setTimeZone(this.getTimeZone()); 706 calendar.render(this.archive.getDate()); 707 return; 708 } 709 710 /** 711 * 712 * @param {Object} param 713 */ 714 Site.prototype.age_macro = function(param) { 715 res.write(Math.floor((new Date() - this.created) / Date.ONEDAY)); 716 return; 717 } 718 719 /** 720 * 721 */ 722 Site.prototype.referrers_macro = function() { 723 var self = this; 724 var sql = new Sql(); 725 sql.retrieve("select referrer, count(*) as requests from " + 726 "log where context_type = 'Site' and context_id = $0 and action = " + 727 "'main' and created > date_add(now(), interval -1 day) group " + 728 "by referrer order by requests desc, referrer asc", this._id); 729 sql.traverse(function() { 730 if (this.requests && this.referrer) { 731 this.text = encode(this.referrer.head(50)); 732 this.referrer = encode(this.referrer); 733 self.renderSkin("$Site#referrer", this); 734 } 735 }); 736 return; 737 } 738 739 /** 740 * @returns {java.util.Locale} 741 */ 742 Site.prototype.getLocale = function() { 743 var locale; 744 if (locale = this.cache.locale) { 745 return locale; 746 } else if (this.locale) { 747 var parts = this.locale.split("_"); 748 locale = new java.util.Locale(parts[0] || String.EMPTY, 749 parts[1] || String.EMPTY, parts.splice(2).join("_")); 750 } else { 751 locale = java.util.Locale.getDefault(); 752 } 753 return this.cache.locale = locale; 754 } 755 756 /** 757 * @returns {java.util.TimeZone} 758 */ 759 Site.prototype.getTimeZone = function() { 760 var timeZone; 761 if (timeZone = this.cache.timeZone) { 762 return timeZone; 763 } 764 if (this.timeZone) { 765 timeZone = java.util.TimeZone.getTimeZone(this.timeZone); 766 } else { 767 timeZone = java.util.TimeZone.getDefault(); 768 } 769 this.cache.timezone = timeZone; 770 return timeZone; 771 } 772 773 /** 774 * 775 * @param {String} href 776 */ 777 Site.prototype.processHref = function(href) { 778 var vhost = getProperty("vhost." + this.name, 779 app.properties.defaultHost + "/" + this.name); 780 return vhost + href; 781 } 782 783 /** 784 * 785 * @param {String} type 786 * @param {String} group 787 * @returns {Tag[]} 788 */ 789 Site.prototype.getTags = function(type, group) { 790 var handler; 791 type = type.toLowerCase(); 792 switch (type) { 793 case "story": 794 case "tags": 795 handler = this.stories; 796 type = "tags"; 797 break; 798 case "image": 799 case "galleries": 800 handler = this.images; 801 type = "galleries"; 802 break; 803 } 804 switch (group) { 805 case Tags.ALL: 806 return handler[type]; 807 case Tags.OTHER: 808 case Tags.ALPHABETICAL: 809 return handler[group + type.titleize()]; 810 default: 811 return handler["alphabetical" + type.titleize()].get(group); 812 } 813 return null; 814 } 815 816 /** 817 * 818 * @param {String} tail 819 * @returns {helma.File} 820 */ 821 Site.prototype.getStaticFile = function(tail) { 822 res.push(); 823 res.write(app.properties.staticPath); 824 res.write(this.name); 825 res.write("/"); 826 tail && res.write(tail); 827 return new helma.File(res.pop()); 828 } 829 830 /** 831 * 832 * @param {String} tail 833 * @returns {String} 834 */ 835 Site.prototype.getStaticUrl = function(tail) { 836 res.push(); 837 res.write(app.properties.staticUrl); 838 res.write(this.name); 839 res.write("/"); 840 tail && res.write(tail); 841 return encodeURI(res.pop()); 842 } 843 844 /** 845 * 846 * @param {Object} ref 847 */ 848 Site.prototype.callback = function(ref) { 849 if (this.callbackMode === Site.ENABLED && this.callbackUrl) { 850 app.data.callbacks.push({ 851 site: this._id, 852 handler: ref.constructor, 853 id: ref._id 854 }); 855 } 856 return; 857 } 858 859 /** 860 * 861 * @param {String} name 862 * @returns {String[]} 863 */ 864 Site.prototype.getAdminHeader = function(name) { 865 switch (name) { 866 case "tags": 867 case "galleries": 868 return ["#", "Name", "Items"]; 869 } 870 return []; 871 } 872