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