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