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