cgi-bin/loona.lua
author Timm S. Mueller <tmueller@neoscientists.org>
Sun, 11 Mar 2007 21:13:25 +0100
changeset 151 e01bdf886128
parent 150 9772192829c1
child 154 b6f0d8084648
permissions -rw-r--r--
fixed argument passing to includes; fixed extensions
tmueller@0
     1
tmueller@0
     2
--
tmueller@0
     3
--	loona - tiny CMS
tmueller@0
     4
--	Written by Timm S. Mueller <tmueller at neoscientists.org>
tmueller@0
     5
--	See copyright notice in COPYRIGHT
tmueller@0
     6
--
tmueller@0
     7
tmueller@68
     8
local tek = require "tek"
tmueller@68
     9
local cgi = require "tek.cgi"
tmueller@68
    10
local posix = require "tek.posix"
tmueller@0
    11
require "tek.cgi.request"
tmueller@0
    12
require "tek.cgi.request.args"
tmueller@0
    13
require "tek.cgi.session"
tmueller@0
    14
require "tek.web"
tmueller@0
    15
require "tek.web.markup"
tmueller@0
    16
require "tek.util"
tmueller@0
    17
tmueller@23
    18
tmueller@0
    19
local boxed_G = { 
tmueller@0
    20
	string = string, table = table,
tmueller@0
    21
	assert = assert, collectgarbage = collectgarbage, dofile = dofile,
tmueller@0
    22
	error = error, getfenv = getfenv, getmetatable = getmetatable,
tmueller@0
    23
	ipairs = ipairs, load = load, loadfile = loadfile, loadstring = loadstring,
tmueller@0
    24
	next = next, pairs = pairs, pcall = pcall, print = print,
tmueller@0
    25
	rawequal = rawequal, rawget = rawget, rawset = rawset, require = require,
tmueller@0
    26
	select = select, setfenv = setfenv, setmetatable = setmetatable,
tmueller@0
    27
	tonumber = tonumber, tostring = tostring, type = type, unpack = unpack,
tmueller@0
    28
	xpcall = xpcall
tmueller@0
    29
}
tmueller@0
    30
tmueller@68
    31
local table, string, assert, unpack, ipairs, pairs, type, require =
tmueller@68
    32
	table, string, assert, unpack, ipairs, pairs, type, require
tmueller@0
    33
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv
tmueller@149
    34
local open, remove, rename, getenv, time, date =
tmueller@149
    35
	io.open, os.remove, os.rename, os.getenv, os.time, os.date
tmueller@0
    36
tmueller@0
    37
tmueller@0
    38
module "loona"
tmueller@0
    39
tmueller@0
    40
tmueller@129
    41
_VERSION = 3
tmueller@141
    42
_REVISION = 1
tmueller@0
    43
tmueller@0
    44
tmueller@20
    45
local function lookupname(tab, val)
tmueller@0
    46
	if tab then
tmueller@0
    47
		for i, v in ipairs(tab) do
tmueller@20
    48
			if v.name == val then
tmueller@0
    49
				return i
tmueller@0
    50
			end
tmueller@0
    51
		end
tmueller@0
    52
	end
tmueller@0
    53
end
tmueller@0
    54
tmueller@0
    55
tmueller@129
    56
local atom = { }
tmueller@20
    57
tmueller@149
    58
tmueller@129
    59
function atom:new(o)
tmueller@129
    60
	o = o or { }
tmueller@129
    61
	setmetatable(o, self)
tmueller@129
    62
	self.__index = self
tmueller@129
    63
	return o
tmueller@129
    64
end
tmueller@129
    65
tmueller@149
    66
tmueller@129
    67
local loona = atom:new(getfenv())
tmueller@129
    68
tmueller@129
    69
tmueller@129
    70
function loona:dbmsg(msg, detail)
tmueller@129
    71
 	return (msg and detail and self.authuser) and
tmueller@129
    72
 		("%s : %s"):format(msg, detail) or msg
tmueller@129
    73
end
tmueller@129
    74
tmueller@129
    75
tmueller@129
    76
function loona:checkprofilename(n)
tmueller@129
    77
	assert(n:match("^%w+$") and n ~= "current",
tmueller@129
    78
		self:dbmsg("Invalid profile name", n))
tmueller@129
    79
	return n
tmueller@129
    80
end
tmueller@129
    81
tmueller@129
    82
tmueller@129
    83
function loona:checkbodyname(s)
tmueller@129
    84
	s = s or "main"
tmueller@129
    85
	assert(s:match("^[%w_]*%w+[%w_]*$"), self:dbmsg("Invalid body name", s))
tmueller@129
    86
	return s
tmueller@129
    87
end
tmueller@129
    88
tmueller@129
    89
tmueller@129
    90
function loona:deleteprofile(p, lang)
tmueller@129
    91
	p = self.config.contentdir .. "/" .. p .. "_" .. (lang or self.lang)
tmueller@129
    92
	for e in tek.util.readdir(p) do
tmueller@129
    93
 		local success, msg = remove(p .. "/" .. e)
tmueller@129
    94
		assert(success, self:dbmsg("Error removing entry in profile", msg))
tmueller@129
    95
	end
tmueller@129
    96
	return remove(p)
tmueller@129
    97
end
tmueller@129
    98
tmueller@129
    99
tmueller@129
   100
function loona:copyprofile(newprofile, srcprofile, lang)
tmueller@129
   101
	srcprofile = srcprofile or self.profile
tmueller@129
   102
	lang = lang or self.lang
tmueller@129
   103
	local contentdir = self.config.contentdir
tmueller@143
   104
	local src = ("%s/%s_%s"):format(contentdir, srcprofile or self.profile, 
tmueller@143
   105
		lang)
tmueller@129
   106
	assert(posix.stat(src, "mode") == "directory",
tmueller@129
   107
		self:dbmsg("Not a directory", src))
tmueller@129
   108
	local dst = ("%s/%s_%s"):format(contentdir, newprofile, lang)
tmueller@129
   109
	local success, msg = posix.mkdir(dst)
tmueller@129
   110
	assert(success, self:dbmsg("Error creating profile directory", msg))
tmueller@129
   111
	for e in tek.util.readdir(src) do
tmueller@129
   112
		local ext = e:match("^[^.].*%.([^.]*)$")
tmueller@129
   113
		if ext ~= "LOCK" then
tmueller@129
   114
			local f = src .. "/" .. e
tmueller@129
   115
			if posix.stat(f, "mode") == "file" then
tmueller@129
   116
				success, msg = tek.copyfile(f, dst .. "/" .. e)
tmueller@129
   117
				assert(success, self:dbmsg("Error copying file", msg))
tmueller@129
   118
			end
tmueller@129
   119
		end
tmueller@129
   120
	end
tmueller@129
   121
end
tmueller@129
   122
tmueller@129
   123
tmueller@129
   124
function loona:publishprofile(profile, lang)
tmueller@129
   125
	lang = lang or self.lang
tmueller@129
   126
	local contentdir = self.config.contentdir
tmueller@129
   127
	local newpath = ("%s/current_%s"):format(contentdir, lang)
tmueller@129
   128
	local tmppath = newpath .. "." .. self.session.name
tmueller@129
   129
	local success, msg = posix.symlink(profile .. "_" .. lang, tmppath)
tmueller@129
   130
	assert(success, self:dbmsg("Cannot create symlink", msg))
tmueller@129
   131
	success, msg = rename(tmppath, newpath)
tmueller@129
   132
	assert(success, self:dbmsg("Cannot put symlink in place", msg))
tmueller@129
   133
	
tmueller@147
   134
	-- Get languages in the current profile
tmueller@141
   135
	
tmueller@141
   136
	local plangs = { }
tmueller@141
   137
	local lmatch = "^" .. self.profile .. "_(%w+)$"
tmueller@141
   138
	for e in tek.util.readdir(self.config.contentdir) do
tmueller@141
   139
		local l = e:match(lmatch)
tmueller@141
   140
		if l then
tmueller@141
   141
			table.insert(plangs, l)
tmueller@141
   142
		end
tmueller@141
   143
	end
tmueller@141
   144
	
tmueller@147
   145
	-- For all languages, unroll site to static HTML
tmueller@129
   146
	
tmueller@141
   147
	for _, lang in ipairs(plangs) do
tmueller@141
   148
		local ext = (#plangs == 1 and ".html") or (".html." .. lang)
tmueller@141
   149
		self:recursesections(self.sections, function(self, s, e, path)
tmueller@141
   150
			path = path and path .. "/" .. e.name or e.name
tmueller@141
   151
			if not e.notvisible then
tmueller@143
   152
				loona:dumphtml { requestpath = path, requestlang = lang,
tmueller@150
   153
					htmlext = ext, insecure = true }
tmueller@141
   154
			end
tmueller@141
   155
			return path
tmueller@141
   156
		end)
tmueller@141
   157
	end
tmueller@131
   158
	
tmueller@131
   159
	-- Update file cache
tmueller@131
   160
tmueller@131
   161
	local htdocs = self.config.htdocsdir
tmueller@131
   162
	local cache = self.config.htmlcachedir
tmueller@131
   163
tmueller@131
   164
	for e in tek.util.readdir(cache) do
tmueller@141
   165
		local f = e:match("^.*%.html%.?(%w*)$")
tmueller@141
   166
		if f and f ~= "tmp" then
tmueller@131
   167
			local success, msg = remove(htdocs .. "/" .. e)
tmueller@131
   168
			success, msg = remove(cache .. "/" .. e)
tmueller@143
   169
 			assert(success,
tmueller@143
   170
 				self:dbmsg("Could not purge cached HTML file", msg))
tmueller@131
   171
		end
tmueller@131
   172
	end
tmueller@147
   173
	
tmueller@131
   174
	for e in tek.util.readdir(cache) do
tmueller@141
   175
		local f = e:match("^(.*%.html%.?%w*)%.tmp$")
tmueller@131
   176
		if f then
tmueller@131
   177
			local success, msg = rename(cache .. "/" .. e, cache .. "/" .. f)
tmueller@143
   178
			assert(success,
tmueller@143
   179
				self:dbmsg("Could not update cached HTML file", msg))
tmueller@131
   180
			success, msg = rename(htdocs .. "/" .. e, htdocs .. "/" .. f)
tmueller@143
   181
			assert(success,
tmueller@143
   182
				self:dbmsg("Could not update cached HTML file", msg))
tmueller@131
   183
		end
tmueller@131
   184
	end
tmueller@129
   185
end
tmueller@129
   186
tmueller@129
   187
tmueller@129
   188
function loona:recursesections(s, func, ...)
tmueller@0
   189
	for _, e in ipairs(s) do
tmueller@129
   190
		local udata = { func(self, s, e, unpack(arg)) }
tmueller@0
   191
		if e.subs then
tmueller@129
   192
			self:recursesections(e.subs, func, unpack(udata))
tmueller@0
   193
		end
tmueller@129
   194
	end
tmueller@129
   195
end
tmueller@129
   196
tmueller@129
   197
tmueller@129
   198
function loona:indexsections()
tmueller@129
   199
	self:recursesections(self.sections, function(self, s, e)
tmueller@129
   200
		e.notvalid = (not self.secure and e.secure) or 
tmueller@129
   201
			(not self.authuser and e.secret) or nil
tmueller@129
   202
		e.notvisible = e.notvalid or not self.authuser and e.hidden or nil
tmueller@16
   203
		s[e.name] = e
tmueller@129
   204
	end)
tmueller@0
   205
end
tmueller@0
   206
tmueller@0
   207
tmueller@20
   208
--	Decompose section path into a stack of sections, returning only up to
tmueller@0
   209
--	the last valid element in the path. additionally returns the table of
tmueller@0
   210
--	the last section path element (or the default section)
tmueller@0
   211
tmueller@129
   212
function loona:getsection(path)
tmueller@129
   213
	local default = not self.authuser and self.config.defname
tmueller@129
   214
	local tab = { { entries = self.sections, name = default } }
tmueller@129
   215
	local ss = self.sections
tmueller@0
   216
	local sectionpath
tmueller@129
   217
	(path or ""):gsub("(%w+)/?", function(a)
tmueller@27
   218
		if ss then
tmueller@27
   219
			local s = ss[a]
tmueller@20
   220
			if s and not s.notvalid then
tmueller@20
   221
				sectionpath = s
tmueller@0
   222
				tab[#tab].name = a
tmueller@27
   223
				ss = s.subs
tmueller@27
   224
				if ss then
tmueller@27
   225
					table.insert(tab, { entries = ss })
tmueller@0
   226
				end
tmueller@0
   227
			else
tmueller@27
   228
				ss = nil -- stop.
tmueller@0
   229
			end
tmueller@0
   230
		end
tmueller@0
   231
	end)
tmueller@129
   232
	if not self.section and not sectionpath then
tmueller@129
   233
		sectionpath = self.sections[default]
tmueller@0
   234
		if sectionpath then
tmueller@0
   235
			table.insert(tab, { entries = sectionpath.subs })
tmueller@0
   236
		end
tmueller@0
   237
	end
tmueller@0
   238
	return tab, sectionpath
tmueller@0
   239
end
tmueller@0
   240
tmueller@0
   241
tmueller@129
   242
function loona:getpath(delimiter)
tmueller@0
   243
	local t = { }
tmueller@129
   244
	for _, menu in ipairs(self.submenus) do
tmueller@0
   245
		if menu.name then
tmueller@0
   246
			table.insert(t, menu.name)
tmueller@0
   247
		end
tmueller@0
   248
	end
tmueller@0
   249
	return table.concat(t, delimiter or "/")
tmueller@0
   250
end
tmueller@0
   251
tmueller@0
   252
tmueller@129
   253
function loona:deletesection(fname, all_bodies)
tmueller@129
   254
	local fullname = self.contentdir .. "/" .. fname
tmueller@0
   255
	local success, msg = remove(fullname)
tmueller@129
   256
	if all_bodies then
tmueller@0
   257
		local pat = "^" .. 
tmueller@0
   258
			fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
tmueller@129
   259
		for e in tek.util.readdir(self.contentdir) do
tmueller@0
   260
			if e:match(pat) then
tmueller@129
   261
				remove(self.contentdir .. "/" .. e)
tmueller@0
   262
			end
tmueller@0
   263
		end
tmueller@0
   264
	end
tmueller@0
   265
	return success, msg
tmueller@0
   266
end
tmueller@0
   267
tmueller@0
   268
tmueller@129
   269
function loona:addpath(path, e)
tmueller@129
   270
	local tab = self.sections
tmueller@0
   271
	path:gsub("(%w+)/?", function(a)
tmueller@20
   272
		if tab then
tmueller@20
   273
			local s = tab[a]
tmueller@20
   274
			if s then
tmueller@20
   275
				if not s.subs then
tmueller@20
   276
					s.subs = { }
tmueller@0
   277
				end
tmueller@20
   278
				tab = s.subs
tmueller@0
   279
			else
tmueller@20
   280
				table.insert(tab, e)
tmueller@20
   281
				tab[a] = e
tmueller@20
   282
 				tab = nil -- stop
tmueller@0
   283
			end
tmueller@0
   284
		end
tmueller@0
   285
	end)
tmueller@129
   286
	return e
tmueller@0
   287
end
tmueller@0
   288
tmueller@0
   289
tmueller@129
   290
function loona:rmpath(path)
tmueller@20
   291
	local parent
tmueller@129
   292
	local tab = self.sections
tmueller@0
   293
	path:gsub("(%w+)/?", function(a)
tmueller@20
   294
		if tab then
tmueller@20
   295
			local idx = lookupname(tab, a)
tmueller@20
   296
			if idx then
tmueller@20
   297
				if tab[idx].subs then
tmueller@20
   298
					parent = tab[idx]
tmueller@20
   299
					tab = tab[idx].subs
tmueller@0
   300
				else
tmueller@20
   301
					table.remove(tab, idx)
tmueller@43
   302
					tab[a] = nil
tmueller@20
   303
					if #tab == 0 and parent then
tmueller@20
   304
						parent.subs = nil
tmueller@0
   305
					end
tmueller@20
   306
					tab = nil
tmueller@0
   307
				end
tmueller@0
   308
			end
tmueller@0
   309
		end
tmueller@0
   310
	end)
tmueller@0
   311
end
tmueller@0
   312
tmueller@0
   313
tmueller@129
   314
function loona:checkpath(path)
tmueller@129
   315
	if path ~= "index" then -- "index" is reserved
tmueller@129
   316
		local res, idx
tmueller@129
   317
		local tab = self.sections
tmueller@129
   318
		path:gsub("(%w+)/?", function(a)
tmueller@129
   319
			if tab then
tmueller@129
   320
				local i = lookupname(tab, a)
tmueller@129
   321
				if i then
tmueller@129
   322
					res, idx = tab, i
tmueller@129
   323
					tab = tab[i].subs
tmueller@129
   324
				else
tmueller@129
   325
					res, idx = nil, nil
tmueller@129
   326
				end
tmueller@129
   327
			end
tmueller@129
   328
		end)
tmueller@129
   329
		return res, idx
tmueller@129
   330
	end
tmueller@8
   331
end
tmueller@8
   332
tmueller@8
   333
tmueller@129
   334
function loona:title()
tmueller@129
   335
	return self.section and (self.section.title or self.section.label or
tmueller@129
   336
		self.section.name) or ""
tmueller@0
   337
end
tmueller@0
   338
tmueller@0
   339
tmueller@0
   340
--	Run a site function snippet, with full error recovery
tmueller@0
   341
--	(also recovers from errors in error handling function)
tmueller@0
   342
tmueller@129
   343
function loona:dosnippet(func, errfunc)
tmueller@0
   344
	local ret = { tek.catch(func) }
tmueller@0
   345
	if ret[1] == 0 or (errfunc and tek.catch(errfunc) == 0) then
tmueller@0
   346
		return unpack(ret)
tmueller@0
   347
	end
tmueller@129
   348
	self:out("<h2>Error</h2>")
tmueller@129
   349
	self:out("<h3>" .. self:encodeform(ret[2] or "") .. "</h3>")
tmueller@129
   350
	if self.authuser then
tmueller@0
   351
		if type(ret[3]) == "string" then
tmueller@129
   352
			self:out("<p>" .. self:encodeform(ret[3]) .. "</p>")
tmueller@0
   353
		end
tmueller@129
   354
		if ret[4] then
tmueller@129
   355
			self:out("<pre>" .. self:encodeform(ret[4]) .. "</pre>")
tmueller@0
   356
		end
tmueller@0
   357
	end
tmueller@0
   358
end	
tmueller@0
   359
tmueller@0
   360
tmueller@129
   361
function loona:lockfile(file)
tmueller@129
   362
	return not self.session and true or 
tmueller@129
   363
		posix.symlink(self.session.filename, file .. ".LOCK")
tmueller@0
   364
end
tmueller@0
   365
tmueller@0
   366
tmueller@129
   367
function loona:unlockfile(file)
tmueller@129
   368
	return not self.session and true or remove(file .. ".LOCK")
tmueller@0
   369
end
tmueller@0
   370
tmueller@0
   371
tmueller@129
   372
function loona:saveindex()
tmueller@129
   373
	local tempname = self.indexfname .. "." .. self.session.name
tmueller@124
   374
	local f, msg = open(tempname, "wb")
tmueller@129
   375
	assert(f, self:dbmsg("Error opening section file for writing", msg))
tmueller@129
   376
	tek.dump(self.sections, function(...)
tmueller@124
   377
		f:write(unpack(arg))
tmueller@124
   378
	end)
tmueller@124
   379
	f:close()
tmueller@129
   380
	local success, msg = rename(tempname, self.indexfname)
tmueller@129
   381
	assert(success, self:dbmsg("Error renaming section file", msg))
tmueller@124
   382
end
tmueller@124
   383
tmueller@124
   384
tmueller@129
   385
function loona:savebody(fname, content)
tmueller@129
   386
	fname = self.contentdir .. "/" .. fname
tmueller@0
   387
	local f, msg = open(fname, "wb")
tmueller@129
   388
	assert(f, self:dbmsg("Could not open file for writing", msg))
tmueller@0
   389
	f:write(content or "")
tmueller@0
   390
	f:close()
tmueller@0
   391
end
tmueller@0
   392
tmueller@23
   393
tmueller@129
   394
function loona:runboxed(func, envitems, ...)
tmueller@129
   395
	local fenv = {
tmueller@129
   396
 		arg = arg,
tmueller@129
   397
 		loona = self
tmueller@129
   398
 	}
tmueller@129
   399
 	if envitems then
tmueller@129
   400
	 	for k, v in pairs(envitems) do
tmueller@129
   401
 			fenv[k] = v
tmueller@129
   402
 		end
tmueller@129
   403
 	end
tmueller@129
   404
 	setmetatable(fenv, { __index = boxed_G }) -- boxed global environment
tmueller@129
   405
	setfenv(func, fenv)
tmueller@129
   406
	return func()
tmueller@129
   407
end
tmueller@0
   408
tmueller@129
   409
tmueller@129
   410
function loona:include(fname, ...)
tmueller@129
   411
	assert(not fname:match("%W"), self:dbmsg("Invalid include name", fname))
tmueller@129
   412
	local fname2 = ("%s/%s.lua"):format(self.config.extdir, fname)
tmueller@0
   413
	local f, msg = open(fname2)
tmueller@129
   414
	assert(f, self:dbmsg("Cannot open file", msg))
tmueller@129
   415
	local parsed, msg = self:loadhtml(f, "loona:out", fname2)
tmueller@129
   416
	assert(parsed, self:dbmsg("Syntax error", msg))
tmueller@151
   417
	return self:runboxed(parsed, nil, unpack(arg))
tmueller@0
   418
end
tmueller@0
   419
tmueller@0
   420
tmueller@68
   421
--	produce link target, propagate lang, profile, session
tmueller@68
   422
tmueller@129
   423
function loona:href(section, ...)
tmueller@129
   424
	local target = self:getdocname(section)
tmueller@129
   425
	if self.session then
tmueller@68
   426
		return tek.web.gethref(target, { "profile", "session", "lang", 
tmueller@68
   427
			unpack(arg) })
tmueller@0
   428
	end
tmueller@68
   429
	return tek.web.gethref(target, { "lang", unpack(arg) })
tmueller@0
   430
end
tmueller@0
   431
tmueller@149
   432
tmueller@129
   433
function loona:ilink(target, text, extra)
tmueller@68
   434
	return ('<a href="%s"%s>%s</a>'):format(target, extra or "", text)
tmueller@0
   435
end
tmueller@0
   436
tmueller@149
   437
tmueller@68
   438
--	internal link, propagation of lang, profile, session
tmueller@0
   439
tmueller@129
   440
function loona:link(section, text, ...)
tmueller@129
   441
	return self:ilink(self:href(section, unpack(arg)), text or section)
tmueller@0
   442
end
tmueller@0
   443
tmueller@149
   444
tmueller@68
   445
--	external link (opens in a new window), no argument propagation
tmueller@0
   446
tmueller@129
   447
function loona:elink(target, text)
tmueller@143
   448
	return self:ilink(target, text or target,
tmueller@143
   449
		not self.config.extlinksamewindow and
tmueller@83
   450
		' class="extlink" onclick="void(window.open(this.href, \'\', \'\')); return false;"')
tmueller@0
   451
end
tmueller@0
   452
tmueller@149
   453
tmueller@68
   454
--	plain link, no argument propagation
tmueller@68
   455
tmueller@129
   456
function loona:plink(section, text, ...)
tmueller@129
   457
	return self:ilink(tek.web.gethref(section, arg), text or section)
tmueller@68
   458
end
tmueller@68
   459
tmueller@149
   460
tmueller@68
   461
--	user interface link, propagation of lang, profile, session
tmueller@68
   462
tmueller@129
   463
function loona:uilink(section, text, ...)
tmueller@129
   464
	return self:ilink(self:href(section, unpack(arg)), text or section)
tmueller@68
   465
end
tmueller@68
   466
tmueller@149
   467
tmueller@68
   468
--	produce a hidden input value in forms
tmueller@0
   469
tmueller@129
   470
function loona:hidden(name, value)
tmueller@68
   471
	return not value and "" or
tmueller@68
   472
		('<input type="hidden" name="%s" value="%s" />'):format(name, value)
tmueller@0
   473
end
tmueller@0
   474
tmueller@0
   475
tmueller@129
   476
function loona:getprofiles(lang)
tmueller@129
   477
	lang = lang or self.lang
tmueller@129
   478
	local dir = self.config.contentdir
tmueller@0
   479
	local t = { }
tmueller@129
   480
	for f in tek.util.readdir(dir) do
tmueller@129
   481
		if posix.lstat(dir .. "/" .. f, "mode") == "directory" then
tmueller@0
   482
			local e = f:match("^(%w+)_" .. lang .. "$")
tmueller@0
   483
 			if e then
tmueller@0
   484
	 			t[e] = e
tmueller@0
   485
 	 		end
tmueller@0
   486
		end
tmueller@0
   487
	end
tmueller@0
   488
	return t
tmueller@0
   489
end
tmueller@0
   490
tmueller@0
   491
tmueller@80
   492
--	Functions to produce a hierarchical navigation menu
tmueller@80
   493
tmueller@129
   494
local newent = { name = "new", label = "[+]", action="actionnew=true" }
tmueller@124
   495
tmueller@143
   496
function loona:rmenu(level, linkf, path, addnew)
tmueller@143
   497
	local sub = (addnew and level == #self.submenus + 1) and
tmueller@129
   498
		{ name = "new", entries = { }} or self.submenus[level]
tmueller@124
   499
 	if sub and sub.entries then
tmueller@80
   500
		local visible = { }
tmueller@80
   501
		for _, e in ipairs(sub.entries) do
tmueller@80
   502
			if not e.notvisible then
tmueller@80
   503
				table.insert(visible, e)
tmueller@80
   504
			end
tmueller@80
   505
		end
tmueller@143
   506
		if addnew then
tmueller@124
   507
			table.insert(visible, newent)
tmueller@124
   508
		end
tmueller@80
   509
		if #visible > 0 then
tmueller@129
   510
			self:out('<ul id="menulevel' .. level .. '">\n')
tmueller@80
   511
			for _, e in ipairs(visible) do
tmueller@129
   512
				local label = self:encodeform(e.label or e.name)
tmueller@80
   513
				local newpath = path and path .. "/" .. e.name or e.name
tmueller@80
   514
				local active = (e.name == sub.name)
tmueller@129
   515
				self:out('<li>\n')
tmueller@129
   516
				linkf(self, newpath, label, active, e.action)
tmueller@80
   517
				if active then
tmueller@145
   518
					self:rmenu(level + 1, linkf, newpath, addnew)
tmueller@80
   519
				end
tmueller@129
   520
				self:out('</li>\n')
tmueller@80
   521
			end
tmueller@129
   522
			self:out('</ul>\n')
tmueller@80
   523
		end
tmueller@80
   524
	end
tmueller@80
   525
end
tmueller@80
   526
tmueller@149
   527
tmueller@129
   528
function loona:menulink(path, label, active, ...)
tmueller@143
   529
	self:out(('<a %shref="%s">%s</a>\n'):format(active and 
tmueller@143
   530
		'class="active" ' or "", self:href(path, unpack(arg)), label))
tmueller@80
   531
end
tmueller@80
   532
tmueller@149
   533
tmueller@129
   534
function loona:menu(level, linkf)
tmueller@143
   535
	local addnew = self.authuser and not self.ispubprofile
tmueller@143
   536
	self:rmenu(level or 1, linkf or menulink, nil, addnew)
tmueller@80
   537
end
tmueller@80
   538
tmueller@80
   539
tmueller@147
   540
function loona:loadcontent(fname)
tmueller@147
   541
	if fname then
tmueller@147
   542
		local f = open(self.contentdir .. "/" .. fname)
tmueller@147
   543
		local c = f:read("*a")
tmueller@147
   544
		f:close()
tmueller@147
   545
		return c
tmueller@147
   546
	end
tmueller@147
   547
	return ""
tmueller@147
   548
end
tmueller@147
   549
tmueller@147
   550
tmueller@147
   551
function loona:loadmarkup(fname)
tmueller@147
   552
	return (fname and fname ~= "") and
tmueller@147
   553
		self:domarkup(self:loadcontent(fname)) or ""
tmueller@147
   554
end
tmueller@147
   555
tmueller@147
   556
tmueller@147
   557
function loona:editable(editkey, fname, savename)
tmueller@147
   558
	
tmueller@147
   559
	local contentdir = self.contentdir
tmueller@147
   560
	local edit, show, hidden, extramsg, changed
tmueller@147
   561
	
tmueller@147
   562
	if self.authuser then
tmueller@149
   563
		
tmueller@149
   564
		local hiddenvars = table.concat( {
tmueller@149
   565
			self:hidden("lang", self.args.lang),
tmueller@149
   566
			self:hidden("profile", self.profile),
tmueller@149
   567
			self:hidden("session", self.session.id),
tmueller@149
   568
			self:hidden("editkey", editkey) }, " ")
tmueller@149
   569
	
tmueller@147
   570
		local lockfname = fname and (contentdir .. "/" .. fname)
tmueller@147
   571
		
tmueller@147
   572
		if self.useralert and editkey == self.args.editkey then
tmueller@149
   573
			
tmueller@147
   574
			--	display user alert/request/confirmation
tmueller@149
   575
			
tmueller@147
   576
			hidden = true
tmueller@149
   577
			self:out([[
tmueller@149
   578
			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
tmueller@149
   579
				<fieldset>
tmueller@149
   580
					<legend>]] .. self.useralert.text ..[[</legend>
tmueller@149
   581
					]] .. (self.useralert.confirm or "") .. [[
tmueller@149
   582
					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL  ..[[" />
tmueller@149
   583
					]] .. hiddenvars .. [[
tmueller@149
   584
				</fieldset>
tmueller@149
   585
			</form>
tmueller@147
   586
			]])
tmueller@147
   587
			
tmueller@147
   588
		elseif self.args.actionnew and editkey == "main" then
tmueller@149
   589
			
tmueller@147
   590
			--	form for creating a new section
tmueller@149
   591
			
tmueller@147
   592
			hidden = true
tmueller@149
   593
			if self.ispubprofile then
tmueller@149
   594
				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
tmueller@149
   595
			end
tmueller@149
   596
			self:out([[
tmueller@149
   597
			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
tmueller@149
   598
				<fieldset>
tmueller@149
   599
					<legend>
tmueller@149
   600
						]] .. self.locale.CREATE_NEW_SECTION_UNDER .. " " .. self.sectionpath .. [[
tmueller@149
   601
					</legend>
tmueller@149
   602
					<table>
tmueller@149
   603
						<tr>
tmueller@149
   604
							<td align="right">
tmueller@149
   605
								]] .. self.locale.PATHNAME .. [[
tmueller@149
   606
							</td>
tmueller@149
   607
							<td>
tmueller@149
   608
								<input size="30" maxlength="30" name="editname" />
tmueller@149
   609
							</td>
tmueller@149
   610
						</tr>
tmueller@149
   611
						<tr>
tmueller@149
   612
							<td align="right">
tmueller@149
   613
								]] .. self.locale.MENULABEL .. [[
tmueller@149
   614
							</td>
tmueller@149
   615
							<td>
tmueller@149
   616
								<input size="30" maxlength="50" name="editlabel" />
tmueller@149
   617
							</td>
tmueller@149
   618
						</tr>
tmueller@149
   619
						<tr>
tmueller@149
   620
							<td align="right">
tmueller@149
   621
								]] .. self.locale.WINDOWTITLE .. [[
tmueller@149
   622
							</td>
tmueller@149
   623
							<td>
tmueller@149
   624
								<input size="30" maxlength="50" name="edittitle" />
tmueller@149
   625
							</td>
tmueller@149
   626
						</tr>
tmueller@149
   627
						<tr>
tmueller@149
   628
							<td align="right">
tmueller@149
   629
								]] .. self.locale.INVISIBLE .. [[
tmueller@149
   630
							</td>
tmueller@149
   631
							<td>
tmueller@149
   632
								<input type="checkbox" name="editvisibility" />
tmueller@149
   633
							</td>
tmueller@149
   634
						</tr>
tmueller@149
   635
						<tr>
tmueller@149
   636
							<td align="right">
tmueller@149
   637
								]] .. self.locale.SECRET .. [[
tmueller@149
   638
							</td>
tmueller@149
   639
							<td>
tmueller@149
   640
								<input type="checkbox" name="editsecrecy" />
tmueller@149
   641
							</td>
tmueller@149
   642
						</tr>
tmueller@149
   643
						<tr>
tmueller@149
   644
							<td align="right">
tmueller@149
   645
								]] .. self.locale.SECURE_CONNECTION .. [[
tmueller@149
   646
							</td>
tmueller@149
   647
							<td>
tmueller@149
   648
								<input type="checkbox" name="editsecure" />
tmueller@149
   649
							</td>
tmueller@149
   650
						</tr>
tmueller@149
   651
						<tr>
tmueller@149
   652
							<td align="right">
tmueller@149
   653
								]] .. self.locale.REDIRECT .. [[
tmueller@149
   654
							</td>
tmueller@149
   655
							<td>
tmueller@149
   656
								<input size="30" maxlength="50" name="editredirect" />
tmueller@149
   657
							</td>
tmueller@149
   658
						</tr>
tmueller@149
   659
					</table>					
tmueller@149
   660
					<input type="submit" name="actioncreate" value="]] .. self.locale.CREATE .. [[" />
tmueller@149
   661
					]] .. hiddenvars .. [[
tmueller@149
   662
				</fieldset>
tmueller@149
   663
			</form>
tmueller@149
   664
			<hr />
tmueller@147
   665
			]])
tmueller@147
   666
		
tmueller@147
   667
		elseif self.args.actioneditprops and editkey == "main" then
tmueller@147
   668
			hidden = true
tmueller@149
   669
			if self.ispubprofile then
tmueller@149
   670
				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
tmueller@149
   671
			end
tmueller@149
   672
			self:out([[
tmueller@149
   673
			<form action="]] ..self.document .. [[" method="post" accept-charset="utf-8">
tmueller@149
   674
				<fieldset>
tmueller@149
   675
					<legend>
tmueller@149
   676
						]] .. self.locale.MODIFY_PROPERTIES_OF_SECTION .. " " .. self.sectionpath .. [[
tmueller@149
   677
					</legend>
tmueller@149
   678
					<table>
tmueller@149
   679
						<tr>
tmueller@149
   680
							<td align="right">
tmueller@149
   681
								]] .. self.locale.MENULABEL .. [[
tmueller@149
   682
							</td>
tmueller@149
   683
							<td>
tmueller@149
   684
								<input size="30" maxlength="50" name="editlabel" value="]] .. (self.section.label or "") .. [[" />
tmueller@149
   685
							</td>
tmueller@149
   686
						</tr>
tmueller@149
   687
						<tr>
tmueller@149
   688
							<td align="right">
tmueller@149
   689
								]] .. self.locale.WINDOWTITLE .. [[
tmueller@149
   690
							</td>
tmueller@149
   691
							<td>
tmueller@149
   692
								<input size="30" maxlength="50" name="edittitle" value="]] .. (self.section.title or "") .. [[" />
tmueller@149
   693
							</td>
tmueller@149
   694
						</tr>
tmueller@149
   695
						<tr>
tmueller@149
   696
							<td align="right">
tmueller@149
   697
								]] .. self.locale.INVISIBLE .. [[
tmueller@149
   698
							</td>
tmueller@149
   699
							<td>
tmueller@149
   700
								<input type="checkbox" name="editvisibility" ]] .. (self.section.hidden and 'checked="checked"' or "") .. [[/>
tmueller@149
   701
							</td>
tmueller@149
   702
						</tr>
tmueller@149
   703
						<tr>
tmueller@149
   704
							<td align="right">
tmueller@149
   705
								]] .. self.locale.SECRET .. [[
tmueller@149
   706
							</td>
tmueller@149
   707
							<td>
tmueller@149
   708
								<input type="checkbox" name="editsecrecy" ]] .. (self.section.secret and 'checked="checked"' or "") .. [[/>
tmueller@149
   709
							</td>
tmueller@149
   710
						</tr>
tmueller@149
   711
						<tr>
tmueller@149
   712
							<td align="right">
tmueller@149
   713
								]] .. self.locale.SECURE_CONNECTION .. [[
tmueller@149
   714
							</td>
tmueller@149
   715
							<td>
tmueller@149
   716
								<input type="checkbox" name="editsecure" ]] .. (self.section.secure and 'checked="checked"' or "") .. [[/>
tmueller@149
   717
							</td>
tmueller@149
   718
						</tr>
tmueller@149
   719
						<tr>
tmueller@149
   720
							<td align="right">
tmueller@149
   721
								]] .. self.locale.REDIRECT .. [[
tmueller@149
   722
							</td>
tmueller@149
   723
							<td>
tmueller@149
   724
								<input size="30" maxlength="50" name="editredirect" value="]] .. (self.section.redirect or "") .. [[" />
tmueller@149
   725
							</td>
tmueller@149
   726
						</tr>
tmueller@149
   727
					</table>
tmueller@149
   728
					<input type="submit" name="actionsaveprops" value="]] .. self.locale.SAVE .. [[" />
tmueller@149
   729
					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
tmueller@149
   730
					]] .. hiddenvars .. [[
tmueller@149
   731
				</fieldset>
tmueller@149
   732
			</form>
tmueller@147
   733
			]])
tmueller@149
   734
		
tmueller@147
   735
		elseif (self.args.actioneditprofiles or
tmueller@147
   736
			self.args.actioncreateprofile or 
tmueller@147
   737
			self.args.actionchangeprofile or 
tmueller@147
   738
			self.args.actionpublishprofile) and editkey == "main" then
tmueller@147
   739
			hidden = true
tmueller@147
   740
			local profiles = { }
tmueller@147
   741
			for p in pairs(self:getprofiles()) do
tmueller@147
   742
				table.insert(profiles, p)
tmueller@147
   743
			end
tmueller@147
   744
			table.sort(profiles)
tmueller@149
   745
			self:out([[
tmueller@149
   746
			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
tmueller@149
   747
				<fieldset>
tmueller@149
   748
					<legend>
tmueller@149
   749
						]] .. self.locale.CHANGEPROFILE .. [[
tmueller@149
   750
					</legend>
tmueller@149
   751
					<select name="changeprofile" size="1">]])
tmueller@149
   752
						for _, val in ipairs(profiles) do
tmueller@149
   753
							self:out('<option' .. (val == self.profile and " selected" or "") .. '>')
tmueller@149
   754
							self:out(val)
tmueller@149
   755
							self:out('</option>')
tmueller@149
   756
						end
tmueller@149
   757
					self:out([[
tmueller@149
   758
					</select>							
tmueller@149
   759
					<input type="submit" name="actionchangeprofile" value="]] .. self.locale.CHANGE ..[[" />
tmueller@149
   760
					]] .. hiddenvars .. [[
tmueller@149
   761
				</fieldset>
tmueller@149
   762
			</form>
tmueller@149
   763
			<form action="]] .. self.document ..[[" method="post" accept-charset="utf-8">
tmueller@149
   764
				<fieldset>
tmueller@149
   765
					<legend>
tmueller@149
   766
						]] .. self.locale.CREATEPROFILE .. [[
tmueller@149
   767
					</legend>
tmueller@149
   768
					<input size="20" maxlength="20" name="createprofile" />
tmueller@149
   769
					<input type="submit" name="actioncreateprofile" value="]] .. self.locale.CREATE .. [[" />
tmueller@149
   770
					]] .. hiddenvars .. [[
tmueller@149
   771
				</fieldset>
tmueller@149
   772
			</form>
tmueller@149
   773
			]])
tmueller@149
   774
			if not self.ispubprofile then
tmueller@147
   775
				self:out([[
tmueller@147
   776
				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
tmueller@147
   777
					<fieldset>
tmueller@147
   778
						<legend>
tmueller@149
   779
							]] .. self.locale.PUBLISHPROFILE .. [[
tmueller@147
   780
						</legend>
tmueller@149
   781
						]] .. self:hidden("publishprofile", self.profile) .. [[
tmueller@149
   782
						<input type="submit" name="actionpublishprofile" value="]] .. self.locale.PUBLISH .. [[" />
tmueller@147
   783
						]] .. hiddenvars .. [[
tmueller@147
   784
					</fieldset>
tmueller@147
   785
				</form>
tmueller@149
   786
				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
tmueller@147
   787
					<fieldset>
tmueller@147
   788
						<legend>
tmueller@149
   789
							]] .. self.locale.DELETEPROFILE .. [[
tmueller@147
   790
						</legend>
tmueller@149
   791
						]] .. self:hidden("deleteprofile", self.profile) .. [[
tmueller@149
   792
						<input type="submit" name="actiondeleteprofile" value="]] .. self.locale.DELETE .. [[" />
tmueller@147
   793
						]] .. hiddenvars .. [[
tmueller@147
   794
					</fieldset>
tmueller@147
   795
				</form>
tmueller@147
   796
				]])
tmueller@149
   797
			end
tmueller@149
   798
			
tmueller@147
   799
		elseif self.args.actionedit and editkey == self.args.editkey then
tmueller@147
   800
			if not self.section.redirect then
tmueller@147
   801
				extramsg = self.ispubprofile and
tmueller@147
   802
					self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE
tmueller@147
   803
				edit = self:loadcontent(fname):gsub("\194\160", "&nbsp;") -- TODO
tmueller@147
   804
				changed = self.section and (self.section.revisiondate or self.section.creationdate)
tmueller@147
   805
			end
tmueller@149
   806
		
tmueller@147
   807
		elseif self.args.actionpreview and editkey == self.args.editkey then
tmueller@147
   808
			edit = self.args.editform
tmueller@147
   809
			show = self:domarkup(edit:gsub("&nbsp;", "\194\160")) -- TODO
tmueller@149
   810
		
tmueller@147
   811
		elseif self.args.actionsave and editkey == self.args.editkey then
tmueller@147
   812
			local c = self.args.editform
tmueller@147
   813
			local dynamic
tmueller@147
   814
			
tmueller@147
   815
			if lockfname then
tmueller@147
   816
				self:expire(contentdir, "[^.]%S+.LOCK")
tmueller@147
   817
				if self:lockfile(lockfname) then
tmueller@147
   818
					-- lock was expired, aquired a new one
tmueller@147
   819
					extramsg = self.locale.SECTION_COULD_HAVE_CHANGED
tmueller@147
   820
					edit = c
tmueller@147
   821
				else
tmueller@147
   822
					local tab = tek.source(lockfname .. ".LOCK")
tmueller@147
   823
					if tab and tab.id == self.session.id then
tmueller@147
   824
						-- lock already held and is mine - try to save:
tmueller@147
   825
						local savec = c:gsub("&nbsp;", "\194\160") -- TODO
tmueller@149
   826
						remove(contentdir .. "/" .. savename .. ".html")
tmueller@147
   827
						self:savebody(savename, savec)
tmueller@147
   828
						-- TODO: error handling
tmueller@147
   829
						self:unlockfile(lockfname)
tmueller@147
   830
						show, dynamic = self:domarkup(savec)
tmueller@149
   831
						changed = time()
tmueller@147
   832
					else
tmueller@147
   833
						-- lock was expired and someone else has it now
tmueller@147
   834
						extramsg = self.locale.SECTION_IN_USE
tmueller@147
   835
						edit = c
tmueller@147
   836
					end
tmueller@147
   837
				end
tmueller@147
   838
			else
tmueller@147
   839
				-- new sidefile
tmueller@147
   840
				local savec = c:gsub("&nbsp;", "\194\160") -- TODO
tmueller@147
   841
				self:savebody(savename, savec)
tmueller@147
   842
				-- TODO: error handling
tmueller@147
   843
				show, dynamic = self:domarkup(savec)
tmueller@147
   844
			end
tmueller@147
   845
			
tmueller@147
   846
			-- mark dynamic text bodies
tmueller@147
   847
			if not self.section.dynamic then
tmueller@147
   848
				self.section.dynamic = { }
tmueller@147
   849
			end
tmueller@147
   850
			self.section.dynamic[editkey] = dynamic
tmueller@147
   851
			local n = 0
tmueller@147
   852
			for _ in pairs(self.section.dynamic) do
tmueller@147
   853
				n = n + 1
tmueller@147
   854
			end
tmueller@147
   855
			if n == 0 then
tmueller@147
   856
				self.section.dynamic = nil
tmueller@147
   857
			end
tmueller@147
   858
			
tmueller@147
   859
			self:saveindex()
tmueller@147
   860
			
tmueller@147
   861
		elseif self.args.actioncancel and editkey == self.args.editkey then
tmueller@147
   862
			if lockfname then
tmueller@147
   863
				self:unlockfile(lockfname) -- remove lock
tmueller@147
   864
			end
tmueller@147
   865
		end
tmueller@147
   866
		
tmueller@147
   867
		if editkey == "main" and self.section and self.section.redirect then
tmueller@149
   868
			self:out('<h2>' .. self.locale.SECTION_IS_REDIRECT ..'</h2>')
tmueller@149
   869
			self:out(self:link(self.section.redirect))
tmueller@149
   870
			self:out('<hr />')
tmueller@147
   871
		end
tmueller@147
   872
	
tmueller@147
   873
		if edit then
tmueller@147
   874
			self:expire(contentdir, "[^.]%S+.LOCK")
tmueller@147
   875
			if fname and not self:lockfile(contentdir .. "/" .. fname) then
tmueller@147
   876
				local tab = tek.source(contentdir .. "/" .. fname .. ".LOCK")
tmueller@147
   877
				if tab and tab.id ~= self.session.id then
tmueller@147
   878
					extramsg = self.locale.SECTION_IN_USE
tmueller@147
   879
				end
tmueller@147
   880
				-- else already owner
tmueller@147
   881
			end
tmueller@149
   882
			if extramsg then
tmueller@149
   883
				self:out('<h2><span class="warn">' .. extramsg .. '</span></h2>')
tmueller@149
   884
			end
tmueller@149
   885
			self:out([[
tmueller@149
   886
			<form action="]] .. self.document .. [[#preview" method="post" accept-charset="utf-8">
tmueller@149
   887
				<fieldset>
tmueller@149
   888
					<legend>
tmueller@149
   889
						]] .. self.locale.EDIT_SECTION .. [[
tmueller@149
   890
					</legend>
tmueller@149
   891
					<textarea cols="80" rows="25" name="editform">]] .. self:encodeform(edit) .. [[</textarea>
tmueller@149
   892
					<br />
tmueller@149
   893
					<input type="submit" name="actionsave" value="]] .. self.locale.SAVE .. [[" />
tmueller@149
   894
					<input type="submit" name="actionpreview" value="]] .. self.locale.PREVIEW .. [[" />
tmueller@149
   895
					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
tmueller@149
   896
					]] .. hiddenvars .. [[
tmueller@149
   897
				</fieldset>
tmueller@149
   898
			</form>
tmueller@147
   899
			]])
tmueller@147
   900
		end
tmueller@147
   901
	end	
tmueller@147
   902
	
tmueller@147
   903
	if not hidden then
tmueller@149
   904
		self:dosnippet(function()
tmueller@149
   905
			if not show then
tmueller@149
   906
				show = self:loadmarkup(fname)
tmueller@149
   907
				changed = self.section and (self.section.revisiondate or self.section.creationdate)
tmueller@149
   908
			end
tmueller@149
   909
			local parsed, msg = self:loadhtml(show, "loona:out", "<parsed html>")
tmueller@149
   910
			assert(parsed, msg and "Syntax error : " .. msg)
tmueller@149
   911
			self:runboxed(parsed)
tmueller@147
   912
		end)
tmueller@147
   913
	end
tmueller@147
   914
	
tmueller@147
   915
	if self.authuser then
tmueller@149
   916
		self:out([[
tmueller@149
   917
		<hr />
tmueller@149
   918
		<div class="edit">]])
tmueller@149
   919
			if editkey == "main" then
tmueller@149
   920
				self:out([[
tmueller@149
   921
				<a name="preview"></a>
tmueller@149
   922
				]] .. self.authuser .. [[ : 
tmueller@149
   923
				]] .. self:uilink(self.sectionpath, "[" .. self.locale.PROFILE .. "]", "actioneditprofiles=true", "editkey=" .. editkey) .. [[ :
tmueller@149
   924
				]] .. self.profile .. "_" .. self.lang)
tmueller@149
   925
				if self.ispubprofile then
tmueller@149
   926
					self:out([[
tmueller@149
   927
						<span class="warn">[]] .. self.locale.PUBLIC .. [[]</span>]])
tmueller@149
   928
				end
tmueller@149
   929
				self:out(' : ' .. self.sectionpath .. ' ')
tmueller@149
   930
			end
tmueller@149
   931
			if self.section and not self.ispubprofile then
tmueller@149
   932
				self:out(self:uilink(self.sectionpath, "[" .. self.locale.EDIT .. "]", "actionedit=true", "editkey=" .. editkey) .. " ")
tmueller@147
   933
				if editkey == "main" then
tmueller@149
   934
					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.PROPERTIES .. "]", "actioneditprops=true", "editkey=" .. editkey) .. " ")
tmueller@147
   935
				end
tmueller@149
   936
				if fname == savename or not self.section.subs then
tmueller@149
   937
					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.DELETE .. "]", "actiondelete=true", "editkey=" .. editkey) .. " ")
tmueller@147
   938
				end
tmueller@149
   939
				if editkey == "main" then
tmueller@149
   940
					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEUP .. "]", "actionup=true", "editkey=" .. editkey) .. " ")
tmueller@149
   941
					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEDOWN .. "]", "actiondown=true", "editkey=" .. editkey) .. " ")
tmueller@149
   942
				end
tmueller@149
   943
				if changed and editkey == "main" then
tmueller@149
   944
					self:out('- ' .. self.locale.CHANGED .. ': ' .. date("%d-%b-%Y %T", changed))
tmueller@149
   945
				end
tmueller@149
   946
			end
tmueller@149
   947
		self:out('</div>')
tmueller@147
   948
	end
tmueller@147
   949
tmueller@147
   950
end
tmueller@147
   951
tmueller@147
   952
tmueller@147
   953
--	Get pathname of an existing content file that
tmueller@147
   954
--	the current path is determined by (or defaults to)
tmueller@147
   955
tmueller@147
   956
function loona:getsectionpath(bodyname, requestpath)
tmueller@147
   957
	local ext = (not bodyname or bodyname == "main") and "" or "." .. bodyname
tmueller@147
   958
	local t, path, section = { }
tmueller@147
   959
	for _, menu in ipairs(self.submenus) do
tmueller@147
   960
		if menu.entries and menu.entries[menu.name] then
tmueller@147
   961
			table.insert(t, menu.name)
tmueller@147
   962
			local fn = table.concat(t, "_")
tmueller@147
   963
			if posix.stat(self.contentdir .. "/" .. fn .. ext, 
tmueller@147
   964
				"mode") == "file" then
tmueller@147
   965
				path, section = fn, menu
tmueller@147
   966
			end
tmueller@147
   967
		end
tmueller@147
   968
	end
tmueller@147
   969
	return path, ext, section
tmueller@147
   970
end
tmueller@147
   971
tmueller@147
   972
tmueller@147
   973
function loona:body(name)
tmueller@147
   974
	name = self:checkbodyname(name)
tmueller@147
   975
	local path, ext = self:getsectionpath(name)
tmueller@149
   976
-- 	self:dosnippet(self:editable(name, path and path .. ext, self.sectionname .. ext))
tmueller@149
   977
	self:dosnippet(function()
tmueller@149
   978
		self:editable(name, path and path .. ext, self.sectionname .. ext)
tmueller@149
   979
	end)
tmueller@147
   980
end
tmueller@147
   981
tmueller@147
   982
tmueller@129
   983
function loona:init()
tmueller@0
   984
	
tmueller@0
   985
	-- get list of languages, in order of preference
tmueller@141
   986
	-- TODO: respect quality parameter, not just order
tmueller@0
   987
	
tmueller@141
   988
	local l = self.requestlang or self.args.lang
tmueller@141
   989
	self.langs = { l and l:match("^%w+$") }
tmueller@129
   990
	if self.config.browserlang then
tmueller@0
   991
		local s = getenv("HTTP_ACCEPT_LANGUAGE")
tmueller@0
   992
		while s do
tmueller@0
   993
			local l, r = s:match("^([%w.=]+)[,;](.*)$")
tmueller@0
   994
			l = l or s
tmueller@0
   995
			s = r
tmueller@0
   996
			if l:match("^%w+$") then
tmueller@129
   997
				table.insert(self.langs, l)
tmueller@0
   998
			end
tmueller@0
   999
		end
tmueller@0
  1000
	end
tmueller@129
  1001
	table.insert(self.langs, self.config.deflang)
tmueller@0
  1002
	
tmueller@0
  1003
	-- get list of possible profiles
tmueller@0
  1004
	
tmueller@0
  1005
	local profiles = { }
tmueller@129
  1006
	for e in tek.util.readdir(self.config.contentdir) do
tmueller@0
  1007
		profiles[e] = e
tmueller@0
  1008
	end
tmueller@0
  1009
	
tmueller@0
  1010
	-- get pubprofile
tmueller@0
  1011
	
tmueller@129
  1012
	for _, lang in ipairs(self.langs) do
tmueller@129
  1013
		local p = posix.readlink(self.config.contentdir .. "/current_" .. lang)
tmueller@0
  1014
		p = p and p:match("^(%w+)_" .. lang .. "$")
tmueller@0
  1015
		if p then
tmueller@129
  1016
			self.pubprofile = p
tmueller@0
  1017
			break
tmueller@0
  1018
		end
tmueller@0
  1019
	end
tmueller@0
  1020
	
tmueller@0
  1021
	-- get profile
tmueller@0
  1022
	
tmueller@143
  1023
	local checkprofile =
tmueller@149
  1024
		self.authuser and self.args.profile or self.pubprofile or "work"
tmueller@129
  1025
	for _, lang in ipairs(self.langs) do
tmueller@129
  1026
		if profiles[checkprofile .. "_" .. lang] then
tmueller@129
  1027
			self.profile = checkprofile
tmueller@129
  1028
			self.lang = lang
tmueller@0
  1029
			break
tmueller@0
  1030
		end
tmueller@0
  1031
	end
tmueller@0
  1032
	
tmueller@129
  1033
	assert(self.profile and self.lang, "Invalid profile or language")
tmueller@0
  1034
	
tmueller@47
  1035
	
tmueller@143
  1036
	self.ispubprofile = self.profile == self.pubprofile
tmueller@143
  1037
	
tmueller@129
  1038
	-- write back language and profile
tmueller@0
  1039
tmueller@129
  1040
	self.args.lang = self.lang ~= self.config.deflang and self.lang or nil
tmueller@129
  1041
	self.args.profile = self.profile
tmueller@0
  1042
	
tmueller@47
  1043
	
tmueller@0
  1044
	-- determine content directory pathname and section filename
tmueller@0
  1045
	
tmueller@143
  1046
	self.contentdir =
tmueller@143
  1047
		("%s/%s_%s"):format(self.config.contentdir, self.profile, self.lang)
tmueller@129
  1048
 	self.indexfname = self.contentdir .. "/.sections"
tmueller@0
  1049
	
tmueller@0
  1050
	-- load sections
tmueller@0
  1051
	
tmueller@129
  1052
 	self.sections = tek.source(self.indexfname)
tmueller@0
  1053
	
tmueller@20
  1054
	-- index sections, determine visibility in menu
tmueller@0
  1055
	
tmueller@129
  1056
	self:indexsections()
tmueller@0
  1057
	
tmueller@129
  1058
	-- decompose request path, produce a stack of sections
tmueller@0
  1059
	
tmueller@129
  1060
	self.submenus, self.section = self:getsection(self.requestpath)
tmueller@0
  1061
tmueller@0
  1062
	-- handle redirects if not logged on
tmueller@0
  1063
	
tmueller@129
  1064
	if not self.authuser and self.section and self.section.redirect then
tmueller@129
  1065
		self.submenus, self.section = self:getsection(self.section.redirect)
tmueller@0
  1066
	end
tmueller@0
  1067
			
tmueller@0
  1068
	-- section path and document name (refined)
tmueller@0
  1069
	
tmueller@129
  1070
	self.sectionpath = self:getpath()
tmueller@129
  1071
	self.sectionname = self:getpath("_")
tmueller@0
  1072
tmueller@0
  1073
end
tmueller@0
  1074
tmueller@0
  1075
tmueller@129
  1076
function loona:handlechanges()
tmueller@129
  1077
	
tmueller@129
  1078
	local save
tmueller@0
  1079
tmueller@129
  1080
	if self.args.editkey == "main" then
tmueller@23
  1081
		
tmueller@23
  1082
		-- In main editable section:
tmueller@23
  1083
		
tmueller@129
  1084
		if self.args.actioncreate then
tmueller@129
  1085
			
tmueller@45
  1086
			-- Create new section
tmueller@129
  1087
			
tmueller@129
  1088
			local editname = self.args.editname:lower()
tmueller@20
  1089
			assert(not editname:match("%W"),
tmueller@47
  1090
				dbmsg("Invalid section name", editname))
tmueller@20
  1091
			if not (section and (section.subs or section)[editname]) then
tmueller@143
  1092
				local newpath = (self.sectionpath and 
tmueller@143
  1093
					(self.sectionpath .. "/")) .. editname
tmueller@129
  1094
				local s = self:addpath(newpath, { name = editname,
tmueller@143
  1095
					label = self.args.editlabel ~= "" and
tmueller@143
  1096
						self.args.editlabel or nil,
tmueller@143
  1097
					title = self.args.edittitle ~= "" and
tmueller@143
  1098
						self.args.edittitle or nil,
tmueller@143
  1099
					redirect = self.args.editredirect ~= "" and
tmueller@143
  1100
						self.args.editredirect or nil,
tmueller@129
  1101
					hidden = self.args.editvisibility and true,
tmueller@129
  1102
					secret = self.args.editsecrecy and true,
tmueller@129
  1103
					secure = self.args.editsecure and true,
tmueller@129
  1104
					creator = self.authuser,
tmueller@20
  1105
					creationdate = time() })
tmueller@23
  1106
				save = true
tmueller@0
  1107
			end
tmueller@20
  1108
		
tmueller@129
  1109
		elseif self.args.actionsave then
tmueller@129
  1110
			
tmueller@45
  1111
			-- Save section
tmueller@129
  1112
			
tmueller@129
  1113
			self.section.revisiondate = time()
tmueller@129
  1114
			self.section.revisioner = self.authuser
tmueller@129
  1115
			save = true
tmueller@129
  1116
 		
tmueller@129
  1117
		elseif self.args.actionsaveprops then
tmueller@129
  1118
			
tmueller@129
  1119
			-- Save properties
tmueller@129
  1120
			
tmueller@129
  1121
			self.section.hidden = self.args.editvisibility and true
tmueller@129
  1122
			self.section.secret = self.args.editsecrecy and true
tmueller@129
  1123
			self.section.secure = self.args.editsecure and true
tmueller@143
  1124
			self.section.label = self.args.editlabel ~= "" and
tmueller@143
  1125
				self.args.editlabel or nil
tmueller@143
  1126
			self.section.title = self.args.edittitle ~= "" and
tmueller@143
  1127
				self.args.edittitle or nil
tmueller@129
  1128
			self.section.redirect =
tmueller@129
  1129
				self.args.editredirect ~= "" and self.args.editredirect or nil
tmueller@23
  1130
			save = true
tmueller@20
  1131
		
tmueller@129
  1132
		elseif self.args.actionup then
tmueller@129
  1133
			
tmueller@45
  1134
			-- Move section up
tmueller@129
  1135
			
tmueller@129
  1136
			local t, i = self:checkpath(self.sectionpath)
tmueller@0
  1137
			if t and i > 1 then
tmueller@143
  1138
				if self.ispubprofile and not self.args.actionconfirm then
tmueller@115
  1139
					useralert = {
tmueller@144
  1140
						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
tmueller@115
  1141
						confirm =
tmueller@144
  1142
							'<input type="submit" name="actionup" value="' ..
tmueller@129
  1143
							self.locale.MOVE .. '" /> ' ..
tmueller@129
  1144
							self:hidden("actionconfirm", "true")
tmueller@115
  1145
					}
tmueller@115
  1146
				else
tmueller@115
  1147
					local item = table.remove(t, i)
tmueller@115
  1148
					table.insert(t, i - 1, item)
tmueller@115
  1149
					save = true
tmueller@115
  1150
				end
tmueller@0
  1151
			end
tmueller@20
  1152
		
tmueller@129
  1153
		elseif self.args.actiondown then
tmueller@129
  1154
			
tmueller@45
  1155
			-- Move section down
tmueller@129
  1156
			
tmueller@129
  1157
			local t, i = self:checkpath(self.sectionpath)
tmueller@0
  1158
			if t and i < #t then
tmueller@143
  1159
				if self.ispubprofile and not self.args.actionconfirm then
tmueller@115
  1160
					useralert = {
tmueller@144
  1161
						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
tmueller@115
  1162
						confirm =
tmueller@144
  1163
							'<input type="submit" name="actiondown" value="' ..
tmueller@129
  1164
							self.locale.MOVE .. '" /> ' ..
tmueller@129
  1165
							self:hidden("actionconfirm", "true")
tmueller@115
  1166
					}
tmueller@115
  1167
				else
tmueller@115
  1168
					local item = table.remove(t, i)
tmueller@115
  1169
					table.insert(t, i + 1, item)
tmueller@115
  1170
					save = true
tmueller@115
  1171
				end
tmueller@0
  1172
			end
tmueller@20
  1173
		
tmueller@129
  1174
		elseif self.args.actioncreateprofile and self.args.createprofile then
tmueller@129
  1175
			
tmueller@23
  1176
			-- Create profile
tmueller@129
  1177
			
tmueller@129
  1178
			local c = self:checkprofilename(self.args.createprofile:lower())
tmueller@0
  1179
			if c == profile then
tmueller@144
  1180
				useralert = { 
tmueller@144
  1181
					text = self.locale.ALERT_CANNOT_COPY_PROFILE_TO_SELF
tmueller@144
  1182
				}
tmueller@0
  1183
			else
tmueller@129
  1184
				local profiles = self:getprofiles()
tmueller@129
  1185
				if profiles[c] and not self.args.actionconfirm then
tmueller@20
  1186
					useralert = {
tmueller@129
  1187
						text = c == self.pubprofile and 
tmueller@129
  1188
							self.locale.ALERT_OVERWRITE_PUBLISHED_PROFILE or
tmueller@129
  1189
							self.locale.ALERT_OVERWRITE_EXISTING_PROFILE,
tmueller@144
  1190
						confirm = '<input type="submit" ' ..
tmueller@144
  1191
							'name="actioncreateprofile" value="' ..
tmueller@129
  1192
							self.locale.OVERWRITE .. '" /> ' ..
tmueller@144
  1193
							self:hidden("actionconfirm", "true") ..
tmueller@129
  1194
							self:hidden("createprofile", c)
tmueller@20
  1195
					}
tmueller@0
  1196
				else
tmueller@0
  1197
					if profiles[c] then
tmueller@129
  1198
						self:deleteprofile(c)
tmueller@0
  1199
					end
tmueller@129
  1200
					self:copyprofile(c)
tmueller@0
  1201
				end
tmueller@0
  1202
			end
tmueller@20
  1203
		
tmueller@129
  1204
		elseif self.args.actiondeleteprofile and self.args.deleteprofile then
tmueller@129
  1205
			
tmueller@23
  1206
			-- Delete profile
tmueller@129
  1207
			
tmueller@129
  1208
			local c = self:checkprofilename(self.args.deleteprofile:lower())
tmueller@144
  1209
			assert(c ~= self.pubprofile,
tmueller@144
  1210
				self:dbmsg("Cannot delete published profile", c))
tmueller@129
  1211
			if self.args.actionconfirm then
tmueller@129
  1212
				self:deleteprofile(c)
tmueller@129
  1213
				self.profile = nil
tmueller@129
  1214
				self.args.profile = nil
tmueller@129
  1215
				self:init()
tmueller@23
  1216
				save = true
tmueller@0
  1217
			else
tmueller@20
  1218
				useralert = { 
tmueller@129
  1219
					text = self.locale.ALERT_DELETE_PROFILE,
tmueller@144
  1220
					confirm = '<input type="submit" ' ..
tmueller@144
  1221
						'name="actiondeleteprofile" value="' .. 
tmueller@129
  1222
						self.locale.DELETE .. '" /> ' ..
tmueller@129
  1223
						self:hidden("actionconfirm", "true") ..
tmueller@129
  1224
						self:hidden("deleteprofile", c)
tmueller@20
  1225
				}
tmueller@0
  1226
			end
tmueller@20
  1227
		
tmueller@129
  1228
		elseif self.args.actionchangeprofile and self.args.changeprofile then
tmueller@129
  1229
			
tmueller@23
  1230
			-- Change profile
tmueller@129
  1231
			
tmueller@129
  1232
			local c = self:checkprofilename(self.args.changeprofile:lower())
tmueller@129
  1233
			self.profile = c
tmueller@129
  1234
			self.args.profile = c
tmueller@23
  1235
			save = true
tmueller@20
  1236
		
tmueller@129
  1237
		elseif self.args.actionpublishprofile and self.args.publishprofile then
tmueller@129
  1238
			
tmueller@23
  1239
			-- Publish profile
tmueller@129
  1240
			
tmueller@129
  1241
			local c = self:checkprofilename(self.args.publishprofile:lower())
tmueller@129
  1242
			if c ~= self.publicprofile then
tmueller@129
  1243
				if self.args.actionconfirm then
tmueller@129
  1244
					self:publishprofile(c)
tmueller@23
  1245
					save = true
tmueller@0
  1246
				else
tmueller@20
  1247
					useralert = {
tmueller@129
  1248
						text = self.locale.ALERT_PUBLISH_PROFILE,
tmueller@144
  1249
						confirm = '<input type="submit" ' ..
tmueller@144
  1250
							'name="actionpublishprofile" value="' ..
tmueller@129
  1251
							self.locale.PUBLISH .. '" /> ' ..
tmueller@129
  1252
							self:hidden("actionconfirm", "true") ..
tmueller@129
  1253
							self:hidden("publishprofile", c)
tmueller@20
  1254
					}
tmueller@20
  1255
				end
tmueller@20
  1256
			end
tmueller@0
  1257
		end
tmueller@0
  1258
		
tmueller@129
  1259
	end
tmueller@0
  1260
	
tmueller@129
  1261
	if self.args.actiondelete then
tmueller@129
  1262
		
tmueller@129
  1263
		-- Delete section
tmueller@129
  1264
		
tmueller@129
  1265
		if not self.args.actionconfirm then
tmueller@129
  1266
			useralert = {
tmueller@143
  1267
				text = self.ispubprofile and
tmueller@144
  1268
					self.locale.ALERT_DELETE_IN_PUBLISHED_PROFILE or
tmueller@129
  1269
					self.locale.ALERT_DELETE_SECTION,
tmueller@129
  1270
				confirm =
tmueller@129
  1271
					'<input type="submit" name="actiondelete" value="' .. 
tmueller@129
  1272
					self.locale.DELETE .. '" /> ' ..
tmueller@129
  1273
					self:hidden("actionconfirm", "true")
tmueller@129
  1274
			}
tmueller@129
  1275
		else
tmueller@129
  1276
			local key = self.args.editkey
tmueller@129
  1277
			if key == "main" and not self.section.subs then
tmueller@129
  1278
				self:deletesection(self.sectionname, true) -- all bodies
tmueller@129
  1279
				self:rmpath(self.sectionpath) -- and node
tmueller@129
  1280
			else
tmueller@129
  1281
				local ext = (key == "main" and "") or "." .. key
tmueller@129
  1282
				self:deletesection(self.sectionname .. ext) -- only text
tmueller@129
  1283
				if self.section.dynamic then
tmueller@129
  1284
					self.section.dynamic[key] = nil
tmueller@131
  1285
					local n = 0
tmueller@131
  1286
					for _ in pairs(self.section.dynamic) do
tmueller@131
  1287
						n = n + 1
tmueller@131
  1288
					end
tmueller@131
  1289
					if n == 0 then
tmueller@131
  1290
						self.section.dynamic = nil
tmueller@131
  1291
					end
tmueller@129
  1292
				end
tmueller@129
  1293
			end
tmueller@129
  1294
			save = true
tmueller@0
  1295
		end
tmueller@0
  1296
	end
tmueller@129
  1297
		
tmueller@129
  1298
	if save then
tmueller@129
  1299
		self:saveindex()
tmueller@129
  1300
		self:init()
tmueller@129
  1301
	end
tmueller@129
  1302
	
tmueller@0
  1303
end
tmueller@0
  1304
tmueller@0
  1305
tmueller@129
  1306
function loona:encodeform(s)
tmueller@129
  1307
	return cgi.encodeform(s)
tmueller@129
  1308
end
tmueller@0
  1309
tmueller@80
  1310
tmueller@129
  1311
function loona:loadhtml(src, outfunc, chunkname)
tmueller@129
  1312
	return tek.web.include.load(src, outfunc, chunkname)
tmueller@129
  1313
end
tmueller@80
  1314
tmueller@80
  1315
tmueller@129
  1316
function loona:domarkup(s)
tmueller@129
  1317
	return tek.web.markup.main(s)
tmueller@129
  1318
end
tmueller@0
  1319
tmueller@0
  1320
tmueller@129
  1321
function loona:expire(dir, pat, maxage)
tmueller@129
  1322
	return tek.util.expire(dir, pat, maxage)
tmueller@129
  1323
end
tmueller@129
  1324
tmueller@23
  1325
tmueller@129
  1326
function loona:new(o)
tmueller@0
  1327
tmueller@129
  1328
	local parsed, msg
tmueller@129
  1329
	
tmueller@129
  1330
	o = o or { }
tmueller@129
  1331
	o = atom.new(self, o)
tmueller@129
  1332
	
tmueller@129
  1333
	o.out = o.out or function(self, s) tek.web.out(s) end
tmueller@129
  1334
	o.setheader = o.setheader or function(self, s) tek.web.setheader(s) end
tmueller@129
  1335
	
tmueller@129
  1336
	-- Get configuration
tmueller@129
  1337
	
tmueller@129
  1338
	o.config = o.config or tek.source(o.conffile or "../etc/config.lua") or { }
tmueller@129
  1339
	o.config.defname = o.config.defname or "home"
tmueller@129
  1340
	o.config.deflang = o.config.deflang or "en"
tmueller@129
  1341
	o.config.sessionmaxage = o.config.sessionmaxage or 600
tmueller@129
  1342
	o.config.secureport = o.config.secureport or 443
tmueller@144
  1343
	o.config.passwdfile =
tmueller@144
  1344
		posix.abspath(o.config.passwdfile or "../etc/passwd.lua")
tmueller@144
  1345
	o.config.sessiondir =
tmueller@144
  1346
		posix.abspath(o.config.sessiondir or "../var/sessions")
tmueller@129
  1347
	o.config.extdir = posix.abspath(o.config.extdir or "../extensions")
tmueller@129
  1348
	o.config.contentdir = posix.abspath(o.config.contentdir or "../content")
tmueller@129
  1349
	o.config.localedir = posix.abspath(o.config.localedir or "../locale")
tmueller@129
  1350
	o.config.htdocsdir = posix.abspath(o.config.htdocsdir or "../htdocs")
tmueller@144
  1351
	o.config.htmlcachedir = 
tmueller@144
  1352
		posix.abspath(o.config.htmlcachedir or "../var/htmlcache")
tmueller@129
  1353
	
tmueller@129
  1354
	-- Create proxy for on-demand loading of locales
tmueller@129
  1355
	
tmueller@129
  1356
	o.locale = { }
tmueller@129
  1357
	local locmt = { }
tmueller@129
  1358
	locmt.__index = function(_, key)
tmueller@129
  1359
		for _, l in ipairs(o.langs) do
tmueller@129
  1360
			locmt.__locale = tek.source(o.config.localedir .. "/" .. l)
tmueller@129
  1361
			if locmt.__locale then
tmueller@129
  1362
				break
tmueller@129
  1363
			end
tmueller@129
  1364
		end
tmueller@129
  1365
		locmt.__index = function(tab, key)
tmueller@129
  1366
			return locmt.__locale[key] or key
tmueller@129
  1367
		end
tmueller@129
  1368
		return locmt.__locale[key] or key
tmueller@129
  1369
	end
tmueller@129
  1370
	setmetatable(o.locale, locmt)
tmueller@129
  1371
	
tmueller@129
  1372
	-- Get request, args, document, script name, request path
tmueller@129
  1373
	
tmueller@129
  1374
	o.request = o.request or cgi.request
tmueller@129
  1375
	o.args = o.args or cgi.request.args
tmueller@129
  1376
	o.session = o.session or cgi.session
tmueller@129
  1377
 	
tmueller@129
  1378
 	o.scriptpath = o.scriptpath or cgi.document.Path
tmueller@129
  1379
	o.requesthandler = o.requesthandler or cgi.document.Handler
tmueller@129
  1380
 	o.requestdocument = o.requestdocument or cgi.document.Name
tmueller@129
  1381
	o.requestpath = o.requestpath or cgi.document.VirtualPath
tmueller@129
  1382
tmueller@129
  1383
	-- Manage login and establish session
tmueller@129
  1384
	
tmueller@129
  1385
	o.session.init(o.config.sessiondir, o.args.session, o.config.sessionmaxage)
tmueller@129
  1386
	if o.args.login then
tmueller@129
  1387
		if o.args.login == "false" then
tmueller@129
  1388
			o.session.delete()
tmueller@129
  1389
			o.session = nil
tmueller@129
  1390
		elseif o.args.password then
tmueller@129
  1391
			o.loginfailed = true
tmueller@129
  1392
			local pwddb = tek.source(o.config.passwdfile)
tmueller@129
  1393
			local pwdentry = pwddb[o.args.login]
tmueller@129
  1394
			if pwdentry and pwdentry.password == o.args.password then
tmueller@129
  1395
				o.session.data.authuser = pwdentry.username
tmueller@129
  1396
				o.session.data.id = o.session.id
tmueller@129
  1397
				o.loginfailed = nil
tmueller@129
  1398
			end
tmueller@129
  1399
		end
tmueller@129
  1400
	end
tmueller@129
  1401
	
tmueller@150
  1402
	o.secure = not o.insecure and (o.request.Port == o.config.secureport)
tmueller@129
  1403
	o.authuser = o.session and o.session.data.authuser
tmueller@129
  1404
	
tmueller@129
  1405
	if o.nologin or not o.authuser then
tmueller@129
  1406
		o.authuser = nil
tmueller@129
  1407
		o.session = nil
tmueller@129
  1408
	end
tmueller@129
  1409
	
tmueller@129
  1410
	-- Get lang, locale, profile, section
tmueller@129
  1411
	
tmueller@129
  1412
	o:init()
tmueller@129
  1413
	if o.authuser then
tmueller@129
  1414
		o:handlechanges()
tmueller@129
  1415
	end
tmueller@129
  1416
	
tmueller@129
  1417
	-- Current document
tmueller@129
  1418
	
tmueller@129
  1419
	o.document = o.requestdocument .. "/" .. o.sectionpath
tmueller@129
  1420
	if o.authuser then
tmueller@129
  1421
		o.getdocname = function(self, path)
tmueller@144
  1422
			return path and self.requestdocument .. "/" .. path or
tmueller@144
  1423
				self.requestdocument
tmueller@129
  1424
		end
tmueller@129
  1425
	else
tmueller@129
  1426
		o.getdocname = function(self, path)
tmueller@146
  1427
			local dyn
tmueller@129
  1428
			path = path or self.config.defname
tmueller@146
  1429
			dyn, path = self:isdynamic(path)
tmueller@146
  1430
			if dyn then
tmueller@129
  1431
				return self.requestdocument .. "/" .. path
tmueller@129
  1432
			end
tmueller@129
  1433
			path = path == self.config.defname and "index" or path
tmueller@129
  1434
			return "/" .. path:gsub("/", "_") .. ".html"
tmueller@129
  1435
		end
tmueller@129
  1436
	end
tmueller@129
  1437
	
tmueller@129
  1438
	-- Save session state
tmueller@129
  1439
	
tmueller@129
  1440
	if o.session then
tmueller@129
  1441
		o.session.save()
tmueller@129
  1442
	end
tmueller@129
  1443
tmueller@129
  1444
	return o
tmueller@0
  1445
end
tmueller@0
  1446
tmueller@23
  1447
tmueller@129
  1448
function loona:execute(fname)
tmueller@141
  1449
	self:indexdynamic()
tmueller@129
  1450
	fname = fname or self.requesthandler
tmueller@129
  1451
	local parsed, msg = self:loadhtml(open(fname), "loona:out", fname)
tmueller@129
  1452
	assert(parsed, self:dbmsg("HTML/Lua parsing failed", msg))
tmueller@129
  1453
	self:runboxed(parsed)
tmueller@129
  1454
	return self
tmueller@129
  1455
end
tmueller@80
  1456
tmueller@80
  1457
tmueller@129
  1458
function loona:indexdynamic()
tmueller@129
  1459
	self:recursesections(self.sections, function(self, s, e, path, dynamic)
tmueller@129
  1460
		path = path and path .. "_" .. e.name or e.name
tmueller@129
  1461
		dynamic = dynamic or { }
tmueller@129
  1462
		for k in pairs(e.dynamic or { }) do
tmueller@129
  1463
			dynamic[k] = true
tmueller@129
  1464
		end
tmueller@129
  1465
		for k in pairs(dynamic) do
tmueller@129
  1466
			local ext = (k == "main" and "") or "." .. k
tmueller@144
  1467
			if posix.stat(self.contentdir .. "/" .. path .. ext,
tmueller@144
  1468
				"mode") == "file" then
tmueller@129
  1469
				dynamic[k] = e.dynamic and e.dynamic[k]
tmueller@129
  1470
			end
tmueller@129
  1471
		end
tmueller@129
  1472
		local n = 0
tmueller@129
  1473
		for k in pairs(dynamic) do
tmueller@129
  1474
			n = n + 1
tmueller@129
  1475
		end
tmueller@129
  1476
		if n > 0 then
tmueller@129
  1477
			e.dynamic = { }
tmueller@129
  1478
			for k in pairs(dynamic) do
tmueller@129
  1479
				e.dynamic[k] = true
tmueller@129
  1480
			end
tmueller@129
  1481
		else
tmueller@129
  1482
			e.dynamic = nil
tmueller@129
  1483
		end
tmueller@129
  1484
		return path, dynamic
tmueller@129
  1485
	end)
tmueller@129
  1486
end
tmueller@80
  1487
tmueller@0
  1488
tmueller@129
  1489
function loona:isdynamic(path)
tmueller@129
  1490
	path = path or self.sectionpath
tmueller@129
  1491
	local t, i = self:checkpath(path)
tmueller@146
  1492
	if t and t[i].redirect then
tmueller@146
  1493
		path = t[i].redirect
tmueller@146
  1494
		t, i = self:isdynamic(path) -- TODO: prohibit endless recursion
tmueller@146
  1495
	end
tmueller@146
  1496
	return t and t[i].dynamic, path
tmueller@129
  1497
end
tmueller@0
  1498
tmueller@0
  1499
tmueller@129
  1500
function loona:dumphtml(o)
tmueller@129
  1501
	local outbuf = { }
tmueller@129
  1502
	o = o or { }
tmueller@129
  1503
	o.nologin = true
tmueller@129
  1504
	o.out = function(self, s) table.insert(outbuf, s) end
tmueller@129
  1505
	o.setheader = function(self, s) end
tmueller@129
  1506
	o = self:new(o):execute()
tmueller@129
  1507
	if not o:isdynamic() then
tmueller@129
  1508
		local path = o.sectionname
tmueller@129
  1509
		path = path == o.config.defname and "index" or path
tmueller@141
  1510
		local srcname = o.config.htdocsdir .. "/" .. path .. o.htmlext
tmueller@131
  1511
		local fh, msg = open(srcname .. ".tmp", "wb")
tmueller@131
  1512
		assert(fh, self:dbmsg("Could not write cached HTML", msg))
tmueller@129
  1513
		fh:write(unpack(outbuf))
tmueller@129
  1514
		fh:close()
tmueller@144
  1515
		local dstname = o.config.htmlcachedir .. "/" .. path .. o.htmlext
tmueller@144
  1516
		local success, msg = posix.symlink(srcname, dstname .. ".tmp")
tmueller@137
  1517
-- 		assert(success, self:dbmsg("Could not link to cached HTML", msg))
tmueller@0
  1518
	end
tmueller@0
  1519
end