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