      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 Root prototype.
     28  */
     29
     30 /** @constant */
     31 Root.VERSION = (function(versionString, buildDate) {
     32    // A valid version string is e.g. "1.2.3alpha.4711".
     33    // Repositories could add something like "-compatible" to it,
     34    // FIXME: This should be refactored for modular extension.
     35    var re = /^(\d+)\.(\d+)(?:\.(\d+))?(.+)?\.(\d+)(?:-(.*))?$/;
     36    var parts = re.exec(versionString);
     37    if (parts) {
     38       var result = {
     39          parts: parts,
     40          toString: function() {return parts[0]},
     41          major: parseInt(parts[1]),
     42          revision: parseInt(parts[5]),
     43          date: new Date(buildDate)
     44       };
     45       result.minor = result.major + parseInt(parts[2] || 0) / 10;
     46       result.bugfix = result.minor + "." + (parts[3] || 0);
     47       result.development = parts[4] || "final";
     48       result["default"] = result[parts[3] ? "bugfix" : "minor"] + result.development +
     49             (parts[6] ? "-" + parts[6] : String.EMPTY);
     50       return result;
     51    }
     52    return versionString;
     53 })("@version@.@revision@", "@buildDate@");
     54
     55 this.handleMetadata("creationDelay");
     56 this.handleMetadata("creationScope");
     57 this.handleMetadata("notificationScope");
     58 this.handleMetadata("phaseOutGracePeriod");
     59 this.handleMetadata("phaseOutNotificationPeriod");
     60 this.handleMetadata("phaseOutMode");
     61 this.handleMetadata("probationPeriod");
     62 this.handleMetadata("quota");
     63 this.handleMetadata("replyTo");
     64
     65 /**
     66  * Antville’s root object is an extent of the Site prototype.
     67  * @name Root
     68  * @constructor
     69  * @property {Site[]} _children
     70  * @property {Admin} admin
     71  * @property {User[]} admins
     72  * @property {Api} api
     73  * @property {String} creationDelay
     74  * @property {String} creationScope
     75  * @property {String} notificationScope
     76  * @property {String} phaseOutGracePeriod
     77  * @property {String} phaseOutMode
     78  * @property {String} phaseOutNotificationPeriod
     79  * @property {String} probationPeriod
     80  * @property {String} quote
     81  * @property {String} replyTo
     82  * @property {Site[]} sites
     83  * @property {Site[]} updates
     84  * @property {User[]} users
     85  * @extends Site
     86  */
     87
     88 /**
     89  *
     90  * @param {String} action
     91  * @returns {Boolean}
     92  */
     93 Root.prototype.getPermission = function(action) {
     94    if (action.contains("admin")) {
     95       return User.require(User.PRIVILEGED);
     96    }
     97    switch (action) {
     98       case "debug":
     99       case "default.hook":
    100       case "health":
    101       case "jala.test":
    102       case "jala.test.css":
    103       case "mrtg":
    104       case "sites":
    105       case "updates.xml":
    106       return true;
    107       case "create":
    108       return this.getCreationPermission();
    109    }
    110    return Site.prototype.getPermission.apply(this, arguments);
    111 }
    112
    113 Root.prototype.main_action = function() {
    114    if (this.users.size() < 1) {
    115       this.title = "Antville";
    116       this.created = new Date;
    117       this.replyTo = "root@localhost";
    118       this.locale = java.util.Locale.getDefault().getLanguage();
    119       this.timeZone = java.util.TimeZone.getDefault().getID();
    120       this.layout.reset();
    121       res.redirect(this.members.href("register"));
    122    } else if (session.user && this.members.owners.size() < 1) {
    123       this.creator = this.modifier = this.layout.creator =
    124             this.layout.modifier = session.user;
    125       this.created = this.modified = this.layout.created =
    126             this.layout.modified = new Date;
    127       session.user.role = User.PRIVILEGED;
    128       res.handlers.membership.role = Membership.OWNER;
    129    }
    130    return Site.prototype.main_action.apply(this);
    131 }
    132
    133 Root.prototype.error_action = function() {
    134    res.message = String.EMPTY;
    135    var param = res.error ? res : session.data;
    136    res.status = param.status || 500;
    137    res.data.title = gettext("{0} {1} Error", root.getTitle(), param.status);
    138    res.data.body = root.renderSkinAsString("$Root#error", param);
    139    res.handlers.site.renderSkin("Site#page");
    140    return;
    141 }
    142
    143 Root.prototype.notfound_action = function() {
    144    res.status = 404;
    145    res.data.title = gettext("{0} {1} Error", root.getTitle(), res.status);
    146    res.data.body = root.renderSkinAsString("$Root#notfound", req);
    147    res.handlers.site.renderSkin("Site#page");
    148    return;
    149 }
    150
    151 Root.prototype.create_action = function() {
    152    if (req.postParams.create) {
    153       try {
    154          var site = new Site;
    155          site.update(req.postParams);
    156          site.layout.reset();
    157          this.add(site);
    158          site.members.add(new Membership(session.user, Membership.OWNER));
    159          root.admin.log(root, "Added site " + site.name);
    160          res.message = gettext("Successfully created your site.");
    161          res.redirect(site.href());
    162       } catch (ex) {
    163          res.message = ex;
    164          app.log(ex);
    165       }
    166    }
    167
    168    // Cannot use res.handlers.site because somehow it is always root
    169    res.handlers.newSite = new Site;
    170    res.handlers.example = new Site;
    171    res.handlers.example.name = "foo";
    172    res.data.action = this.href(req.action);
    173    res.data.title = gettext("Add Site");
    174    res.data.body = root.renderSkinAsString("$Root#create");
    175    root.renderSkin("Site#page");
    176    return;
    177 }
    178
    179 Root.prototype.sites_action = function() {
    180    var now = new Date;
    181    if (!this.cache.sites || (now - this.cache.sites.modified > Date.ONEHOUR)) {
    182       var sites = this.sites.list();
    183       sites.sort(new String.Sorter("title"));
    184       this.cache.sites = {list: sites, modified: now};
    185    }
    186    res.data.list = renderList(this.cache.sites.list,
    187          "$Site#listItem", 25, req.queryParams.page);
    188    res.data.pager = renderPager(this.cache.sites.list,
    189          this.href(req.action), 25, req.queryParams.page);
    190    res.data.title = gettext("Public Sites");
    191    res.data.body = this.renderSkinAsString("$Root#sites");
    192    root.renderSkin("Site#page");
    193    return;
    194 }
    195
    196 Root.prototype.updates_xml_action = function() {
    197    var now = new Date;
    198    var feed = new rome.SyndFeedImpl();
    199    feed.setFeedType("rss_2.0");
    200    feed.setLink(root.href());
    201    feed.setTitle("Recently updated sites at " + root.title);
    202    feed.setDescription(root.tagline || String.EMPTY);
    203    feed.setLanguage(root.locale.replace("_", "-"));
    204    feed.setPublishedDate(now);
    205    var entries = new java.util.ArrayList();
    206    var entry, description;
    207    var sites = root.updates.list(0, 25).sort(Number.Sorter("modified",
    208          Number.Sorter.DESC));
    209    for each (var site in sites) {
    210       entry = new rome.SyndEntryImpl();
    211       entry.setTitle(site.title);
    212       entry.setLink(site.href());
    213       entry.setAuthor(site.creator.name);
    214       entry.setPublishedDate(site.modified);
    215       description = new rome.SyndContentImpl();
    216       description.setType("text/plain");
    217       description.setValue(site.tagline);
    218       entry.setDescription(description);
    219       entries.add(entry);
    220    }
    221    feed.setEntries(entries);
    222    var output = new rome.SyndFeedOutput();
    223    //output.output(feed, res.servletResponse.writer); return;
    224    var xml = output.outputString(feed);
    225    res.contentType = "text/xml";
    226    res.write(xml); //injectXslDeclaration(xml));
    227    return;
    228 }
    229
    230 /**
    231  * Sitemap for Google Webmaster Tools
    232  * (Unfortunately, utterly useless.)
    233  */
    234 Root.prototype.sitemap_xml_action = function() {
    235    res.contentType = "text/xml";
    236    res.writeln('<?xml version="1.0" encoding="UTF-8"?>');
    237    res.writeln('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
    238    this.sites.forEach(function() {
    239       res.writeln('<url>');
    240       res.writeln('<loc>' + this.href() + '</loc>');
    241       if (this.modified) {
    242          res.writeln('<lastmod>' + this.modified.format("yyyy-MM-dd") + '</lastmod>');
    243       }
    244       res.writeln('</url>');
    245    });
    246    res.writeln('</urlset>');
    247    return;
    248 }
    249
    250 Root.prototype.health_action = function() {
    251    var jvm = java.lang.Runtime.getRuntime();
    252    var totalMemory = jvm.totalMemory() / 1024 / 1024;
    253    var freeMemory = jvm.freeMemory()  / 1024 / 1024;
    254
    255    var param = {
    256       uptime: formatNumber((new Date - app.upSince.getTime()) /
    257             Date.ONEDAY, "0.##"),
    258       freeMemory: formatNumber(freeMemory),
    259       totalMemory: formatNumber(totalMemory),
    260       usedMemory: formatNumber(totalMemory - freeMemory),
    261       sessions: formatNumber(app.countSessions()),
    262       cacheSize: formatNumber(getProperty("cacheSize"))
    263    };
    264
    265    for each (key in ["activeThreads", "freeThreads", "requestCount",
    266          "errorCount", "xmlrpcCount", "cacheusage"]) {
    267       param[key] = formatNumber(app[key]);
    268    }
    269
    270    if (Admin.health) {
    271       param.requestsPerUnit = formatNumber(Admin.health.requestsPerUnit);
    272       param.errorsPerUnit = formatNumber(Admin.health.errorsPerUnit);
    273    }
    274
    275    param.entries = app.data.entries.length;
    276    param.mails = app.data.mails.length;
    277    param.requests = 0;
    278    for (var i in app.data.requests) {
    279       param.requests += 1;
    280    }
    281    param.callbacks = app.data.callbacks.length;
    282
    283    res.data.title = gettext("{0} Health", root.getTitle());
    284    res.data.body = this.renderSkinAsString("$Root#health", param);
    285    this.renderSkin("Site#page");
    286 }
    287
    288 Root.prototype.mrtg_action = function() {
    289    res.contentType = "text/plain";
    290    var target = req.queryParams.target;
    291    if (!target) {
    292       return;
    293    }
    294    switch (target) {
    295       case "cache":
    296       res.writeln(0);
    297       res.writeln(app.cacheusage * 100 / getProperty("cacheSize", 100));
    298       break;
    299       case "threads":
    300       res.writeln(0);
    301       res.writeln(app.activeThreads * 100 / app.freeThreads);
    302       break;
    303       case "requests":
    304       res.writeln(app.errorCount);
    305       res.writeln(app.requestCount);
    306       break;
    307       case "users":
    308       res.writeln(app.countSessions());
    309       res.writeln(root.users.size());
    310       break;
    311       case "postings":
    312       res.writeln(0);
    313       var sql = new Sql;
    314       sql.retrieve(Sql.COUNT, "content");
    315       sql.traverse(function() {
    316          res.writeln(this.count);
    317       });
    318       break;
    319       case "uploads":
    320       var sql = new Sql;
    321       sql.retrieve(Sql.COUNT, "file");
    322       sql.traverse(function() {
    323          res.writeln(this.count);
    324       });
    325       sql.retrieve(Sql.COUNT, "image");
    326       sql.traverse(function() {
    327          res.writeln(this.count);
    328       });
    329       break;
    330    }
    331    res.writeln(app.upSince);
    332    res.writeln("mrtg." + target + " of Antville version " + Root.VERSION);
    333    return;
    334 }
    335
    336 /**
    337  *
    338  * @param {String} name
    339  * @returns {HopObject}
    340  * @see Site#getMacroHandler
    341  */
    342 Root.prototype.getMacroHandler = function(name) {
    343    switch (name) {
    344       case "admin":
    345       case "api":
    346       case "sites":
    347       return this[name];
    348    }
    349    return Site.prototype.getMacroHandler.apply(this, arguments);
    350 }
    351
    352 /**
    353  *
    354  * @param {String} name
    355  * @returns {Object}
    356  * @see Site#getFormOptions
    357  */
    358 Root.prototype.getFormOptions = function(name) {
    359    switch (name) {
    360       case "creationScope":
    361       return Admin.getCreationScopes();
    362       case "notificationScope":
    363       return Admin.getNotificationScopes();
    364       case "phaseOutMode":
    365       return Admin.getPhaseOutModes();
    366    }
    367    return Site.prototype.getFormOptions.apply(root, arguments);
    368 }
    369
    370 /**
    371  * @returns {Boolean}
    372  */
    373 Root.prototype.getCreationPermission = function() {
    374    var user;
    375    if (!(user = session.user)) {
    376       return false;
    377    } if (User.require(User.PRIVILEGED)) {
    378       return true;
    379    }
    380
    381    switch (root.creationScope) {
    382       case User.PRIVILEGED:
    383       return false;
    384       case User.TRUSTED:
    385       return User.require(User.TRUSTED);
    386       default:
    387       case User.REGULAR:
    388       var now = new Date, delta;
    389       if (root.probationPeriod) {
    390          delta = root.probationPeriod - Math.floor((now -
    391                user.created) / Date.ONEDAY);
    392          if (delta > 0) {
    393             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.",
    394                   ngettext("{0} day", "{0} days", delta));
    395             return false;
    396          }
    397       }
    398       if (root.creationDelay && user.sites.count() > 0) {
    399          delta = root.creationDelay - Math.floor((now -
    400                user.sites.get(0).created) / Date.ONEDAY);
    401          if (delta > 0) {
    402             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.",
    403                   ngettext("{0} day", "{0} days", delta));
    404             return false;
    405          }
    406       }
    407    }
    408    return true;
    409 }
    410
    411 /**
    412  *
    413  * @param {Object} param
    414  * @param {String} name
    415  */
    416 Root.prototype.static_macro = function(param, name) {
    417    return this.getStaticUrl(name);
    418 }
    419