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:3333 $ 20 // $LastChangedBy:piefke3000 $ 21 // $LastChangedDate:2007-09-15 01:25:23 +0200 (Sat, 15 Sep 2007) $ 22 // $URL$ 23 // 24 25 /** 26 * @fileOverview Defines the Admin prototype. 27 */ 28 29 /** 30 * 31 */ 32 Admin.purgeDatabase = function() { 33 return; 34 } 35 36 /** 37 * @name Admin 38 * @constructor 39 * @property {LogEntry[]} entries 40 * @property {Sites[]} privateSites 41 * @property {Sites[]} sites 42 * @property {Users[]} users 43 * @extends HopObject 44 */ 45 Admin.prototype.constructor = function() { 46 this.filterSites(); 47 this.filterUsers(); 48 this.filterLog(); 49 return this; 50 } 51 52 /** 53 * 54 * @param {Object} action 55 * @returns {Boolean} 56 */ 57 Admin.prototype.getPermission = function(action) { 58 if (!session.user) { 59 return false; 60 } 61 switch (action) { 62 case "users": 63 if (req.queryParams.id === session.user._id) { 64 return false; 65 } 66 } 67 return User.require(User.PRIVILEGED); 68 } 69 70 /** 71 * 72 */ 73 Admin.prototype.onRequest = function() { 74 HopObject.prototype.onRequest.apply(this); 75 if (!session.data.admin) { 76 session.data.admin = new Admin(); 77 } 78 return; 79 } 80 81 /** 82 * 83 * @param {String} name 84 */ 85 Admin.prototype.onUnhandledMacro = function(name) { 86 res.debug("Add " + name + "_macro to Admin!"); 87 return null; 88 } 89 90 Admin.prototype.main_action = function() { 91 return res.redirect(this.href("log")); 92 } 93 94 Admin.prototype.setup_action = function() { 95 if (req.postParams.save) { 96 try { 97 this.update(req.postParams); 98 this.log(root, "setup"); 99 res.message = gettext("Successfully updated the setup."); 100 res.redirect(this.href(req.action)); 101 } catch (ex) { 102 res.message = ex; 103 app.log(ex); 104 } 105 } 106 107 res.data.title = gettext("Setup of {0}", root.title); 108 res.data.action = this.href(req.action); 109 res.data.body = this.renderSkinAsString("$Admin#setup"); 110 root.renderSkin("Site#page"); 111 return; 112 } 113 114 /** 115 * 116 * @param {Object} data 117 */ 118 Admin.prototype.update = function(data) { 119 root.map({ 120 email: data.email, 121 notificationScope: data.notificationScope, 122 quota: data.quota, 123 creationScope: data.creationScope, 124 creationDelay: data.creationDelay, 125 qualifyingPeriod: data.qualifyingPerido, 126 qualifyingDate: data.qualifyingDate, 127 autoCleanupEnabled: data.autoCleanupEnabled, 128 autoCleanupStartTime: data.autoCleanupStartTime, 129 phaseOutPrivateSites: data.phaseOutPrivateSites, 130 phaseOutInactiveSites: data.phaseOutInactiveSites, 131 phaseOutNotificationPeriod: data.phaseOutNotificationPeriod, 132 phaseOutGracePeriod: data.phaseOutGracePeriod 133 }); 134 this.log(root, "Updated setup"); 135 136 // FIXME: 137 //for (var i in app.modules) { 138 // this.applyModuleMethod(app.modules[i], "evalSystemSetup", data); 139 //} 140 return; 141 } 142 143 Admin.prototype.log_action = function() { 144 if (req.postParams.search || req.postParams.filter) { 145 session.data.admin.filterLog(req.postParams); 146 } 147 res.data.list = renderList(session.data.admin.entries, 148 this.renderItem, 10, req.queryParams.page); 149 res.data.pager = renderPager(session.data.admin.entries, 150 this.href(req.action), 10, req.queryParams.page); 151 152 res.data.title = gettext("Administration log of {0}", root.title); 153 res.data.action = this.href(req.action); 154 res.data.body = this.renderSkinAsString("$Admin#log"); 155 res.data.body += this.renderSkinAsString("$Admin#main"); 156 root.renderSkin("Site#page"); 157 return; 158 } 159 160 Admin.prototype.sites_action = function() { 161 if (req.postParams.id) { 162 if (req.postParams.remove === "1") { 163 var site = Site.getById(req.postParams.id); 164 try { 165 Site.remove(site); 166 res.message = gettext("The site {0} was removed successfully.", 167 site.name); 168 res.redirect(this.href(req.action) + "?page=" + req.postParams.page); 169 } catch (err) { 170 res.message = err.toString(); 171 } 172 } else if (req.postParams.save === "1") { 173 this.updateSite(req.postParams); 174 res.message = gettext("The changes were saved successfully."); 175 } 176 res.redirect(this.href(req.action) + "?page=" + req.postParams.page + 177 "#" + req.postParams.id); 178 return; 179 } 180 181 if (req.postParams.search || req.postParams.filter) { 182 session.data.admin.filterSites(req.postParams); 183 } else if (req.queryParams.id) { 184 res.meta.item = Site.getById(req.queryParams.id); 185 } 186 187 res.data.list = renderList(session.data.admin.sites, 188 this.renderItem, 10, req.queryParams.page); 189 res.data.pager = renderPager(session.data.admin.sites, 190 this.href(req.action), 10, req.data.page); 191 192 res.data.title = gettext("Site administration of {0}", root.title); 193 res.data.action = this.href(req.action); 194 res.data.body = this.renderSkinAsString("$Admin#sites"); 195 res.data.body += this.renderSkinAsString("$Admin#main"); 196 root.renderSkin("Site#page"); 197 return; 198 } 199 200 Admin.prototype.users_action = function() { 201 if (req.postParams.search || req.postParams.filter) { 202 session.data.admin.filterUsers(req.postParams); 203 } else if (req.postParams.save) { 204 this.updateUser(req.postParams); 205 res.message = gettext("The changes were saved successfully."); 206 res.redirect(this.href(req.action) + "?page=" + req.postParams.page + 207 "#" + req.postParams.id); 208 } else if (req.queryParams.id) { 209 res.meta.item = User.getById(req.queryParams.id); 210 } 211 212 res.data.list = renderList(session.data.admin.users, 213 this.renderItem, 10, req.data.page); 214 res.data.pager = renderPager(session.data.admin.users, 215 this.href(req.action), 10, req.data.page); 216 217 res.data.title = gettext("User administration of {0}", root.title); 218 res.data.action = this.href(req.action); 219 res.data.body = this.renderSkinAsString("$Admin#users"); 220 res.data.body += this.renderSkinAsString("$Admin#main"); 221 root.renderSkin("Site#page"); 222 return; 223 } 224 225 /** 226 * 227 * @param {Object} data 228 */ 229 Admin.prototype.filterLog = function(data) { 230 data || (data = {}); 231 var sql = ""; 232 if (data.filter > 0) { 233 sql += "where context_type = '"; 234 switch (data.filter) { 235 case "1": 236 sql += "Site"; break; 237 case "2": 238 sql += "User"; break; 239 case "3": 240 sql += "Root"; break; 241 } 242 sql += "' and "; 243 } else { 244 sql += "where " 245 } 246 sql += "action <> 'main' "; 247 if (data.query) { 248 var parts = stripTags(data.query).split(" "); 249 var keyword, like; 250 for (var i in parts) { 251 sql += i < 1 ? "and " : "or "; 252 keyword = parts[i].replace(/\*/g, "%"); 253 like = keyword.contains("%") ? "like" : "="; 254 sql += "action " + like + " '" + keyword + "' "; 255 } 256 } 257 sql += "order by created "; 258 (data.dir == 1) || (sql += "desc"); 259 this.entries.subnodeRelation = sql; 260 return; 261 } 262 263 /** 264 * 265 * @param {Object} data 266 */ 267 Admin.prototype.filterSites = function(data) { 268 data || (data = {}); 269 var sql; 270 switch (data.filter) { 271 case "1": 272 sql = "where mode = 'public' "; break; 273 case "2": 274 sql = "where mode = 'private' "; break; 275 case "3": 276 sql = "where status = 'blocked' "; break; 277 case "4": 278 sql = "where status = 'trusted' "; break; 279 case "0": 280 default: 281 sql = "where true "; 282 } 283 if (data.query) { 284 var parts = stripTags(data.query).split(" "); 285 var keyword, like; 286 for (var i in parts) { 287 sql += i < 1 ? "and " : "or "; 288 keyword = parts[i].replace(/\*/g, "%"); 289 like = keyword.contains("%") ? "like" : "="; 290 sql += "(name " + like + " '" + keyword + "') "; 291 } 292 } 293 switch (data.order) { 294 case "1": 295 sql += "group by created order by created "; break; 296 case "2": 297 sql += "group by name order by name "; break; 298 default: 299 sql += "group by modified order by modified "; break; 300 } 301 (data.dir == 1) || (sql += "desc"); 302 this.sites.subnodeRelation = sql; 303 return; 304 } 305 306 /** 307 * 308 * @param {Object} data 309 */ 310 Admin.prototype.filterUsers = function(data) { 311 data || (data = {}); 312 var sql; 313 switch (data.filter) { 314 case "1": 315 sql = "where status = 'blocked' "; break; 316 case "2": 317 sql = "where status = 'trusted' "; break; 318 case "3": 319 sql = "where status = 'privileged' "; break; 320 default: 321 sql = "where true "; break; 322 } 323 if (data.query) { 324 var parts = stripTags(data.query).split(" "); 325 var keyword, like; 326 for (var i in parts) { 327 sql += i < 1 ? "and " : "or "; 328 keyword = parts[i].replace(/\*/g, "%"); 329 like = keyword.contains("%") ? "like" : "="; 330 if (keyword.contains("@")) { 331 sql += "email " + like + " '" + keyword.replace(/@/g, "") + "' "; 332 } else { 333 sql += "name " + like + " '" + keyword + "' "; 334 } 335 } 336 } 337 switch (data.order) { 338 case "1": 339 sql += "group by created order by created "; break; 340 case "2": 341 sql += "group by created order by name "; break; 342 case "0": 343 default: 344 sql += "group by modified order by modified "; break; 345 } 346 (data.dir == 1) || (sql += "desc"); 347 this.users.subnodeRelation = sql; 348 return; 349 } 350 351 /** 352 * 353 * @param {Object} data 354 */ 355 Admin.prototype.updateSite = function(data) { 356 var site = Site.getById(data.id); 357 if (!site) { 358 throw Error(gettext("Please choose a site you want to edit.")); 359 } 360 if (site.status !== data.status) { 361 var current = site.status; 362 site.status = data.status; 363 this.log(site, "Changed status from " + current + " to " + site.status); 364 } 365 return; 366 } 367 368 /** 369 * 370 * @param {Object} data 371 */ 372 Admin.prototype.updateUser = function(data) { 373 var user = User.getById(data.id); 374 if (!user) { 375 throw Error(gettext("Please choose a user you want to edit.")); 376 } 377 if (user === session.user) { 378 throw Error(gettext("Sorry, you are not allowed to modify your own account.")); 379 } 380 if (data.status !== user.status) { 381 var current = user.status; 382 user.status = data.status; 383 this.log(user, "Changed status from " + current + " to " + data.status); 384 } 385 return; 386 } 387 388 /** 389 * 390 * @param {HopObject} item 391 */ 392 Admin.prototype.renderItem = function(item) { 393 res.handlers.item = item; 394 var name = item._prototype; 395 (name === "Root") && (name = "Site"); 396 Admin.prototype.renderSkin("$Admin#" + name); 397 if (item === res.meta.item) { 398 Admin.prototype.renderSkin((req.data.action === "delete" ? 399 "$Admin#delete" : "$Admin#edit") + name); 400 } 401 return; 402 } 403 404 /** 405 * 406 * @param {HopObject} context 407 * @param {String} action 408 */ 409 Admin.prototype.log = function(context, action) { 410 var entry = new LogEntry(context, action); 411 this.entries.add(entry); 412 return; 413 } 414 415 /** 416 * 417 * @param {Object} param 418 * @param {String} action 419 * @param {Number} id 420 * @param {String} text 421 */ 422 Admin.prototype.link_macro = function(param, action, id, text) { 423 switch (action) { 424 case "edit": 425 case "delete": 426 if (req.action === "users" && (id === session.user._id)) { 427 return; 428 } 429 if (req.action === "sites" && (id === root._id)) { 430 return; 431 } 432 text = gettext(action.capitalize()); 433 action = req.action + "?action=" + action + "&id=" + id; 434 if (req.queryParams.page) { 435 action += "&page=" + req.queryParams.page; 436 } 437 action += "#" + id; 438 break; 439 default: 440 text = id; 441 } 442 return HopObject.prototype.link_macro.call(this, param, action, text); 443 } 444 445 /** 446 * 447 * @param {Object} param 448 * @param {HopObject} object 449 * @param {String} name 450 */ 451 Admin.prototype.count_macro = function(param, object, name) { 452 if (!object || !object.size) { 453 return; 454 } 455 switch (name) { 456 case "comments": 457 if (object.constructor === Site) { 458 res.write("FIXME: takes very long... :("); 459 //res.write(object.stories.comments.size()); 460 } 461 return; 462 } 463 res.write(object.size()); 464 return; 465 } 466 467 /** 468 * 469 * @param {Object} param 470 * @param {String} name 471 */ 472 Admin.prototype.skin_macro = function(param, name) { 473 if (this.getPermission("main")) { 474 return HopObject.prototype.skin_macro.apply(this, arguments); 475 } 476 return; 477 } 478 479 /** 480 * 481 * @param {Object} param 482 * @param {HopObject} object 483 * @param {String} name 484 */ 485 Admin.prototype.items_macro = function(param, object, name) { 486 if (!object || !object.size) { 487 return; 488 } 489 var max = Math.min(object.size(), parseInt(param.limit) || 5); 490 for (var i=0; i<max; i+=1) { 491 html.link({href: object.get(i).href()}, "#" + (object.size()-i) + " "); 492 } 493 return; 494 } 495 496 /** 497 * 498 * @param {Object} param 499 */ 500 Admin.prototype.dropdown_macro = function(param) { 501 if (!param.name || !param.values) { 502 return; 503 } 504 var options = param.values.split(","); 505 var selectedIndex = req.postParams[param.name]; 506 html.dropDown({name: param.name}, options, selectedIndex); 507 return; 508 } 509 510 /** 511 * 512 * @param {Object} param 513 */ 514 Admin.prototype.moduleSetup_macro = function(param) { 515 for (var i in app.modules) { 516 this.applyModuleMethod(app.modules[i], "renderSetup", param); 517 } 518 return; 519 } 520 521 /** 522 * FIXME! 523 */ 524 Admin.prototype.autoCleanUp = function() { 525 return; 526 527 if (root.sys_enableAutoCleanup) { 528 var startAtHour = root.sys_startAtHour; 529 var nextCleanup = new Date(); 530 nextCleanup.setDate(nextCleanup.getDate() + 1); 531 nextCleanup.setHours((!isNaN(startAtHour) ? startAtHour : 0), 0, 0, 0); 532 // check if it's time to run autocleanup 533 if (!app.data.nextCleanup) { 534 app.data.nextCleanup = nextCleanup; 535 this.add (new SysLog("system", null, "next cleanup scheduled for " + app.data.nextCleanup.format("EEEE, dd.MM.yyyy HH:mm"), null)); 536 } else if (new Date() >= app.data.nextCleanup) { 537 log("system", null, "starting automatic cleanup ...", null); 538 app.data.nextCleanup = nextCleanup; 539 // now start the auto-cleanup-functions 540 this.cleanupAccesslog(); 541 this.blockPrivateSites(); 542 // this.deleteInactiveSites(); 543 this.add (new SysLog("system", null, "next cleanup scheduled for " + app.data.nextCleanup.format("EEEE, dd.MM.yyyy HH:mm"), null)); 544 } 545 } 546 } 547 548 /** 549 * FIXME! 550 */ 551 Admin.prototype.blockPrivateSites = function() { 552 return; 553 554 var enable = root.sys_blockPrivateSites; 555 var blockWarningAfter = root.sys_blockWarningAfter; 556 var blockAfterWarning = root.sys_blockAfterWarning; 557 if (!enable) { 558 // blocking of private sites is disabled 559 return; 560 } else if (!blockWarningAfter || !blockAfterWarning) { 561 // something is fishy with blocking properties 562 log("system", null, "blocking of private sites cancelled", null); 563 return; 564 } 565 var size = this.privateSites.size(); 566 log("system", null, "checking " + size + " private site(s) ...", null); 567 568 // get thresholds in millis 569 warnThreshold = blockWarningAfter*1000*60*60*24; 570 blockThreshold = blockAfterWarning*1000*60*60*24; 571 572 for (var i=0;i<size;i++) { 573 var site = this.privateSites.get(i); 574 // if site is trusted, we do nothing 575 if (site.trusted) 576 continue; 577 578 var privateFor = new Date() - site.lastoffline; 579 var timeSinceWarning = new Date() - site.lastblockwarn; 580 if (privateFor >= warnThreshold) { 581 // check if site-admins have been warned already 582 var alreadyWarned = false; 583 if (site.lastblockwarn > site.lastoffline) 584 alreadyWarned = true; 585 // check whether warn admins or block site 586 if (!alreadyWarned) { 587 // admins of site haven't been warned about upcoming block, so do it now 588 var warning = new Mail; 589 var recipient = site.email ? site.email : site.creator.email; 590 warning.addTo(recipient); 591 warning.setFrom(root.sys_email); 592 warning.setSubject(gettext("Warning! Your site {0} soon will be blocked!", site.title)); 593 var sp = new Object(); 594 sp.site = site.alias; 595 sp.url = site.href(); 596 sp.privatetime = blockWarningAfter; 597 sp.daysleft = blockAfterWarning; 598 sp.contact = root.sys_email; 599 warning.addText(this.renderSkinAsString("blockwarnmail", sp)); 600 warning.send(); 601 log("site", site.alias, "site is private for more than " + 602 blockWarningAfter + " days, sent warning to " + recipient, null); 603 site.lastblockwarn = new Date(); 604 } else if (timeSinceWarning >= blockThreshold) { 605 // site is offline for too long, so block it 606 site.blocked = 1; 607 log("site", site.alias, "blocked site", null); 608 } 609 } else 610 break; 611 } 612 log("system", null, "finished checking for private sites", null); 613 return true; 614 } 615 616 /** 617 * FIXME! 618 */ 619 Admin.prototype.deleteInactiveSites = function() { 620 return; 621 622 var enable = root.sys_deleteInactiveSites; 623 var delWarningAfter = root.sys_deleteWarningAfter; 624 var delAfterWarning = root.sys_deleteAfterWarning; 625 if (!enable) { 626 // blocking of private sites is disabled 627 return; 628 } else if (!delWarningAfter || !delAfterWarning) { 629 // something is fishy with properties 630 log("system", null, "cleanup of sites cancelled", null); 631 return; 632 } 633 var size = root.size(); 634 log("system", null, "checking " + size + " sites for inactivity ...", null); 635 636 // get thresholds in millis 637 warnThreshold = delWarningAfter*1000*60*60*24; 638 delThreshold = delAfterWarning*1000*60*60*24; 639 640 for (var i=size;i>0;i--) { 641 var site = root.get(i-1); 642 // if site is trusted, we do nothing 643 if (site.trusted) 644 continue; 645 646 var idleFor = new Date() - site.modified; 647 var timeSinceWarning = new Date() - site.lastdelwarn; 648 if (idleFor >= warnThreshold) { 649 // check if site-admins have been warned already 650 var alreadyWarned = false; 651 if (site.lastdelwarn > site.modified) 652 alreadyWarned = true; 653 // check whether warn admins or block site 654 if (!alreadyWarned) { 655 // admins of site haven't been warned about upcoming block, so do it now 656 var warning = new Mail(); 657 var recipient = site.email ? site.email : site.creator.email; 658 warning.addTo(recipient); 659 warning.setFrom(root.sys_email); 660 warning.setSubject(gettext("Warning! Your site {0} soon will be deleted!", site.title)); 661 var sp = new Object(); 662 sp.site = site.alias; 663 sp.url = site.href(); 664 sp.inactivity = delWarningAfter; 665 sp.daysleft = delAfterWarning; 666 sp.contact = root.sys_email; 667 warning.addText(this.renderSkinAsString("deletewarnmail", sp)); 668 warning.send(); 669 log("site", site.alias, "site was inactive for more than " + 670 delWarningAfter + " days, sent warning to " + recipient, null); 671 site.lastdelwarn = new Date(); 672 } else if (timeSinceWarning >= blockThreshold) { 673 // site is inactive for too long, so delete it 674 root.deleteSite(site); 675 } 676 } else 677 break; 678 } 679 log("system", null, "finished checking for inactive sites", null); 680 return true; 681 } 682 683 /** 684 * FIXME! 685 */ 686 Admin.prototype.cleanupAccesslog = function() { 687 return; 688 689 var dbConn = getDBConnection("antville"); 690 var dbError = dbConn.getLastError(); 691 if (dbError) { 692 log("system", null, "failed to clean up accesslog-table!", null); 693 return; 694 } 695 var threshold = new Date(); 696 threshold.setDate(threshold.getDate() -2); 697 var query = "delete from AV_ACCESSLOG where ACCESSLOG_F_TEXT is null and ACCESSLOG_DATE < '" + threshold.format("yyyy-MM-dd HH:mm:ss") + "'"; 698 var delRows = dbConn.executeCommand(query); 699 if (delRows) { 700 log("system", null, "removed " + delRows + 701 " records from accesslog-table", null); 702 } 703 return; 704 } 705 706 /** 707 * @returns {String[]} List of 24 formatted hour strings 708 */ 709 Admin.getHours = function() { 710 var hours = []; 711 for (var i=0; i<24; i+=1) { 712 hours.push(String(i).pad("0", 2, String.LEFT) + ":00"); 713 } 714 return hours; 715 } 716