antville/code/Root/Root.js

406 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The Antville Project
// http://code.google.com/p/antville
//
// Copyright 20012014 by the Workers of Antville.
//
// Licensed under the Apache License, Version 2.0 (the ``License'');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ``AS IS'' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileOverview Defines the Root prototype.
*/
/** @constant */
Root.VERSION = (function (versionString, buildDate) {
// A valid version string is e.g. '1.2.3alpha.c0ffee'.
// Repositories could add something like '-compatible' to it,
// FIXME: This should be refactored for modular extension.
var re = /^(\d+)\.(\d+)(?:\.(\d+))?(.+)?\.([a-f0-9]+)(?:-(.*))?$/;
var parts = re.exec(versionString);
if (parts) {
var result = {
parts: parts,
toString: function() {return parts[0]},
major: parseInt(parts[1]),
hash: parts[5],
date: new Date(buildDate)
};
result.minor = result.major + parseInt(parts[2] || 0) / 10;
result.bugfix = result.minor + '.' + (parts[3] || 0);
result.development = parts[4] || '';
result['default'] = result[parts[3] ? 'bugfix' : 'minor'] + result.development +
(parts[6] ? '-' + parts[6] : String.EMPTY);
return result;
}
return versionString;
})('<v>0</v>.<h>0</h>', '<d/>');
this.handleMetadata('creationDelay');
this.handleMetadata('creationScope');
this.handleMetadata('notificationScope');
this.handleMetadata('phaseOutGracePeriod');
this.handleMetadata('phaseOutNotificationPeriod');
this.handleMetadata('phaseOutMode');
this.handleMetadata('probationPeriod');
this.handleMetadata('quota');
this.handleMetadata('replyTo');
/**
* Antvilles Root prototype is an extent of the Site prototype.
* The singleton instance root is the first created site (“www”).
* @name Root
* @constructor
* @extends Site
* @property {Site[]} _children
* @property {Admin} admin
* @property {User[]} admins
* @property {Api} api
* @property {String} creationDelay
* @property {String} creationScope
* @property {String} notificationScope
* @property {String} phaseOutGracePeriod
* @property {String} phaseOutMode
* @property {String} phaseOutNotificationPeriod
* @property {String} probationPeriod
* @property {String} quote
* @property {String} replyTo
* @property {Site[]} sites
* @property {Site[]} updates
* @property {User[]} users
*/
/**
* The permission framework handler for the Root prototype.
* @param {String} action
* @returns {Boolean}
*/
Root.prototype.getPermission = function(action) {
if (action && action.contains('admin')) {
return User.require(User.PRIVILEGED);
}
switch (action) {
case 'debug':
case 'default.hook':
case 'favicon.ico':
case 'health':
case 'jala.test':
case 'jala.test.css':
case 'mrtg':
case 'sites':
case 'updates.xml':
return true;
case 'create':
return this.getCreationPermission();
}
return Site.prototype.getPermission.apply(this, arguments);
}
Root.prototype.main_action = function() {
if (this.users.size() < 1) {
this.title = 'Antville';
this.created = this.modified = new Date;
this.replyTo = 'root@localhost';
this.locale = java.util.Locale.getDefault().getLanguage();
this.timeZone = java.util.TimeZone.getDefault().getID();
this.layout.reset();
res.redirect(this.members.href('register'));
} else if (session.user && this.members.owners.size() < 1) {
this.creator = this.modifier = this.layout.creator = this.layout.modifier = session.user;
this.created = this.modified = this.layout.created = this.layout.modified = new Date;
session.user.role = User.PRIVILEGED;
res.handlers.membership.role = Membership.OWNER;
}
return Site.prototype.main_action.apply(this);
}
Root.prototype.error_action = function() {
res.message = String.EMPTY;
var param = res.error ? res : session.data;
res.status = param.status || 500;
res.data.title = gettext('{0} {1} Error', root.getTitle(), param.status);
res.data.body = root.renderSkinAsString('$Root#error', param);
res.handlers.site.renderSkin('Site#page');
return;
}
Root.prototype.notfound_action = function() {
res.status = 404;
res.data.title = gettext('{0} {1} Error', root.getTitle(), res.status);
res.data.body = root.renderSkinAsString('$Root#notfound', req);
res.handlers.site.renderSkin('Site#page');
return;
}
Root.prototype.favicon_ico_action = function () {
res.contentType = 'image/x-icon';
res.writeBinary([]);
};
Root.prototype.create_action = function() {
if (req.postParams.create) {
try {
var site = Site.add(req.postParams);
Membership.add(session.user, Membership.OWNER, site);
root.admin.log(root, 'Added site ' + site.name);
res.message = gettext('Successfully created your site.');
res.redirect(site.href());
} catch (ex) {
res.message = ex;
app.log(ex);
}
}
// Cannot use res.handlers.site because somehow it is always root
res.handlers.newSite = new Site;
res.handlers.example = new Site;
res.handlers.example.name = 'foo';
res.data.action = this.href(req.action);
res.data.title = gettext('Add Site');
res.data.body = root.renderSkinAsString('$Root#create');
root.renderSkin('Site#page');
return;
}
Root.prototype.sites_action = function() {
var now = new Date;
if (!this.cache.sites || (now - this.cache.sites.modified > Date.ONEHOUR)) {
var sites = this.sites.list();
sites.sort(new String.Sorter('title'));
this.cache.sites = {list: sites, modified: now};
}
res.data.list = renderList(this.cache.sites.list, '$Site#listItem', 25, req.queryParams.page);
res.data.pager = renderPager(this.cache.sites.list, this.href(req.action), 25, req.queryParams.page);
res.data.title = gettext('Public Sites');
res.data.body = this.renderSkinAsString('$Root#sites');
root.renderSkin('Site#page');
return;
}
Root.prototype.updates_xml_action = function() {
res.contentType = 'application/rss+xml';
var now = new Date;
var feed = new rome.SyndFeedImpl();
feed.setFeedType('rss_2.0');
feed.setLink(root.href());
feed.setTitle('Recently updated sites at ' + root.title);
feed.setDescription(root.tagline || String.EMPTY);
feed.setLanguage(root.locale.replace('_', '-'));
feed.setPublishedDate(now);
var entries = new java.util.ArrayList();
var entry, description;
var sites = root.updates.list(0, 25);
for each (var site in sites) {
var story = site.stories.union.get(0);
if (!story) {
continue;
}
entry = new rome.SyndEntryImpl();
entry.setTitle(site.title);
entry.setLink(story.href());
entry.setAuthor(story.creator.name);
entry.setPublishedDate(story.modified);
description = new rome.SyndContentImpl();
description.setType('text/plain');
description.setValue(story.format_filter(story.text, {}));
entry.setDescription(description);
entries.add(entry);
}
feed.setEntries(entries);
var output = new rome.SyndFeedOutput();
res.servletResponse.setCharacterEncoding('utf-8');
output.output(feed, res.servletResponse.writer, true);
return;
}
// Sitemap for Google Webmaster Tools
// (Unfortunately, utterly useless.)
Root.prototype.sitemap_xml_action = function() {
res.contentType = 'text/xml';
res.writeln('<?xml version="1.0" encoding="UTF-8"?>');
res.writeln('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
this.sites.forEach(function() {
res.writeln('<url>');
res.writeln('<loc>' + this.href() + '</loc>');
if (this.modified) {
res.writeln('<lastmod>' + this.modified.format("yyyy-MM-dd") + '</lastmod>');
}
res.writeln('</url>');
});
res.writeln('</urlset>');
return;
}
Root.prototype.health_action = function() {
var jvm = java.lang.Runtime.getRuntime();
var totalMemory = jvm.totalMemory() / 1024 / 1024;
var freeMemory = jvm.freeMemory() / 1024 / 1024;
var param = {
uptime: formatNumber((new Date - app.upSince.getTime()) /
Date.ONEDAY, '0.##'),
freeMemory: formatNumber(freeMemory),
totalMemory: formatNumber(totalMemory),
usedMemory: formatNumber(totalMemory - freeMemory),
sessions: formatNumber(app.countSessions()),
cacheSize: formatNumber(getProperty('cacheSize'))
};
for each (key in ['activeThreads', 'freeThreads', 'requestCount',
'errorCount', 'xmlrpcCount', 'cacheusage']) {
param[key] = formatNumber(app[key]);
}
param.errorRatio = formatNumber(100 * app.errorCount / app.requestCount || 0);
param.errorRatioPerUnit = formatNumber(Admin.health.errorsPerUnit / Admin.health.requestsPerUnit || 0);
if (Admin.health) {
param.requestsPerUnit = formatNumber(Admin.health.requestsPerUnit);
param.errorsPerUnit = formatNumber(Admin.health.errorsPerUnit);
}
param.callbacks = app.data.callbacks.length;
param.entries = app.data.entries.length;
param.mails = app.data.mails.length;
param.requests = 0;
for (var i in app.data.requests) {
param.requests += 1;
}
res.data.title = gettext('Health');
res.data.body = this.renderSkinAsString('$Root#health', param);
this.renderSkin('Site#page');
}
Root.prototype.mrtg_action = function() {
res.contentType = 'text/plain';
var target = req.queryParams.target;
if (!target) {
return;
}
switch (target) {
case 'cache':
res.writeln(0);
res.writeln(app.cacheusage * 100 / getProperty('cacheSize', 100));
break;
case 'threads':
res.writeln(0);
res.writeln(app.activeThreads * 100 / app.freeThreads);
break;
case 'requests':
res.writeln(app.errorCount);
res.writeln(app.requestCount);
break;
case 'users':
res.writeln(app.countSessions());
res.writeln(root.users.size());
break;
case 'postings':
res.writeln(0);
var sql = new Sql;
sql.retrieve(Sql.COUNT, 'content');
sql.traverse(function() {
res.writeln(this.count);
});
break;
case 'uploads':
var sql = new Sql;
sql.retrieve(Sql.COUNT, 'file');
sql.traverse(function() {
res.writeln(this.count);
});
sql.retrieve(Sql.COUNT, 'image');
sql.traverse(function() {
res.writeln(this.count);
});
break;
}
res.writeln(app.upSince);
res.writeln('mrtg.' + target + ' of Antville version ' + Root.VERSION);
return;
}
/**
* Catch some undefined macro handlers, then delegate to the super prototype.
* @param {String} name
* @returns {HopObject}
* @see Site#getMacroHandler
*/
Root.prototype.getMacroHandler = function(name) {
switch (name) {
case 'admin':
case 'api':
case 'sites':
return this[name];
}
return Site.prototype.getMacroHandler.apply(this, arguments);
}
/**
*
* @param {String} name
* @returns {Object}
* @see Site#getFormOptions
*/
Root.prototype.getFormOptions = function(name) {
switch (name) {
case 'creationScope':
return Admin.getCreationScopes();
case 'notificationScope':
return Admin.getNotificationScopes();
case 'phaseOutMode':
return Admin.getPhaseOutModes();
}
return Site.prototype.getFormOptions.apply(root, arguments);
}
/**
* @returns {Boolean}
*/
Root.prototype.getCreationPermission = function() {
var user;
if (!(user = session.user)) {
return false;
} if (User.require(User.PRIVILEGED)) {
return true;
}
switch (root.creationScope) {
case User.PRIVILEGED:
return false;
case User.TRUSTED:
return User.require(User.TRUSTED);
default:
case User.REGULAR:
var now = new Date, delta;
if (root.probationPeriod) {
delta = root.probationPeriod - Math.floor((now -
user.created) / Date.ONEDAY);
if (delta > 0) {
session.data.error = gettext('You need to wait {0} before you are allowed to create a new site.',
ngettext('{0} day', '{0} days', delta));
return false;
}
}
if (root.creationDelay && user.sites.count() > 0) {
delta = root.creationDelay - Math.floor((now -
user.sites.get(0).created) / Date.ONEDAY);
if (delta > 0) {
session.data.error = gettext('You need to wait {0} before you are allowed to create a new site.',
ngettext('{0} day', '{0} days', delta));
return false;
}
}
}
return true;
}