1936 lines
59 KiB
JavaScript
1936 lines
59 KiB
JavaScript
//
|
|
// The Antville Project
|
|
// http://code.google.com/p/antville
|
|
//
|
|
// Copyright 2001-2007 by The Antville People
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the ``License'');
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an ``AS IS'' BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
// $Revision$
|
|
// $LastChangedBy$
|
|
// $LastChangedDate$
|
|
// $URL$
|
|
//
|
|
|
|
/**
|
|
* constructor function for site objects
|
|
* @param String Title
|
|
* @param String Alias
|
|
* @param Object Creator
|
|
*/
|
|
Site.prototype.constructor = function(title, alias, creator) {
|
|
this.title = title;
|
|
this.alias = alias;
|
|
this.creator = creator;
|
|
this.createtime = this.lastoffline = new Date();
|
|
this.email = creator.email;
|
|
this.online = 0;
|
|
this.blocked = 0;
|
|
this.trusted = creator.trusted;
|
|
this.enableping = 0;
|
|
|
|
// create initial preferences
|
|
var prefs = new HopObject();
|
|
prefs.tagline = null;
|
|
prefs.discussions = 1;
|
|
prefs.usercontrib = 0;
|
|
prefs.archive = 1;
|
|
prefs.days = 3;
|
|
// retrieve locale-object from root
|
|
var loc = root.getLocale();
|
|
prefs.language = loc.getLanguage();
|
|
prefs.country = loc.getCountry();
|
|
prefs.timezone = root.getTimeZone().getID();
|
|
prefs.longdateformat = "EEEE, dd. MMMM yyyy, h:mm a";
|
|
prefs.shortdateformat = "yyyy.MM.dd, HH:mm";
|
|
this.preferences_xml = Xml.writeToString(prefs);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* main action
|
|
*/
|
|
Site.prototype.main_action = function() {
|
|
if (this.allstories.size() == 0) {
|
|
res.data.body = this.renderSkinAsString("welcome");
|
|
if (session.user) {
|
|
if (session.user == this.creator)
|
|
res.data.body += this.renderSkinAsString("welcomeowner");
|
|
if (session.user.sysadmin)
|
|
res.data.body += this.renderSkinAsString("welcomesysadmin");
|
|
}
|
|
} else {
|
|
this.renderStorylist(req.data.day);
|
|
res.data.body = this.renderSkinAsString("main");
|
|
}
|
|
res.data.title = this.title;
|
|
this.renderSkin("page");
|
|
logAccess();
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* edit action
|
|
*/
|
|
Site.prototype.edit_action = function() {
|
|
if (req.data.cancel) {
|
|
res.redirect(this.href());
|
|
} else if (req.data.rebuildIndex) {
|
|
app.data.indexManager.queueForRebuilding(this.alias);
|
|
res.message = new Message("rebuildIndex");
|
|
res.redirect(this.href(req.action));
|
|
} else if (req.data.save) {
|
|
try {
|
|
res.message = this.evalPreferences(req.data, session.user);
|
|
res.redirect(this.href(req.action));
|
|
} catch (err) {
|
|
res.message = err.toString();
|
|
}
|
|
}
|
|
|
|
res.data.action = this.href(req.action);
|
|
res.data.title = getMessage("Site.preferencesTitle", {siteTitle: this.title});
|
|
res.data.body = this.renderSkinAsString("edit");
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* delete action
|
|
*/
|
|
Site.prototype.delete_action = function() {
|
|
if (req.data.cancel)
|
|
res.redirect(this.href());
|
|
else if (req.data.remove) {
|
|
try {
|
|
res.message = root.deleteSite(this);
|
|
res.redirect(root.href());
|
|
} catch (err) {
|
|
res.message = err.toString();
|
|
}
|
|
}
|
|
|
|
res.data.action = this.href(req.action);
|
|
res.data.title = getMessage("Site.deleteTitle");
|
|
var skinParam = {
|
|
description: getMessage("Site.deleteDescription"),
|
|
detail: this.title
|
|
};
|
|
res.data.body = this.renderSkinAsString("delete", skinParam);
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* wrapper to access colorpicker also from site
|
|
*/
|
|
Site.prototype.colorpicker_action = function() {
|
|
res.handlers.site = this;
|
|
root.colorpicker_action();
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* wrapper to make style.skin public
|
|
*/
|
|
Site.prototype.main_css_action = function() {
|
|
res.dependsOn(this.modifytime);
|
|
res.dependsOn(res.handlers.layout.modifytime);
|
|
res.dependsOn(res.handlers.layout.skins.getSkinSource("Site", "style"));
|
|
res.digest();
|
|
res.contentType = "text/css";
|
|
this.renderSkin("style");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* wrapper to make javascript.skin public
|
|
*/
|
|
Site.prototype.main_js_action = function() {
|
|
res.dependsOn(this.modifytime);
|
|
res.dependsOn(res.handlers.layout.modifytime);
|
|
res.dependsOn(res.handlers.layout.skins.getSkinSource("Site", "javascript"));
|
|
res.digest();
|
|
res.contentType = "text/javascript";
|
|
this.renderSkin("javascript");
|
|
root.renderSkin("systemscripts");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* wrapper for rss feed
|
|
*/
|
|
Site.prototype.rss_xml_action = function() {
|
|
res.redirect(root.href("rss"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* rss feed
|
|
*/
|
|
Site.prototype.rss_action = function() {
|
|
res.contentType = "text/xml";
|
|
res.dependsOn(this.lastupdate);
|
|
res.digest();
|
|
|
|
var now = new Date();
|
|
var systitle = root.getTitle();
|
|
var sdf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
sdf.setTimeZone(new java.util.SimpleTimeZone(0, "UTC"));
|
|
|
|
var collection, subtitle;
|
|
switch (true) {
|
|
case (req.data.show == "all") :
|
|
collection = this.allcontent;
|
|
subtitle = "with comments";
|
|
break;
|
|
// FIXME: i don't think a day makes much sense as rss output [tobi]
|
|
case (req.data.day != null) :
|
|
collection = this.get(req.data.day);
|
|
subtitle = req.data.day;
|
|
break;
|
|
case (req.data.topic != null) :
|
|
collection = this.topics.get(req.data.topic);
|
|
subtitle = req.data.topic;
|
|
break;
|
|
default :
|
|
collection = this.allstories;
|
|
}
|
|
var size = (collection != null) ? collection.size() : 0;
|
|
|
|
var max = req.data.max ? parseInt(req.data.max) : 7;
|
|
max = Math.min(max, size);
|
|
max = Math.min(max, 10);
|
|
|
|
var item = {};
|
|
if (max > 0 && this.online) {
|
|
var items = new java.lang.StringBuffer();
|
|
var resources = new java.lang.StringBuffer();
|
|
collection.prefetchChildren(0, max);
|
|
for (var i=0; i<max; i++) {
|
|
var story = collection.get(i);
|
|
var item = {
|
|
url: story.href(),
|
|
title: story.getRenderedContentPart("title").stripTags(),
|
|
text: story.getRenderedContentPart("text").stripTags(),
|
|
publisher: systitle,
|
|
creator: story.creator.name,
|
|
date: sdf.format(story.createtime),
|
|
subject: story.topic ? story.topic : "",
|
|
year: story.createtime.getFullYear()
|
|
};
|
|
if (story.creator.publishemail)
|
|
item.email = story.creator.email.entitize();
|
|
if (!item.title) {
|
|
// shit happens: if a content part contains a markup
|
|
// element only, String.clip() will return nothing...
|
|
if (!item.text )
|
|
item.title = "...";
|
|
else {
|
|
var embody = item.text.embody(10, "...", "\\s");
|
|
item.title = embody.head;
|
|
item.text = embody.tail;
|
|
}
|
|
}
|
|
items.append(story.renderSkinAsString("rssItem", item));
|
|
resources.append(story.renderSkinAsString("rssResource", item));
|
|
}
|
|
|
|
var site = {
|
|
subtitle: subtitle,
|
|
url: this.href(),
|
|
title: systitle,
|
|
creator: this.creator.name,
|
|
year: now.getFullYear(),
|
|
lastupdate: max > 0 ? sdf.format(this.lastUpdate): sdf.format(this.createtime),
|
|
items: items.toString(),
|
|
resources: resources.toString()
|
|
};
|
|
if (this.email)
|
|
site.email = this.email.entitize();
|
|
else if (this.creator.publishemail)
|
|
site.email = this.creator.email.entitize();
|
|
this.renderSkin("rss", site);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* this action tries to get a file with the given name
|
|
* if it finds it, it increases the request-counter of this file
|
|
* sets the appropriate mimetype and redirects the browser to the file
|
|
*/
|
|
Site.prototype.getfile_action = function() {
|
|
var f = this.files.get(req.data.name);
|
|
if (f) {
|
|
f.requestcnt++;
|
|
res.contentType = f.mimetype;
|
|
res.redirect(f.getUrl());
|
|
} else {
|
|
res.message = getMessage("error.fileNotFound", req.data.name);
|
|
res.redirect(this.href());
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* most read stories of a site
|
|
*/
|
|
Site.prototype.mostread_action = function() {
|
|
res.data.title = getMessage("Site.mostReadTitle", {siteTitle: this.title});
|
|
res.data.body = this.renderSkinAsString("mostread");
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* referrers of a site
|
|
*/
|
|
Site.prototype.referrers_action = function() {
|
|
if (req.data.permanent && session.user) {
|
|
try {
|
|
this.checkEdit(session.user, req.data.memberlevel);
|
|
} catch (err) {
|
|
res.message = err.toString();
|
|
res.redirect(this.href());
|
|
return;
|
|
}
|
|
var urls = req.data.permanent_array ?
|
|
req.data.permanent_array : [req.data.permanent];
|
|
res.push();
|
|
res.write(this.preferences.getProperty("spamfilter"));
|
|
for (var i in urls) {
|
|
res.write("\n");
|
|
res.write(urls[i].replace(/\?/g, "\\\\?"));
|
|
}
|
|
this.preferences.setProperty("spamfilter", res.pop());
|
|
res.redirect(this.href(req.action));
|
|
return;
|
|
}
|
|
res.data.action = this.href("referrers");
|
|
res.data.title = getMessage("Site.referrersReadTitle", {siteTitle: this.title});
|
|
res.data.body = this.renderSkinAsString("referrers");
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* search action
|
|
*/
|
|
Site.prototype.search_action = function() {
|
|
var self = this;
|
|
|
|
/**
|
|
* private method for rendering the results
|
|
*/
|
|
var renderResult = function(hits, itemsPerPage, pageIdx) {
|
|
var currIdx = 0;
|
|
var size = hits.length();
|
|
var validCnt = 0;
|
|
|
|
var totalPages = Math.ceil(size/itemsPerPage);
|
|
if (isNaN(pageIdx) || pageIdx > totalPages || pageIdx < 0)
|
|
pageIdx = 0;
|
|
var start = (pageIdx * itemsPerPage);
|
|
stop = Math.min(start + itemsPerPage, size);
|
|
res.push();
|
|
while (currIdx < size && validCnt < stop) {
|
|
var item = Story.getById(hits.doc(currIdx).get("id"));
|
|
if (item) {
|
|
var status = (item instanceof Comment) ? item.story.online : item.online;
|
|
if (status > 0) {
|
|
if (validCnt >= start) {
|
|
item.renderSkin("searchview", {score: Math.round(hits.score(currIdx) * 100)});
|
|
}
|
|
validCnt++;
|
|
} else {
|
|
// "correct" the number of hits since
|
|
// the story/comment is offline
|
|
total--;
|
|
}
|
|
}
|
|
currIdx++;
|
|
}
|
|
return res.pop();
|
|
}
|
|
|
|
/**
|
|
* main action body
|
|
*/
|
|
if (req.isGet() && req.data.q != null) {
|
|
var query = stripTags(req.data.q);
|
|
var queryArr = ["q=" + query];
|
|
|
|
// construct the filter query
|
|
var fq = new Search.BooleanQuery();
|
|
fq.addQuery(new Search.RangeQuery("online", 1, 2));
|
|
// filter by creator
|
|
if (req.data.c) {
|
|
fq.addTerm("creator", req.data.c);
|
|
queryArr.push("c=" + req.data.c);
|
|
}
|
|
// filter by createtime
|
|
if (req.data.ct) {
|
|
var then = new Date();
|
|
var min = new Date(then.setMonth(then.getMonth() - parseInt(req.data.ct, 10)));
|
|
min = min.format("yyyyMMdd", this.getLocale(), this.getTimeZone());
|
|
fq.addQuery(new Search.RangeQuery("createtime", min));
|
|
queryArr.push("ct=" + req.data.ct);
|
|
}
|
|
var filter = new Search.QueryFilter(fq);
|
|
|
|
// construct the query itself
|
|
var q = new Search.BooleanQuery();
|
|
q.addTerm("site", this._id);
|
|
// occurrence
|
|
queryArr.push("o=" + req.data.o);
|
|
switch (req.data.o) {
|
|
case "topic":
|
|
q.addTerm("topic", query);
|
|
break;
|
|
case "title":
|
|
q.addTerm("title", query);
|
|
break;
|
|
case "text":
|
|
q.addTerm("text", query);
|
|
break;
|
|
default:
|
|
q.addTerm(["title", "text", "topic"], query);
|
|
break;
|
|
}
|
|
|
|
var index = this.getIndex();
|
|
if (!index) {
|
|
res.message = getMessage("error.searchNothingFound", encodeForm(query));
|
|
} else {
|
|
var now = new Date();
|
|
var searcher = new index.Searcher();
|
|
var total = searcher.search(q, filter);
|
|
res.data.resultlist = renderResult(searcher.hits, 10, req.data.page);
|
|
switch(total) {
|
|
case 0:
|
|
res.message = getMessage("error.searchNothingFound", encodeForm(query));
|
|
break;
|
|
case 1:
|
|
res.message = getMessage("confirm.resultOne", encodeForm(query));
|
|
break;
|
|
default:
|
|
res.message = getMessage("confirm.resultMany", [encodeForm(query), total]);
|
|
break;
|
|
}
|
|
res.data.pagenavigation = renderPageNavigation(total, this.href(req.action) + "?" + queryArr.join("&"), 10, req.data.page);
|
|
searcher.close();
|
|
}
|
|
app.debug("[" + this.alias + "] search for " + q.toString() +
|
|
", filter: " + filter.toString() +
|
|
" (" + (new Date()).diff(now) + "ms)");
|
|
}
|
|
|
|
res.data.action = this.href(req.action);
|
|
res.data.title = this.title;
|
|
res.data.body = this.renderSkinAsString("searchresult");
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* subscribe action
|
|
*/
|
|
Site.prototype.subscribe_action = function() {
|
|
// create a new member-object and add it to membership-mountpoint
|
|
this.members.add(new Membership(session.user));
|
|
res.message = new Message("subscriptionCreate", this.title);
|
|
res.redirect(this.href());
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* unsubscribe action
|
|
*/
|
|
Site.prototype.unsubscribe_action = function() {
|
|
if (req.data.cancel)
|
|
res.redirect(this.members.href("subscriptions"));
|
|
else if (req.data.remove) {
|
|
try {
|
|
res.message = this.members.deleteMembership(this.members.get(session.user.name), session.user);
|
|
res.redirect(this.members.href("subscriptions"));
|
|
} catch (err) {
|
|
res.message = err.toString();
|
|
}
|
|
}
|
|
|
|
res.data.title = getMessage("Site.subscription.deleteTitle", {siteTitle: this.title});
|
|
var skinParam = {
|
|
description: getMessage("Site.subscription.deleteDescription"),
|
|
details: this.title
|
|
};
|
|
res.data.body = this.renderSkinAsString("delete", skinParam);
|
|
this.renderSkin("page");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* context menu extension
|
|
*/
|
|
Site.prototype.menuext_action = function() {
|
|
this.renderSkin("menuext");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* context menu extension (accessible as /menuext.reg)
|
|
*/
|
|
Site.prototype.menuext_reg_action = function() {
|
|
res.contentType = "text/plain";
|
|
this.renderSkin("menuext.reg");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* robots.txt action
|
|
*/
|
|
Site.prototype.robots_txt_action = function() {
|
|
res.contentType = "text/plain";
|
|
this.renderSkin("robots");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering title
|
|
*/
|
|
Site.prototype.title_macro = function(param) {
|
|
if (param.as == "editor")
|
|
Html.input(this.createInputParam("title", param));
|
|
else {
|
|
if (param && param.linkto) {
|
|
if (param.linkto == "main")
|
|
param.linkto = "";
|
|
Html.openLink({href: this.href(param.linkto)});
|
|
if (this.title && this.title.trim())
|
|
res.write(stripTags(this.title));
|
|
else
|
|
res.write("<em>[untitled]</em>");
|
|
Html.closeLink();
|
|
} else
|
|
res.write(this.title);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering alias
|
|
*/
|
|
Site.prototype.alias_macro = function(param) {
|
|
if (param.as == "editor")
|
|
Html.input(this.createInputParam("alias", param));
|
|
else
|
|
res.write(this.alias);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering tagline
|
|
*/
|
|
Site.prototype.tagline_macro = function(param) {
|
|
if (param.as == "editor")
|
|
Html.input(this.preferences.createInputParam("tagline", param));
|
|
else if (this.preferences.getProperty("tagline"))
|
|
res.write(stripTags(this.preferences.getProperty("tagline")));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering email
|
|
*/
|
|
Site.prototype.email_macro = function(param) {
|
|
if (param.as == "editor")
|
|
Html.input(this.createInputParam("email", param));
|
|
else
|
|
res.write(this.email);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering lastupdate
|
|
*/
|
|
Site.prototype.lastupdate_macro = function(param) {
|
|
if (this.lastupdate)
|
|
res.write(formatTimestamp(this.lastupdate, param.format));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering online-status
|
|
*/
|
|
Site.prototype.online_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.createCheckBoxParam("online", param);
|
|
if (req.data.save && !req.data.online)
|
|
delete inputParam.checked;
|
|
Html.checkBox(inputParam);
|
|
} else if (this.online)
|
|
res.write(param.yes ? param.yes : getMessage("generic.yes"));
|
|
else
|
|
res.write(param.no ? param.no : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering discussion-flag
|
|
*/
|
|
Site.prototype.hasdiscussions_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.preferences.createCheckBoxParam("discussions", param);
|
|
if (req.data.save && !req.data.preferences_discussions)
|
|
delete inputParam.checked;
|
|
Html.checkBox(inputParam);
|
|
} else
|
|
res.write(this.preferences.getProperty("discussions") ?
|
|
getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering usercontrib-flag
|
|
*/
|
|
Site.prototype.usermaycontrib_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.preferences.createCheckBoxParam("usercontrib", param);
|
|
if (req.data.save && !req.data.preferences_usercontrib)
|
|
delete inputParam.checked;
|
|
Html.checkBox(inputParam);
|
|
} else
|
|
res.write(this.preferences.getProperty("usercontrib") ?
|
|
getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering nr. of days to show on site-fontpage
|
|
*/
|
|
Site.prototype.showdays_macro = function(param) {
|
|
if (param.as == "editor")
|
|
Html.input(this.preferences.createInputParam("days", param));
|
|
else
|
|
res.write(this.preferences.getProperty("days"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering archive-flag
|
|
*/
|
|
Site.prototype.showarchive_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.preferences.createCheckBoxParam("archive", param);
|
|
if (req.data.save && !req.data.preferences_archive)
|
|
delete inputParam.checked;
|
|
Html.checkBox(inputParam);
|
|
} else
|
|
res.write(this.preferences.getProperty("archive") ?
|
|
getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering enableping-flag
|
|
*/
|
|
Site.prototype.enableping_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.createCheckBoxParam("enableping", param);
|
|
if (req.data.save && !req.data.enableping)
|
|
delete inputParam.checked;
|
|
Html.checkBox(inputParam);
|
|
} else
|
|
res.write(this.enableping ? getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering default longdateformat
|
|
*/
|
|
Site.prototype.longdateformat_macro = function(param) {
|
|
if (param.as == "chooser")
|
|
renderDateformatChooser("longdateformat", this.getLocale(),
|
|
this.preferences.getProperty("longdateformat"));
|
|
else if (param.as == "editor")
|
|
Html.input(this.preferences.createInputParam("longdateformat", param));
|
|
else
|
|
res.write(this.preferences.getProperty("longdateformat"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering default shortdateformat
|
|
*/
|
|
Site.prototype.shortdateformat_macro = function(param) {
|
|
if (param.as == "chooser")
|
|
renderDateformatChooser("shortdateformat", this.getLocale(),
|
|
this.preferences.getProperty("shortdateformat"));
|
|
else if (param.as == "editor")
|
|
Html.input(this.preferences.createInputParam("shortdateformat", param));
|
|
else
|
|
res.write(this.preferences.getProperty("shortdateformat"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering loginStatus of user
|
|
* valid params: - loginSkin
|
|
* - logoutSkin
|
|
*/
|
|
Site.prototype.loginstatus_macro = function(param) {
|
|
if (session.user)
|
|
this.members.renderSkin("statusloggedin");
|
|
else if (req.action != "login")
|
|
this.members.renderSkin("statusloggedout");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering two different navigation-skins
|
|
* depending on user-status & rights
|
|
*/
|
|
Site.prototype.navigation_macro = function(param) {
|
|
if (param["for"] == "users" && !param.modules) {
|
|
// FIXME: this is left for backwards-compatibility
|
|
// sometime in the future we'll get rid of the usernavigation.skin
|
|
res.write("... ");
|
|
Html.link({href: "http://project.antville.org/project/stories/146"}, "<strong>README</strong>");
|
|
Html.tag("br");
|
|
Html.tag("br");
|
|
this.renderSkin("usernavigation");
|
|
}
|
|
if (!session.user)
|
|
return;
|
|
switch (param["for"]) {
|
|
case "contributors" :
|
|
if (session.user.sysadmin ||
|
|
this.preferences.getProperty("usercontrib") ||
|
|
req.data.memberlevel >= CONTRIBUTOR)
|
|
this.renderSkin("contribnavigation");
|
|
break;
|
|
case "admins" :
|
|
if (session.user.sysadmin || req.data.memberlevel >= ADMIN)
|
|
this.renderSkin("adminnavigation");
|
|
break;
|
|
}
|
|
if (param.modules != null) {
|
|
var mods = param.modules.split(",");
|
|
if (mods.length == 1 && mods[0] == "all") {
|
|
for (var i in app.modules)
|
|
this.applyModuleMethod(app.modules[i], "renderSiteNavigation", param);
|
|
} else {
|
|
for (var i in mods)
|
|
this.applyModuleMethod(app.modules[mods[i]], "renderSiteNavigation", param);
|
|
}
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* call the site navigation render method
|
|
* of a module
|
|
*/
|
|
Site.prototype.moduleNavigation_macro = function(param) {
|
|
if (!param.module)
|
|
return;
|
|
this.applyModuleMethod(app.modules[param.module],
|
|
"renderSiteNavigation", param);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders a calendar
|
|
* version 2
|
|
*/
|
|
Site.prototype.calendar_macro = function(param) {
|
|
// do nothing if there is not a single story :-))
|
|
// or if archive of this site is disabled
|
|
if (!this.allstories.size() || !this.preferences.getProperty("archive"))
|
|
return;
|
|
// define variables needed in this function
|
|
var calParam = new Object();
|
|
var dayParam = new Object();
|
|
var weekParam = new Object();
|
|
res.push();
|
|
|
|
// create new calendar-object
|
|
var cal = java.util.Calendar.getInstance(this.getTimeZone(), this.getLocale());
|
|
var symbols = this.getDateSymbols();
|
|
|
|
// render header-row of calendar
|
|
var firstDayOfWeek = cal.getFirstDayOfWeek();
|
|
var weekdays = symbols.getShortWeekdays();
|
|
res.push();
|
|
for (var i=0;i<7;i++) {
|
|
dayParam.day = weekdays[(i+firstDayOfWeek-1)%7+1];
|
|
this.renderSkin("calendardayheader", dayParam);
|
|
}
|
|
weekParam.week = res.pop();
|
|
this.renderSkin("calendarweek", weekParam);
|
|
|
|
cal.set(java.util.Calendar.DATE, 1);
|
|
// check whether there's a day or a story in path
|
|
// if so, use it to determine the month to render
|
|
if (path.Story)
|
|
var today = path.Story.day.toString();
|
|
else if (path.Day && path.Day._prototype == "Day")
|
|
var today = path.Day.groupname.toString();
|
|
if (today) {
|
|
// instead of using String.toDate
|
|
// we do it manually here to avoid that a day like 20021001
|
|
// would be changed to 20020930 in some cases
|
|
cal.set(java.util.Calendar.YEAR, parseInt(today.substring(0, 4), 10));
|
|
cal.set(java.util.Calendar.MONTH, parseInt(today.substring(4, 6), 10)-1);
|
|
}
|
|
// nr. of empty days in rendered calendar before the first day of month appears
|
|
var pre = (7-firstDayOfWeek+cal.get(java.util.Calendar.DAY_OF_WEEK)) % 7;
|
|
var days = cal.getActualMaximum(java.util.Calendar.DATE);
|
|
var weeks = Math.ceil((pre + days) / 7);
|
|
var daycnt = 1;
|
|
|
|
var monthNames = symbols.getMonths();
|
|
calParam.month = monthNames[cal.get(java.util.Calendar.MONTH)];
|
|
calParam.year = cal.get(java.util.Calendar.YEAR);
|
|
|
|
// pre-render the year and month so we only have to append the days as we loop
|
|
var currMonth = formatTimestamp(new Date(cal.getTime().getTime()), "yyyyMM");
|
|
// remember the index of the first and last days within this month.
|
|
// this is needed to optimize previous and next month links.
|
|
var lastDayIndex = 9999999;
|
|
var firstDayIndex = -1;
|
|
|
|
for (var i=0;i<weeks;i++) {
|
|
res.push();
|
|
for (var j=0;j<7;j++) {
|
|
dayParam.skin = "calendarday";
|
|
if ((i == 0 && j < pre) || daycnt > days)
|
|
dayParam.day = " ";
|
|
else {
|
|
var currGroupname = currMonth+daycnt.format("00");
|
|
var linkText = daycnt < 10 ? " " + daycnt + " " : daycnt.toString();
|
|
var currGroup = this.get(currGroupname);
|
|
if (currGroup) {
|
|
var idx = this.contains(currGroup);
|
|
if (idx > -1) {
|
|
if (idx > firstDayIndex)
|
|
firstDayIndex = idx;
|
|
if (idx < lastDayIndex)
|
|
lastDayIndex = idx;
|
|
}
|
|
dayParam.day = Html.linkAsString({href: currGroup.href()}, linkText);
|
|
} else {
|
|
dayParam.day = linkText;
|
|
}
|
|
if (currGroupname == today)
|
|
dayParam.skin = "calendarselday";
|
|
daycnt++;
|
|
}
|
|
this.renderSkin(dayParam.skin, dayParam);
|
|
}
|
|
weekParam.week = res.pop();
|
|
this.renderSkin("calendarweek", weekParam);
|
|
}
|
|
// set day to last day of month and try to render next month
|
|
// check what the last day of the month is
|
|
calParam.back = this.renderLinkToPrevMonth(firstDayIndex, currMonth + "01", monthNames);
|
|
calParam.forward = this.renderLinkToNextMonth(lastDayIndex, currMonth + "31", monthNames);
|
|
calParam.calendar = res.pop();
|
|
this.renderSkin("calendar", calParam);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders age
|
|
*/
|
|
Site.prototype.age_macro = function(param) {
|
|
res.write(Math.floor((new Date() - this.createtime) / ONEDAY));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders a list of recently added/updated stories/comments
|
|
* of this site
|
|
*/
|
|
Site.prototype.history_macro = function(param) {
|
|
try {
|
|
this.checkView(session.user, req.data.memberlevel);
|
|
} catch (deny) {
|
|
return;
|
|
}
|
|
var limit = param.limit ? parseInt(param.limit, 10) : 5;
|
|
var cnt = i = 0;
|
|
var size = this.lastmod.size();
|
|
var discussions = this.preferences.getProperty("discussions");
|
|
while (cnt < limit && i < size) {
|
|
if (i % limit == 0)
|
|
this.lastmod.prefetchChildren(i, limit);
|
|
var item = this.lastmod.get(i++);
|
|
switch (item._prototype) {
|
|
case "Story":
|
|
if (param.show == "comments")
|
|
continue;
|
|
break;
|
|
case "Comment":
|
|
if (param.show == "stories" || !item.story.online ||
|
|
!item.story.discussions || !discussions)
|
|
continue;
|
|
break;
|
|
}
|
|
item.renderSkin("historyview");
|
|
cnt++;
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders a list of available locales as dropdown
|
|
*/
|
|
Site.prototype.localechooser_macro = function(param) {
|
|
renderLocaleChooser(this.getLocale());
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders a list of available time zones as dropdown
|
|
*/
|
|
Site.prototype.timezonechooser_macro = function(param) {
|
|
renderTimeZoneChooser(this.getTimeZone());
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* renders a list of most read pages, ie. a link
|
|
* to a story together with the read counter et al.
|
|
*/
|
|
Site.prototype.listMostRead_macro = function() {
|
|
var param = new Object();
|
|
var size = this.mostread.size();
|
|
for (var i=0; i<size; i++) {
|
|
var s = this.mostread.get(i);
|
|
param.reads = s.reads;
|
|
param.rank = i+1;
|
|
s.renderSkin("mostread", param);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* renders a list of referrers, ie. a link
|
|
* to a url together with the read counter et al.
|
|
*/
|
|
Site.prototype.listReferrers_macro = function() {
|
|
var c = getDBConnection("antville");
|
|
var dbError = c.getLastError();
|
|
if (dbError)
|
|
return getMessage("error.database", dbError);
|
|
// we're doing this with direct db access here
|
|
// (there's no need to do it with prototypes):
|
|
var d = new Date();
|
|
d.setDate(d.getDate()-1); // 24 hours ago
|
|
var query = "select ACCESSLOG_REFERRER, count(*) as \"COUNT\" from AV_ACCESSLOG " +
|
|
"where ACCESSLOG_F_SITE = " + this._id + " and ACCESSLOG_DATE > {ts '" +
|
|
d.format("yyyy-MM-dd HH:mm:ss") + "'} group by ACCESSLOG_REFERRER "+
|
|
"order by \"COUNT\" desc, ACCESSLOG_REFERRER asc;";
|
|
var rows = c.executeRetrieval(query);
|
|
var dbError = c.getLastError();
|
|
if (dbError)
|
|
return getMessage("error.database", dbError);
|
|
var skinParam = new Object();
|
|
var referrer;
|
|
while (rows.next()) {
|
|
skinParam.count = rows.getColumnItem("COUNT");
|
|
referrer = rows.getColumnItem("ACCESSLOG_REFERRER");
|
|
skinParam.referrer = encode(referrer);
|
|
skinParam.text = encode(referrer.length > 50 ? referrer.substring(0, 50) + "..." : referrer);
|
|
this.renderSkin("referrerItem", skinParam);
|
|
}
|
|
rows.release();
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* renders the xml button for use
|
|
* when referring to an rss feed
|
|
*/
|
|
Site.prototype.xmlbutton_macro = function(param) {
|
|
param.linkto = this.href("rss");
|
|
DefaultImages.render("xmlbutton", param);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* renders the searchbox
|
|
*/
|
|
Site.prototype.searchbox_macro = function(param) {
|
|
this.renderSkin("searchbox");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function renders the months of the archive
|
|
*/
|
|
Site.prototype.monthlist_macro = function(param) {
|
|
if (!this.stories.size() || !this.preferences.getProperty("archive"))
|
|
return;
|
|
var size = param.limit ? Math.min(this.size(), param.limit) : this.size();
|
|
for (var i=0;i<size;i++) {
|
|
var curr = this.get(i);
|
|
var next = this.get(i+1);
|
|
if (!next || next.groupname.substring(0, 6) < curr.groupname.substring(0, 6)) {
|
|
res.write(param.itemprefix);
|
|
Html.openLink({href: curr.href()});
|
|
var ts = curr.groupname.substring(0, 6).toDate("yyyyMM", this.getTimeZone());
|
|
res.write(formatTimestamp(ts, param.format ? param.format : "MMMM yyyy"));
|
|
Html.closeLink();
|
|
res.write(param.itemsuffix);
|
|
}
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* proxy-macro for layout chooser
|
|
*/
|
|
Site.prototype.layoutchooser_macro = function(param) {
|
|
if (this.layout)
|
|
param.selected = this.layout.alias;
|
|
this.layouts.layoutchooser_macro(param);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering recipients for email notification
|
|
* param.event: storycreate/commentcreate/textupdate/upload
|
|
* please add some error message for undefined param.event
|
|
*/
|
|
Site.prototype.notify_macro = function(param) {
|
|
var notifyContributors = param.notifyContributors ?
|
|
param.notifyContributors : getMessage("Site.notifyContributors");
|
|
var notifyAdmins = param.notifyAdmins ?
|
|
param.notifyAdmins : getMessage("Site.notifyAdmins");
|
|
var notifyNobody = param.notifyNobody ?
|
|
param.notifyNobody : getMessage("Site.notifyNobody");
|
|
|
|
var pref = this.preferences.getProperty("notify_" + param.event);
|
|
if (param.as == "editor") {
|
|
var options = new Array(notifyNobody, notifyAdmins, notifyContributors);
|
|
Html.dropDown({name: "notify_" + param.event}, options, pref);
|
|
} else {
|
|
switch (pref) {
|
|
case 2:
|
|
return notifyContributors;
|
|
case 1:
|
|
return notifyAdmins;
|
|
default:
|
|
return notifyNobody;
|
|
}
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro rendering notification settings if enabled
|
|
*/
|
|
Site.prototype.notification_macro = function(param) {
|
|
if (this.isNotificationEnabled())
|
|
this.renderSkin("notification");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* render generic preference editor or value
|
|
*/
|
|
Site.prototype.preferences_macro = function(param) {
|
|
if (param.as == "editor") {
|
|
var inputParam = this.preferences.createInputParam(param.name, param);
|
|
delete inputParam.part;
|
|
if (param.cols || param.rows)
|
|
Html.textArea(inputParam);
|
|
else
|
|
Html.input(inputParam);
|
|
} else
|
|
res.write(this.preferences.getProperty(param.name));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* output spamfilter data appropriate
|
|
* for client-side javascript code
|
|
*/
|
|
Site.prototype.spamfilter_macro = function(param) {
|
|
var str = this.preferences.getProperty("spamfilter");
|
|
if (!str)
|
|
return;
|
|
var items = str.replace(/\r/g, "").split("\n");
|
|
for (var i in items) {
|
|
res.write('"');
|
|
res.write(items[i]);
|
|
res.write('"');
|
|
if (i < items.length-1)
|
|
res.write(",");
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro returns the used disk space for this site
|
|
*/
|
|
Site.prototype.diskusage_macro = function(param) {
|
|
res.write(this.getDiskUsage().format("###,###") + " KB");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro checks if there are any modules present
|
|
* and if they need to be included in the system setup page
|
|
*/
|
|
Site.prototype.modulePreferences_macro = function(param) {
|
|
for (var i in app.modules)
|
|
this.applyModuleMethod(app.modules[i], "renderPreferences", param);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* catch some special needs before passing the
|
|
* macro call up to the HopObject prototype
|
|
* FIXME: this is probably to hackish...
|
|
*/
|
|
Site.prototype.switch_macro = function(param) {
|
|
if (param.name == "userMayEdit") {
|
|
try {
|
|
// FIXME: unfortunately, the check* methods are
|
|
// not very handy, anymore... (need try/catch block)
|
|
this.checkEdit(session.user, req.data.memberlevel);
|
|
res.write(param.on);
|
|
} catch (err) {
|
|
res.write(param.off);
|
|
return;
|
|
}
|
|
} else
|
|
HopObject.switch_macro.apply(this, [param]);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* returns the number of members of this site
|
|
*/
|
|
Site.prototype.membercounter_macro = function(param) {
|
|
return this.members.size();
|
|
};
|
|
|
|
/**
|
|
* renders a dropdown containing the possible occurrences
|
|
* of search terms in a content object
|
|
*/
|
|
Site.prototype.searchOccurrence_macro = function() {
|
|
var options = [["", "anywhere"],
|
|
["title", "in the title"],
|
|
["text", "in the text"],
|
|
["topic", "in the topic name"]];
|
|
Html.dropDown({name: "o"}, options, req.data.o);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* renders a dropdown containing some reasonable
|
|
* timespans for searching
|
|
*/
|
|
Site.prototype.searchCreatetime_macro = function() {
|
|
var options = [["", "anytime"],
|
|
["1", "the past month"],
|
|
["2", "the past 2 months"],
|
|
["4", "the past 4 months"],
|
|
["6", "the past half year"],
|
|
["12", "the past year"]];
|
|
Html.dropDown({name: "ct"}, options, req.data.ct);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro counts
|
|
*/
|
|
Site.prototype.sysmgr_count_macro = function(param) {
|
|
if (!param || !param.what)
|
|
return;
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin)
|
|
return;
|
|
switch (param.what) {
|
|
case "stories" :
|
|
return this.allstories.size();
|
|
case "comments" :
|
|
return this.allcontent.size() - this.allstories.size();
|
|
case "images" :
|
|
return this.images.size();
|
|
case "files" :
|
|
return this.files.size();
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function renders the statusflags for this site
|
|
*/
|
|
Site.prototype.sysmgr_statusflags_macro = function(param) {
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin)
|
|
return;
|
|
if (this.trusted)
|
|
res.write("<span class=\"flagDark\" style=\"background-color:#009900;\">TRUSTED</span>");
|
|
if (!this.online)
|
|
res.write("<span class=\"flagDark\" style=\"background-color:#CC0000;\">PRIVATE</span>");
|
|
else
|
|
res.write("<span class=\"flagDark\" style=\"background-color:#006600;\">PUBLIC</span>");
|
|
if (this.blocked)
|
|
res.write("<span class=\"flagDark\" style=\"background-color:#000000;\">BLOCKED</span>");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function renders an edit-link
|
|
*/
|
|
Site.prototype.sysmgr_editlink_macro = function(param) {
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin || req.data.item == this.alias)
|
|
return;
|
|
param.linkto = "sites";
|
|
param.urlparam = "item=" + this.alias + "&action=edit";
|
|
if (req.data.page)
|
|
param.urlparam += "&page=" + req.data.page;
|
|
param.anchor = this.alias;
|
|
Html.openTag("a", root.manage.createLinkParam(param));
|
|
res.write(param.text ? param.text : "edit");
|
|
Html.closeTag("a");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function renders a delete-link
|
|
*/
|
|
Site.prototype.sysmgr_deletelink_macro = function(param) {
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin || req.data.item == this.alias)
|
|
return;
|
|
param.linkto = "sites";
|
|
param.urlparam = "item=" + this.alias + "&action=remove";
|
|
if (req.data.page)
|
|
param.urlparam += "&page=" + req.data.page;
|
|
param.anchor = this.alias;
|
|
Html.openTag("a", root.manage.createLinkParam(param));
|
|
res.write(param.text ? param.text : "delete");
|
|
Html.closeTag("a");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders the trust-state of this site
|
|
*/
|
|
Site.prototype.sysmgr_trusted_macro = function(param) {
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin)
|
|
return;
|
|
if (param.as == "editor") {
|
|
var options = [getMessage("generic.no"), getMessage("generic.yes")];
|
|
Html.dropDown({name: "trusted"}, options, this.trusted);
|
|
} else
|
|
res.write(this.trusted ? getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* macro renders the block-state of this site
|
|
*/
|
|
Site.prototype.sysmgr_blocked_macro = function(param) {
|
|
// this macro is allowed just for sysadmins
|
|
if (!session.user.sysadmin)
|
|
return;
|
|
if (param.as == "editor") {
|
|
var options = [getMessage("generic.no"), getMessage("generic.yes")];
|
|
Html.dropDown({name: "blocked"}, options, this.blocked);
|
|
} else
|
|
res.write(this.blocked ? getMessage("generic.yes") : getMessage("generic.no"));
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function saves new properties of site
|
|
* @param Obj Object containing the form values
|
|
* @param Obj User-Object modifying this site
|
|
* @throws Exception
|
|
*/
|
|
Site.prototype.evalPreferences = function(param, modifier) {
|
|
if (!evalEmail(param.email))
|
|
throw new Exception("emailInvalid");
|
|
this.title = stripTags(param.title);
|
|
this.email = param.email;
|
|
if (this.online && !param.online)
|
|
this.lastoffline = new Date();
|
|
this.online = param.online ? 1 : 0;
|
|
this.enableping = param.enableping ? 1 : 0;
|
|
|
|
// store new preferences
|
|
var prefs = new HopObject();
|
|
for (var i in param) {
|
|
if (i.startsWith("preferences_"))
|
|
prefs[i.substring(12)] = param[i];
|
|
}
|
|
prefs.days = !isNaN(parseInt(param.preferences_days, 10)) ? parseInt(param.preferences_days, 10) : 3;
|
|
prefs.discussions = param.preferences_discussions ? 1 : 0;
|
|
prefs.usercontrib = param.preferences_usercontrib ? 1 : 0;
|
|
prefs.archive = param.preferences_archive ? 1 : 0;
|
|
// store selected locale
|
|
if (param.locale) {
|
|
var loc = param.locale.split("_");
|
|
prefs.language = loc[0];
|
|
prefs.country = loc.length == 2 ? loc[1] : null;
|
|
}
|
|
prefs.timezone = param.timezone;
|
|
prefs.longdateformat = param.longdateformat;
|
|
prefs.shortdateformat = param.shortdateformat;
|
|
|
|
// layout
|
|
this.layout = param.layout ? this.layouts.get(param.layout) : null;
|
|
|
|
// e-mail notification
|
|
prefs.notify_create = parseInt(param.notify_create, 10) || null;
|
|
prefs.notify_update = parseInt(param.notify_update, 10) || null;
|
|
prefs.notify_upload = parseInt(param.notify_upload, 10) || null;
|
|
|
|
// store preferences
|
|
this.preferences.setAll(prefs);
|
|
// call the evalPreferences method of every module
|
|
for (var i in app.modules)
|
|
this.applyModuleMethod(app.modules[i], "evalPreferences", param);
|
|
|
|
|
|
// reset cached locale, timezone and dateSymbols
|
|
this.cache.locale = null;
|
|
this.cache.timezone = null;
|
|
this.cache.dateSymbols = null;
|
|
|
|
this.modifytime = new Date();
|
|
this.modifier = modifier;
|
|
return new Message("update");
|
|
};
|
|
|
|
/**
|
|
* function checks if language and country were specified
|
|
* for this site. if so, it returns the specified Locale-object
|
|
* otherwise it calls getLocale() for root
|
|
*/
|
|
Site.prototype.getLocale = function() {
|
|
var locale = this.cache.locale;
|
|
if (locale)
|
|
return locale;
|
|
if (this.preferences.getProperty("language")) {
|
|
if (this.preferences.getProperty("country"))
|
|
locale = new java.util.Locale(this.preferences.getProperty("language"),
|
|
this.preferences.getProperty("country"));
|
|
else
|
|
locale = new java.util.Locale(this.preferences.getProperty("language"));
|
|
} else
|
|
locale = root.getLocale();
|
|
this.cache.locale =locale;
|
|
return locale;
|
|
};
|
|
|
|
/**
|
|
* function returns the (already cached) DateFormatSymbols according
|
|
* to the locale defined for a site
|
|
*/
|
|
Site.prototype.getDateSymbols = function() {
|
|
var symbols = this.cache.dateSymbols;
|
|
if (symbols)
|
|
return symbols;
|
|
this.cache.dateSymbols = new java.text.DateFormatSymbols(this.getLocale());
|
|
return this.cache.dateSymbols;
|
|
};
|
|
|
|
/**
|
|
* function returns the (already cached) TimeZone-Object
|
|
* according to site-preferences
|
|
*/
|
|
Site.prototype.getTimeZone = function() {
|
|
var tz = this.cache.timezone;
|
|
if (tz)
|
|
return tz;
|
|
if (this.preferences.getProperty("timezone"))
|
|
tz = java.util.TimeZone.getTimeZone(this.preferences.getProperty("timezone"));
|
|
else
|
|
tz = root.getTimeZone();
|
|
this.cache.timezone = tz;
|
|
return tz;
|
|
};
|
|
|
|
/**
|
|
* function deletes all assets of a site (recursive!)
|
|
*/
|
|
Site.prototype.deleteAll = function() {
|
|
this.images.deleteAll();
|
|
this.files.deleteAll();
|
|
// FIXME: add deleting of all layouts!
|
|
// this.layouts.deleteAll();
|
|
this.stories.deleteAll();
|
|
this.members.deleteAll();
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* send notification to weblogs.com
|
|
* that this site was updated
|
|
* @return Object with properties error and message
|
|
*/
|
|
Site.prototype.ping = function() {
|
|
var title = this.title ? this.title : this.alias;
|
|
|
|
// we're doing it the xml-rpc way
|
|
// (specs at http://newhome.weblogs.com/directory/11)
|
|
var xr = new Remote("http://rpc.weblogs.com/RPC2");
|
|
var ping = xr.weblogUpdates.ping(title, this.href());
|
|
if (!ping.result)
|
|
return;
|
|
var result = new Object();
|
|
result.error = ping.result.flerror;
|
|
result.message = ping.result.message;
|
|
|
|
if (result.error)
|
|
app.log("Error when notifying weblogs.com for updated site \"" + this.alias + "\": " + result.message);
|
|
|
|
// lastping is always set to now to prevent blogs
|
|
// hanging in the scheduler if a fatal error occurs
|
|
this.lastping = new Date();
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* href URL postprocessor. If a virtual host mapping is defined
|
|
* for this site's alias, use it. Otherwise, use normal site URL.
|
|
*/
|
|
Site.prototype.processHref = function(href) {
|
|
var vhost = app.properties["vhost." + this.alias];
|
|
if (vhost)
|
|
return vhost + href;
|
|
else
|
|
return app.properties.defaulthost + "/" + this.alias + href;
|
|
};
|
|
|
|
/**
|
|
* basic check if email notification is enabled for a site
|
|
* @param Obj site object
|
|
* @return Boolean true if notification is enabled, false otherwise
|
|
*/
|
|
Site.prototype.isNotificationEnabled = function() {
|
|
if (root.sys_allowEmails == 1 || root.sys_allowEmails == 2 && this.trusted)
|
|
return true;
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* send e-mail notification if necessary
|
|
* @param String type of changes (e.g. createStory)
|
|
* @param HopObject the HopObject the changes were applied to
|
|
*/
|
|
Site.prototype.sendNotification = function(type, obj) {
|
|
var notify = this.preferences.getProperty("notify_" + type);
|
|
if (obj.online === 0 || !notify || notify == 0)
|
|
return;
|
|
var recipients = new Array();
|
|
for (var i=0; i<this.members.size(); i++) {
|
|
var m = this.members.get(i);
|
|
if ((type != "update" && m.user == obj.creator) || (type == "update" && m.user == obj.modifier))
|
|
continue;
|
|
if (notify == 1 && m.level >= CONTENTMANAGER)
|
|
recipients.push(m.user.email);
|
|
else if (notify == 2 && m.level >= CONTRIBUTOR)
|
|
recipients.push(m.user.email);
|
|
}
|
|
if (recipients.length > 0) {
|
|
var param = {
|
|
user: obj.modifier ? obj.modifier.name :
|
|
(obj.creator ? obj.creator.name : null),
|
|
url: obj.href()
|
|
};
|
|
var sender = root.sys_title + "<" + root.sys_email + ">";
|
|
var subject = "[" + root.sys_title + "] " + getMessage("mail.notification");
|
|
var body = this.renderSkinAsString("notificationMail", param);
|
|
sendMail(sender, recipients, subject, body);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* return the currently enabled layout object
|
|
*/
|
|
Site.prototype.getLayout = function() {
|
|
if (this.layout)
|
|
return this.layout;
|
|
return root.getLayout();
|
|
};
|
|
|
|
/**
|
|
* render the path to the static directory
|
|
* of this site
|
|
* @param String name of subdirectory (optional)
|
|
*/
|
|
Site.prototype.staticPath = function(subdir) {
|
|
res.write(app.properties.staticPath);
|
|
res.write(this.alias);
|
|
res.write("/");
|
|
if (subdir)
|
|
res.write(subdir);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* return the path to the static directory
|
|
* of this site
|
|
* @param String name of subdirectory (optional)
|
|
* @return String path to the static directory
|
|
*/
|
|
Site.prototype.getStaticPath = function(subdir) {
|
|
res.push();
|
|
this.staticPath(subdir);
|
|
return res.pop();
|
|
};
|
|
|
|
/**
|
|
* render the url of the static directory
|
|
* of this site
|
|
* @param String optional subdirectory
|
|
*/
|
|
Site.prototype.staticUrl = function(subdir) {
|
|
res.write(app.properties.staticUrl);
|
|
res.write(this.alias);
|
|
res.write("/");
|
|
if (subdir)
|
|
res.write(subdir);
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* return the url of the static directory
|
|
* of this site
|
|
* @param String optional subdirectory
|
|
* @return String static url
|
|
*/
|
|
Site.prototype.getStaticUrl = function(subdir) {
|
|
res.push();
|
|
this.staticUrl(subdir);
|
|
return res.pop();
|
|
};
|
|
|
|
/**
|
|
* return the directory containing static contents
|
|
* @param String subdirectory (optional)
|
|
* @return Object File object
|
|
*/
|
|
Site.prototype.getStaticDir = function(subdir) {
|
|
var f = new Helma.File(this.getStaticPath(subdir));
|
|
f.mkdir();
|
|
return f;
|
|
};
|
|
|
|
/**
|
|
* function returns the title of a site
|
|
*/
|
|
Site.prototype.getTitle = function() {
|
|
if (this.title && this.title.trim())
|
|
return stripTags(this.title);
|
|
else
|
|
return "[" + getMessage("generic.untitled") + "]";
|
|
};
|
|
|
|
/**
|
|
* function returns the used disk space for this site in Kilobyte
|
|
*/
|
|
Site.prototype.getDiskUsage = function() {
|
|
if (this.diskusage == null) {
|
|
this.diskusage = 0;
|
|
for (var i=0; i<this.files.count(); i++)
|
|
this.diskusage += this.files.get(i).filesize;
|
|
for (var i=0; i<this.images.count(); i++) {
|
|
if (this.images.get(i).filesize == null)
|
|
this.images.get(i).filesize = this.images.get(i).getFile().getLength();
|
|
this.diskusage += this.images.get(i).filesize;
|
|
}
|
|
}
|
|
return Math.round(this.diskusage / 1024);
|
|
};
|
|
|
|
/**
|
|
* function returns the disk quota in Kilobyte for this site
|
|
*/
|
|
Site.prototype.getDiskQuota = function() {
|
|
if (this.trusted || !root.sys_diskQuota)
|
|
return Infinity;
|
|
else
|
|
return root.sys_diskQuota;
|
|
};
|
|
|
|
/**
|
|
* returns the corresponding Index object for a site
|
|
* for performance reasons the Index object is cached
|
|
*/
|
|
Site.prototype.getIndex = function(force) {
|
|
if (!this.cache.index || force) {
|
|
var baseDir = new Helma.File(app.properties.indexPath);
|
|
var index, analyzer;
|
|
if (this.getLocale().getLanguage() == java.util.Locale.GERMAN)
|
|
analyzer = Search.getAnalyzer(java.util.Locale.GERMAN);
|
|
else
|
|
analyzer = Search.getAnalyzer();
|
|
// try to mount an existing index, if this fails create a new one
|
|
try {
|
|
index = Search.mountIndex(this.alias, baseDir, analyzer);
|
|
app.log("[" + this.alias + "] mounted index " + index);
|
|
} catch (e) {
|
|
try {
|
|
index = Search.createIndex(this.alias, baseDir, analyzer);
|
|
app.log("[" + this.alias + "] created index " + index);
|
|
} catch (e) {
|
|
throw ("[" + this.alias + "] Error: unable to mount or create index");
|
|
return;
|
|
}
|
|
}
|
|
this.cache.index = index;
|
|
}
|
|
return this.cache.index;
|
|
};
|
|
|
|
/**
|
|
* re-indexes all stories and comments of a site
|
|
* @param Object instance of Search.Index
|
|
* @return void
|
|
*/
|
|
Site.prototype.rebuildIndex = function() {
|
|
/**
|
|
* private method for constructing an index document
|
|
* based on the data retrieved via direct db
|
|
*/
|
|
function getIndexDocument() {
|
|
var doc = new Search.Document();
|
|
doc.addField("prototype", rows.getColumnItem("TEXT_PROTOTYPE"));
|
|
switch (rows.getColumnItem("TEXT_PROTOTYPE")) {
|
|
case "Comment":
|
|
doc.addField("story", rows.getColumnItem("TEXT_F_TEXT_STORY"), {store: true, index: true, tokenize: false});
|
|
if (parent = rows.getColumnItem("TEXT_F_TEXT_PARENT"))
|
|
doc.addField("parent", parent, {store: true, index: true, tokenize: false});
|
|
break;
|
|
default:
|
|
doc.addField("day", rows.getColumnItem("TEXT_DAY"), {store: true, index: true, tokenize: false});
|
|
if (topic = rows.getColumnItem("TEXT_TOPIC"))
|
|
doc.addField("topic", topic, {store: true, index: true, tokenize: true});
|
|
break;
|
|
}
|
|
|
|
doc.addField("online", rows.getColumnItem("TEXT_ISONLINE"), {store: true, index: true, tokenize: false});
|
|
doc.addField("site", self._id, {store: true, index: true, tokenize: false});
|
|
doc.addField("id", rows.getColumnItem("TEXT_ID"), {store: true, index: true, tokenize: false});
|
|
var content = Xml.readFromString(rows.getColumnItem("TEXT_CONTENT"));
|
|
for (var propName in content) {
|
|
doc.addField((propName == "title") ? "title" : "text",
|
|
stripTags(content[propName]),
|
|
{store: false, index: true, tokenize: true});
|
|
}
|
|
if (creator = rows.getColumnItem("USER_NAME")) {
|
|
doc.addField("creator", creator, {store: false, index: true, tokenize: false});
|
|
doc.addField("createtime", (new Date(rows.getColumnItem("TEXT_CREATETIME").getTime())).format("yyyyMMdd", locale, timeZone),
|
|
{store: false, index: true, tokenize: false});
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
var parent, topic, title, text, creator;
|
|
// lock the index queue to prevent it
|
|
// from being flushed by the IndexManager
|
|
var queue = app.data.indexManager.getQueue(this);
|
|
queue.lock();
|
|
var index = this.getIndex();
|
|
index.clear();
|
|
|
|
var buf = new java.util.Vector(500, 500);
|
|
var cnt = 0;
|
|
var now = new Date();
|
|
var start = new Date();
|
|
var locale = this.getLocale();
|
|
var timeZone = this.getTimeZone();
|
|
var dbCon = getDBConnection("antville");
|
|
var rows = dbCon.executeRetrieval("select TEXT_ID, TEXT_PROTOTYPE, TEXT_DAY, TEXT_TOPIC, TEXT_ALIAS, TEXT_F_TEXT_STORY, TEXT_F_TEXT_PARENT, TEXT_PROTOTYPE, TEXT_ISONLINE, TEXT_CONTENT, USER_NAME, TEXT_CREATETIME from AV_TEXT, AV_USER where TEXT_F_SITE = " + this._id + " and TEXT_F_USER_CREATOR = USER_ID");
|
|
app.log("[" + this.alias + "] retrieved indexable contents in " + ((new Date()).diff(now)) + " ms");
|
|
if (dbCon.lastError != null) {
|
|
app.log("[" + this.alias + "] unable to retrieve indexable contents, reason: " + dbCon.lastError);
|
|
} else {
|
|
var self = this;
|
|
while (rows.next()) {
|
|
try {
|
|
buf.add(getIndexDocument());
|
|
if (cnt > 0 && cnt % 2000 == 0) {
|
|
index.addDocument(buf);
|
|
buf.clear();
|
|
app.log("[" + this.alias + "] added " + cnt + " documents to index (last 1000 in " + ((new Date()).diff(now)) + " ms)");
|
|
now = new Date();
|
|
}
|
|
} catch (e) {
|
|
app.log("[" + this.alias + "] Error: unable to add document " + cnt + " to index. Reason: " + e.toString());
|
|
}
|
|
cnt++;
|
|
}
|
|
rows.release();
|
|
index.addDocument(buf);
|
|
app.log("[" + this.alias + "] finished adding " + cnt + " documents to index in " + ((new Date()).diff(start)) + " ms");
|
|
index.optimize();
|
|
}
|
|
|
|
// unlock the queue again
|
|
queue.unlock();
|
|
return cnt;
|
|
};
|
|
|
|
/**
|
|
* check if there are any stories in the previous month
|
|
*/
|
|
Site.prototype.renderLinkToPrevMonth = function(firstDayIndex, currentMonth, monthNames) {
|
|
var l = this.size();
|
|
if (l == 0 || l <= firstDayIndex)
|
|
return " ";
|
|
|
|
var prevDay = this.get(firstDayIndex + 1);
|
|
if (prevDay && prevDay.groupname < currentMonth) {
|
|
var month = prevDay.groupName.toString().substring(4, 6) - 1;
|
|
return Html.linkAsString({href: prevDay.href()}, monthNames[month]);
|
|
} else {
|
|
return " ";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* check if there are any stories in the previous month
|
|
*/
|
|
Site.prototype.renderLinkToNextMonth = function(lastDayIndex, currentMonth, monthNames) {
|
|
var l = this.size();
|
|
if (l == 0 || lastDayIndex == 0)
|
|
return " ";
|
|
|
|
var nextDay = this.get(lastDayIndex - 1);
|
|
if (nextDay && nextDay.groupname > currentMonth) {
|
|
var month = nextDay.groupName.toString().substring(4, 6) - 1;
|
|
return Html.linkAsString({href: nextDay.href()}, monthNames[month]);
|
|
} else {
|
|
return " ";
|
|
}
|
|
};
|
|
|
|
/**
|
|
* function renders the list of stories for site-(front-)pages
|
|
* and assigns the rendered list to res.data.storylist
|
|
* scrollnavigation-links to previous and next page(s) are also
|
|
* assigned to res.data (res.data.prevpage, res.data.nextpage)
|
|
* using this separate renderFunction instead of doing the stuff
|
|
* in storylist_macro() was necessary for completely independent
|
|
* placement of the prevpage- and nextpage-links
|
|
* @param Int Index-position to start with
|
|
*/
|
|
Site.prototype.renderStorylist = function(day) {
|
|
var size = this.size();
|
|
var idx = 0;
|
|
|
|
// if no day is specified, start with today. we may need
|
|
// to search for today's entries (or the latest entry
|
|
// before today) because there may be stories posted for
|
|
// future days. (HW)
|
|
var startdayString = day;
|
|
if (!startdayString)
|
|
startdayString = formatTimestamp(new Date(), "yyyyMMdd");
|
|
|
|
var startday = this.get(startdayString);
|
|
if (startday && startday.size()>0) {
|
|
idx = this.contains(startday);
|
|
} else {
|
|
// loop through days until we find a day less or equal than
|
|
// the one we're looking for.
|
|
for (var i=0; i<size; i++) {
|
|
if (startdayString >= this.get(i).groupname) {
|
|
this.get(i).prefetchChildren();
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var days = this.preferences.getProperty("days") ? this.preferences.getProperty("days") : 2;
|
|
days = Math.min (days, 14); // render 14 days max
|
|
this.prefetchChildren(idx, days);
|
|
|
|
// only display "newer stories" if we are actually browsing the archive,
|
|
// and the day parameter has been explicitly specified,
|
|
// i.e. suppress the link if we are on the home page and there are
|
|
// stories on future days. (HW)
|
|
if (idx > 0 && day) {
|
|
var sp = new Object();
|
|
var prev = this.get (Math.max(0, idx-days));
|
|
sp.url = this.href() + "?day=" + prev.groupname;
|
|
sp.text = getMessage("Story.newerStories");
|
|
res.data.prevpage = renderSkinAsString("prevpagelink", sp);
|
|
}
|
|
days = Math.min(idx + days++, this.size());
|
|
res.push();
|
|
while (idx < days) {
|
|
var day = this.get(idx++);
|
|
day.get(0).renderSkin("dayheader");
|
|
for (var i=0;i<day.size();i++)
|
|
day.get(i).renderSkin("preview");
|
|
}
|
|
res.data.storylist = res.pop();
|
|
if (idx < size) {
|
|
var sp = new Object();
|
|
var next = this.get (idx);
|
|
sp.url = this.href() + "?day=" + next.groupname;
|
|
sp.text = getMessage("Story.olderStories");
|
|
res.data.nextpage = renderSkinAsString("nextpagelink", sp);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* permission check (called by hopobject.onRequest())
|
|
* @param String name of action
|
|
* @param Obj User object
|
|
* @param Int Membership level
|
|
* @return Obj Exception object or null
|
|
*/
|
|
Site.prototype.checkAccess = function(action, usr, level) {
|
|
var url = this.members.href("login");
|
|
try {
|
|
switch (action) {
|
|
case "main" :
|
|
if (!this.online)
|
|
checkIfLoggedIn();
|
|
url = root.href();
|
|
this.checkView(usr, level);
|
|
break;
|
|
case "edit" :
|
|
checkIfLoggedIn();
|
|
url = this.href();
|
|
this.checkEdit(usr, level);
|
|
break;
|
|
case "delete" :
|
|
checkIfLoggedIn();
|
|
url = this.href();
|
|
this.checkDelete(usr, level);
|
|
break;
|
|
case "getfile" :
|
|
if (!this.online)
|
|
checkIfLoggedIn();
|
|
url = root.href();
|
|
this.checkView(usr, level);
|
|
break;
|
|
case "mostread" :
|
|
if (!this.online)
|
|
checkIfLoggedIn();
|
|
url = root.href();
|
|
this.checkView(usr, level);
|
|
break;
|
|
case "referrers" :
|
|
if (!this.online)
|
|
checkIfLoggedIn();
|
|
url = root.href();
|
|
this.checkView(usr, level);
|
|
break;
|
|
case "search" :
|
|
if (!this.online)
|
|
checkIfLoggedIn();
|
|
url = root.href();
|
|
this.checkView(usr, level);
|
|
break;
|
|
case "subscribe" :
|
|
checkIfLoggedIn();
|
|
this.checkSubscribe(usr, level);
|
|
break;
|
|
case "unsubscribe" :
|
|
checkIfLoggedIn();
|
|
this.checkUnsubscribe(usr, level);
|
|
break;
|
|
}
|
|
} catch (deny) {
|
|
res.message = deny.toString();
|
|
res.redirect(url);
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* check if a user is allowed to view a site
|
|
* @param Obj Userobject
|
|
* @param Int Permission-Level
|
|
*/
|
|
Site.prototype.checkView = function(usr, level) {
|
|
if (!this.online) {
|
|
// if not logged in or not logged in with sufficient permissions
|
|
if (!usr || (level < CONTRIBUTOR && !usr.sysadmin))
|
|
throw new DenyException("siteView");
|
|
}
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* check if user is allowed to edit the preferences of this site
|
|
* @param Obj Userobject
|
|
* @param Int Permission-Level
|
|
* @return String Reason for denial (or null if allowed)
|
|
*/
|
|
Site.prototype.checkEdit = function(usr, level) {
|
|
if (!usr.sysadmin && (level & MAY_EDIT_PREFS) == 0)
|
|
throw new DenyException("siteEdit");
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* check if user is allowed to delete the site
|
|
* (only SysAdmins or the creator of a site are allowed to delete it!)
|
|
* @param Obj Userobject
|
|
* @return String Reason for denial (or null if allowed)
|
|
*/
|
|
Site.prototype.checkDelete = function(usr) {
|
|
if (!usr.sysadmin && usr != this.creator)
|
|
throw new DenyException("siteDelete");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* function checks if user is allowed to sign up
|
|
* @param Obj Userobject
|
|
* @param Int Permission-Level
|
|
* @return String Reason for denial (or null if allowed)
|
|
*/
|
|
Site.prototype.checkSubscribe = function(usr, level) {
|
|
if (level != null)
|
|
throw new Exception("subscriptionExist");
|
|
else if (!this.online)
|
|
throw new DenyException("siteView");
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* check if user is allowed to unsubscribe
|
|
* @param Obj Userobject
|
|
* @return String Reason for denial (or null if allowed)
|
|
*/
|
|
Site.prototype.checkUnsubscribe = function(usr, level) {
|
|
if (level == null)
|
|
throw new Exception("subscriptionNoExist");
|
|
else if (level == ADMIN) {
|
|
// Admins are not allowed to remove a subscription
|
|
throw new DenyException("unsubscribe");
|
|
}
|
|
return;
|
|
};
|