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