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