1 //
  2 // The Antville Project
  3 // http://code.google.com/p/antville
  4 //
  5 // Copyright 2001-2007 by The Antville People
  6 //
  7 // Licensed under the Apache License, Version 2.0 (the ``License'');
  8 // you may not use this file except in compliance with the License.
  9 // You may obtain a copy of the License at
 10 //
 11 //    http://www.apache.org/licenses/LICENSE-2.0
 12 //
 13 // Unless required by applicable law or agreed to in writing, software
 14 // distributed under the License is distributed on an ``AS IS'' BASIS,
 15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16 // See the License for the specific language governing permissions and
 17 // limitations under the License.
 18 //
 19 // $Revision$
 20 // $LastChangedBy$
 21 // $LastChangedDate$
 22 // $URL$
 23 //
 24 
 25 /**
 26  * @fileOverview Defines the Root prototype.
 27  */
 28 
 29 /** @constant */
 30 Root.VERSION = "1.2-beta";
 31 
 32 this.handleMetadata("creationDelay");
 33 this.handleMetadata("creationScope");
 34 this.handleMetadata("notificationScope");
 35 this.handleMetadata("phaseOutGracePeriod");
 36 this.handleMetadata("phaseOutNotificationPeriod");
 37 this.handleMetadata("phaseOutMode");
 38 this.handleMetadata("probationPeriod");
 39 this.handleMetadata("quota");
 40 this.handleMetadata("replyTo");
 41 
 42 /**
 43  * Antville’s root object is an extent of the Site prototype.
 44  * @name Root
 45  * @constructor
 46  * @property {Site[]} _children 
 47  * @property {Admin} admin
 48  * @property {User[]} admins
 49  * @property {Api} api
 50  * @property {String} creationDelay
 51  * @property {String} creationScope
 52  * @property {String} notificationScope
 53  * @property {String} phaseOutGracePeriod
 54  * @property {String} phaseOutMode
 55  * @property {String} phaseOutNotificationPeriod
 56  * @property {String} probationPeriod
 57  * @property {String} quote
 58  * @property {String} replyTo
 59  * @property {Site[]} sites
 60  * @property {Site[]} updates
 61  * @property {User[]} users
 62  * @extends Site
 63  */
 64 
 65 /**
 66  * 
 67  * @param {String} action
 68  * @returns {Boolean}
 69  */
 70 Root.prototype.getPermission = function(action) {
 71    if (action.contains("admin")) {
 72       return User.require(User.PRIVILEGED);
 73    }
 74    switch (action) {
 75       case "debug":
 76       case "default.hook":
 77       case "health":
 78       case "mrtg":
 79       case "sites":
 80       case "updates.xml":
 81       return true;
 82       case "create":
 83       case "import":
 84       return this.getCreationPermission();
 85    }
 86    return Site.prototype.getPermission.apply(this, arguments);
 87 }
 88 
 89 Root.prototype.main_action = function() {
 90    if (this.users.size() < 1) {
 91       root.title = "Antville";
 92       root.locale = java.util.Locale.getDefault().getLanguage();
 93       root.timeZone = java.util.TimeZone.getDefault().getID();
 94       res.redirect(this.members.href("register"));
 95    } else if (session.user && this.members.owners.size() < 1) {
 96       this.creator = this.modifier = this.layout.creator = 
 97             this.layout.modifier = session.user;
 98       this.created = this.modified = 
 99             this.layout.created = this.layout.modified = new Date;
100       session.user.role = User.PRIVILEGED;
101       res.handlers.membership.role = Membership.OWNER;
102    }
103    return Site.prototype.main_action.apply(this);
104 }
105 
106 Root.prototype.error_action = function() {
107    res.message = String.EMPTY;
108    var param = res.error ? res : session.data;
109    res.status = param.status || 500;
110    res.data.title = gettext("{0} {1} Error", root.getTitle(), param.status);
111    res.data.body = root.renderSkinAsString("$Root#error", param);
112    res.handlers.site.renderSkin("Site#page");
113    return;
114 }
115 
116 Root.prototype.notfound_action = function() {
117    res.status = 404;
118    res.data.title = gettext("{0} {1} Error", root.getTitle(), res.status);
119    res.data.body = root.renderSkinAsString("$Root#notfound", req);
120    res.handlers.site.renderSkin("Site#page");
121    return;
122 }
123 
124 Root.prototype.create_action = function() {
125    var site = new Site;
126    if (req.postParams.create) {
127       try {
128          site.update(req.postParams);
129          //root.layout.getFile().copyDirectory(site.layout.getFile());
130          site.layout.reset();
131          this.add(site);
132          site.members.add(new Membership(session.user, Membership.OWNER));
133          root.admin.log(root, "Added site " + site.name);
134          res.message = gettext("Successfully created your site.");
135          res.redirect(site.href());
136       } catch (ex) {
137          res.message = ex;
138          app.log(ex);
139       }
140    }
141 
142    res.handlers.example = new Site;
143    res.handlers.example.name = "foo";
144    res.data.action = this.href(req.action);
145    res.data.title = gettext("Add Site");
146    res.data.body = site.renderSkinAsString("$Site#create");
147    root.renderSkin("Site#page");
148    return;
149 }
150 
151 Root.prototype.sites_action = function() {
152    var now = new Date;
153    if (!this.cache.sites || (now - this.cache.sites.modified > Date.ONEHOUR)) {
154       var sites = this.sites.list();
155       sites.sort(new String.Sorter("title"));
156       this.cache.sites = {list: sites, modified: now};
157    }
158    res.data.list = renderList(this.cache.sites.list, 
159          "$Site#listItem", 25, req.queryParams.page);
160    res.data.pager = renderPager(this.cache.sites.list, 
161          this.href(req.action), 25, req.queryParams.page);
162    res.data.title = gettext("Public Sites");
163    res.data.body = this.renderSkinAsString("$Root#sites");
164    root.renderSkin("Site#page");
165    return;
166 }
167 
168 Root.prototype.updates_xml_action = function() {
169    var now = new Date;
170    var feed = new rome.SyndFeedImpl();   
171    feed.setFeedType("rss_2.0");
172    feed.setLink(root.href());
173    feed.setTitle("Recently updated sites at " + root.title);
174    feed.setDescription(root.tagline);
175    feed.setLanguage(root.locale.replace("_", "-"));
176    feed.setPublishedDate(now);
177    var entries = new java.util.ArrayList();
178    var entry, description;
179    var sites = root.updates.list(0, 25).sort(Number.Sorter("modified", 
180          Number.Sorter.DESC));
181    for each (var site in sites) {
182       entry = new rome.SyndEntryImpl();
183       entry.setTitle(site.title);
184       entry.setLink(site.href());
185       entry.setAuthor(site.creator.name);
186       entry.setPublishedDate(site.modified);
187       description = new rome.SyndContentImpl();
188       description.setType("text/plain");
189       description.setValue(site.tagline);
190       entry.setDescription(description);
191       entries.add(entry);
192    }
193    feed.setEntries(entries);
194    var output = new rome.SyndFeedOutput();
195    //output.output(feed, res.servletResponse.writer); return;
196    var xml = output.outputString(feed);
197    res.contentType = "text/xml";
198    res.write(xml); //injectXslDeclaration(xml));
199    return;
200 }
201 
202 /**
203  * Sitemap for Google Webmaster Tools
204  * (Unfortunately, utterly useless.)
205  */
206 Root.prototype.sitemap_xml_action = function() {
207    res.contentType = "text/xml";
208    res.writeln('<?xml version="1.0" encoding="UTF-8"?>');
209    res.writeln('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
210    this.sites.forEach(function() {
211       res.writeln('<url>');
212       res.writeln('<loc>' + this.href() + '</loc>');
213       if (this.modified) {
214          res.writeln('<lastmod>' + this.modified.format("yyyy-MM-dd") + '</lastmod>');
215       }
216       res.writeln('</url>');
217    });
218    res.writeln('</urlset>');
219    return;
220 }
221 
222 Root.prototype.health_action = function() {
223    var jvm = java.lang.Runtime.getRuntime();
224    var totalMemory = jvm.totalMemory() / 1024 / 1024;
225    var freeMemory = jvm.freeMemory()  / 1024 / 1024;
226 
227    var param = {
228       uptime: formatNumber((new Date - app.upSince.getTime()) / 
229             Date.ONEDAY, "0.##"),
230       freeMemory: formatNumber(freeMemory),
231       totalMemory: formatNumber(totalMemory),
232       usedMemory: formatNumber(totalMemory - freeMemory),
233       sessions: formatNumber(app.countSessions()),
234       cacheSize: formatNumber(getProperty("cacheSize"))
235    };
236 
237    for each (key in ["activeThreads", "freeThreads", "requestCount", 
238          "errorCount", "xmlrpcCount", "cacheusage"]) {
239       param[key] = formatNumber(app[key]);
240    }
241 
242    if (Admin.health) {
243       param.requestsPerUnit = formatNumber(Admin.health.requestsPerUnit);
244       param.errorsPerUnit = formatNumber(Admin.health.errorsPerUnit);
245    }
246    
247    param.entries = app.data.entries.length;
248    param.mails = app.data.mails.length;
249    param.requests = 0;
250    for (var i in app.data.requests) {
251       param.requests += 1;
252    }
253    param.callbacks = app.data.callbacks.length;
254 
255    res.data.title = gettext("{0} Health", root.getTitle());
256    res.data.body = this.renderSkinAsString("$Root#health", param);
257    this.renderSkin("Site#page");
258 }
259 
260 Root.prototype.import_action = function() {
261    res.debug(app.data.imports.toSource())
262    var baseDir = this.getStaticFile();
263    var importDir = new java.io.File(baseDir, "import");
264    if (req.postParams.submit === "import") {
265       var data = req.postParams;
266       try {
267          if (!data.file) {
268             throw Error(gettext("Please choose a ZIP file to import"));
269          }
270          var site = new Site;
271          site.update({name: data.name});
272          site.members.add(new Membership(session.user, Membership.OWNER));
273          root.add(site);
274          Importer.add(new java.io.File(importDir, data.file), 
275              site, session.user);
276          res.message = gettext("Queued import of {0} into site »{1}«",
277                data.file, site.name);
278          res.redirect(this.href(req.action));
279       } catch (ex) {
280          res.message = ex.toString();
281          app.log(ex.toString());
282       }
283    }
284 
285    res.push();
286    for each (var file in importDir.listFiles()) {
287       if (file.toString().endsWith(".zip")) {
288          this.renderSkin("$Root#importItem", {
289             file: file.getName(),
290             status: Importer.getStatus(file)
291          });
292       }
293    }
294    res.data.body = this.renderSkinAsString("$Root#import", {list: res.pop()});
295    this.renderSkin("Site#page");
296    return;
297 }
298 
299 Root.prototype.mrtg_action = function() {
300    res.contentType = "text/plain";
301    switch (req.queryParams.target) {
302       case "cache":
303       res.writeln(0);
304       res.writeln(app.cacheusage * 100 / getProperty("cacheSize"));
305       break;
306       case "threads":
307       res.writeln(0);
308       res.writeln(app.activeThreads * 100 / app.freeThreads);
309       break;
310       case "requests":
311       res.writeln(app.errorCount);
312       res.writeln(app.requestCount);
313       break;
314       case "users":
315       res.writeln(app.countSessions());
316       res.writeln(root.users.size());
317       break;
318       case "postings":
319       var db = getDBConnection("antville");
320       var postings = db.executeRetrieval("select count(*) as count from content");
321       postings.next();
322       res.writeln(0);
323       res.writeln(postings.getColumnItem("count"));
324       postings.release();
325       break;
326       case "uploads":
327       var db = getDBConnection("antville");
328       var files = db.executeRetrieval("select count(*) as count from file");
329       var images = db.executeRetrieval("select count(*) as count from image");
330       files.next();
331       images.next()
332       res.writeln(files.getColumnItem("count"));
333       res.writeln(images.getColumnItem("count"));
334       files.release();
335       images.release();
336       break;
337    }
338    res.writeln(app.upSince);
339    res.writeln("mrtg." + req.queryParams.target + " of Antville version " + Root.VERSION);
340    return;
341 } 
342 
343 /**
344  * 
345  * @param {String} name
346  * @returns {HopObject}
347  * @see Site#getMacroHandler
348  */
349 Root.prototype.getMacroHandler = function(name) {
350    switch (name) {
351       case "admin":
352       case "api":
353       case "sites":
354       return this[name];
355    }
356    return Site.prototype.getMacroHandler.apply(this, arguments);
357 }
358 
359 /**
360  * 
361  * @param {String} name
362  * @returns {Object}
363  * @see Site#getFormOptions
364  */
365 Root.prototype.getFormOptions = function(name) {
366    switch (name) {
367       case "creationScope":
368       return Admin.getCreationScopes();
369       case "notificationScope":
370       return Admin.getNotificationScopes();
371       case "phaseOutMode":
372       return Admin.getPhaseOutModes();
373    }
374    return Site.prototype.getFormOptions.apply(root, arguments);
375 }
376 
377 /**
378  * @returns {Boolean}
379  */
380 Root.prototype.getCreationPermission = function() {
381    var user;
382    if (!(user = session.user)) {
383       return false;
384    } if (User.require(User.PRIVILEGED)) {
385       return true;
386    }
387    
388    switch (root.creationScope) {
389       case User.PRIVILEGED:
390       return false;
391       case User.TRUSTED:
392       return User.require(User.TRUSTED);
393       default:
394       case User.REGULAR:
395       var now = new Date, delta;
396       if (root.probationPeriod) {
397          delta = root.probationPeriod - Math.floor((now - 
398                user.created) / Date.ONEDAY);
399          if (delta > 0) {
400             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.", 
401                   ngettext("{0} day", "{0} days", delta));
402             return false;
403          }
404       } 
405       if (root.creationDelay && user.sites.count() > 0) {
406          delta = root.creationDelay - Math.floor((now - 
407                user.sites.get(0).created) / Date.ONEDAY);
408          if (delta > 0) {
409             session.data.error = gettext("You need to wait {0} before you are allowed to create a new site.", 
410                   ngettext("{0} day", "{0} days", root.creationDelay - days));
411             return false;
412          }
413       }
414    }
415    return true;
416 }
417 
418 /**
419  * This method is called from the build script to extract gettext call strings 
420  * from scripts and skins.
421  * @param {String} script
422  * @param {String} scanDirs
423  * @param {String} potFile
424  */
425 Root.prototype.xgettext = function(script, scanDirs, potFile) {
426    var temp = {print: global.print, readFile: global.readFile};
427    global.print = function(str) {app.log(str);}
428    global.readFile = function(fpath, encoding) {
429       res.push();
430       var file = new helma.File(fpath);
431       file.open({charset: encoding || "UTF-8"});
432       var str;
433       while ((str = file.readln()) !== null) {
434          res.writeln(str);
435       }
436       file.close();
437       return res.pop();
438    }
439    var args = ["-o", potFile, "-e", "utf-8"];
440    for each (var dir in scanDirs.split(" ")) {
441       args.push(app.dir + "/../" + dir);
442    }
443    var file = new helma.File(script);
444    var MessageParser = new Function(file.readAll());
445    MessageParser.apply(global, args);
446    global.print = temp.print;
447    global.readFile = temp.readFile;
448    return;
449 }
450