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