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