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