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