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