cgi-bin/loona.lua
author Timm S. Mueller <tmueller@neoscientists.org>
Sun, 18 Feb 2007 05:30:10 +0100
changeset 47 1ef2ca48fea3
parent 45 928bed838f2f
child 68 53354bfe4769
permissions -rw-r--r--
Improved performance, added locale proxy
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@0
     8
require "tek"
tmueller@0
     9
require "tek.cgi"
tmueller@0
    10
require "tek.cgi.request"
tmueller@0
    11
require "tek.cgi.request.args"
tmueller@0
    12
require "tek.cgi.header"
tmueller@0
    13
require "tek.cgi.session"
tmueller@0
    14
require "tek.posix"
tmueller@0
    15
require "tek.web"
tmueller@0
    16
require "tek.web.markup"
tmueller@0
    17
require "tek.util"
tmueller@0
    18
tmueller@23
    19
tmueller@0
    20
local boxed_G = { 
tmueller@0
    21
	string = string, table = table,
tmueller@0
    22
	assert = assert, collectgarbage = collectgarbage, dofile = dofile,
tmueller@0
    23
	error = error, getfenv = getfenv, getmetatable = getmetatable,
tmueller@0
    24
	ipairs = ipairs, load = load, loadfile = loadfile, loadstring = loadstring,
tmueller@0
    25
	next = next, pairs = pairs, pcall = pcall, print = print,
tmueller@0
    26
	rawequal = rawequal, rawget = rawget, rawset = rawset, require = require,
tmueller@0
    27
	select = select, setfenv = setfenv, setmetatable = setmetatable,
tmueller@0
    28
	tonumber = tonumber, tostring = tostring, type = type, unpack = unpack,
tmueller@0
    29
	xpcall = xpcall
tmueller@0
    30
}
tmueller@0
    31
tmueller@0
    32
local tek, table, string, assert, unpack, ipairs, pairs, type, require =
tmueller@0
    33
	tek, table, string, assert, unpack, ipairs, pairs, type, require
tmueller@0
    34
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv
tmueller@0
    35
local open, remove, rename, getenv, time =
tmueller@0
    36
	io.open, os.remove, os.rename, os.getenv, os.time
tmueller@0
    37
tmueller@47
    38
local sectionfname, langs
tmueller@0
    39
tmueller@0
    40
tmueller@0
    41
module "loona"
tmueller@0
    42
tmueller@0
    43
tmueller@0
    44
_VERSION = 2
tmueller@0
    45
_REVISION = 0
tmueller@0
    46
tmueller@0
    47
tmueller@0
    48
out = tek.web.out
tmueller@0
    49
setheader = tek.web.setheader
tmueller@0
    50
cgi = tek.cgi
tmueller@0
    51
session = cgi.session
tmueller@0
    52
request = cgi.request
tmueller@0
    53
args = request.args
tmueller@0
    54
posix = tek.posix
tmueller@0
    55
encodeform = cgi.encodeform
tmueller@0
    56
loadhtml = tek.web.include.load
tmueller@0
    57
source = tek.source
tmueller@0
    58
domarkup = tek.web.markup.main
tmueller@0
    59
expire = tek.util.expire
tmueller@0
    60
tmueller@0
    61
tmueller@20
    62
local function dbmsg(msg, detail)
tmueller@47
    63
	return (msg and detail and config.debug == true) and
tmueller@47
    64
		("%s : %s"):format(msg, detail) or msg
tmueller@20
    65
end
tmueller@20
    66
tmueller@20
    67
tmueller@0
    68
local function checkprofilename(c)
tmueller@47
    69
	assert(c:match("^%w+$") and c ~= "current",
tmueller@47
    70
		dbmsg("Invalid profile name", c))
tmueller@0
    71
	return c
tmueller@0
    72
end
tmueller@0
    73
tmueller@0
    74
tmueller@47
    75
local function checkbodyname(s)
tmueller@0
    76
	s = s or "main"
tmueller@47
    77
	assert(s:match("^[%w_]*%w+[%w_]*$"), dbmsg("Invalid body name", s))
tmueller@0
    78
	return s
tmueller@0
    79
end
tmueller@0
    80
tmueller@0
    81
tmueller@0
    82
local function deletedir(dst)
tmueller@0
    83
	for e in tek.util.readdir(dst) do
tmueller@47
    84
 		local success, msg = remove(dst .. "/" .. e)
tmueller@20
    85
		assert(success, dbmsg("Error removing entry in profile", msg))
tmueller@0
    86
	end
tmueller@0
    87
	return remove(dst)
tmueller@0
    88
end
tmueller@0
    89
tmueller@0
    90
tmueller@0
    91
local function copyprofile(contentdir, lang, srcprofile, newprofile)
tmueller@0
    92
	local src = contentdir .. "/" .. srcprofile .. "_" .. lang
tmueller@20
    93
	assert(posix.stat(src, "mode") == "directory",
tmueller@20
    94
		dbmsg("Not a directory", src))
tmueller@0
    95
	local dst = contentdir .. "/" .. newprofile .. "_" .. lang
tmueller@0
    96
	local success, msg = posix.mkdir(dst)
tmueller@20
    97
	assert(success, dbmsg("Error creating profile directory", msg))
tmueller@0
    98
	for e in tek.util.readdir(src) do
tmueller@0
    99
		local ext = e:match("^[^.].*%.([^.]*)$")
tmueller@0
   100
		if ext ~= "LOCK" then
tmueller@0
   101
			local f = src .. "/" .. e
tmueller@0
   102
			if posix.stat(f, "mode") == "file" then
tmueller@0
   103
				success, msg = tek.copyfile(f, dst .. "/" .. e)
tmueller@20
   104
				assert(success, dbmsg("Error copying file", msg))
tmueller@0
   105
			end
tmueller@0
   106
		end
tmueller@0
   107
	end
tmueller@0
   108
end
tmueller@0
   109
tmueller@0
   110
tmueller@0
   111
local function publishprofile(contentdir, lang, profile)
tmueller@0
   112
	local newpath = contentdir .. "/current_" .. lang
tmueller@23
   113
	local tmppath = newpath .. "." .. session.sessionname
tmueller@23
   114
	local success, msg = posix.symlink(profile .. "_" .. lang, tmppath)
tmueller@20
   115
	assert(success, dbmsg("Cannot create symlink", msg))
tmueller@23
   116
	success, msg = rename(tmppath, newpath)
tmueller@23
   117
	assert(success, dbmsg("Cannot put symlink in place", msg))
tmueller@0
   118
end
tmueller@0
   119
tmueller@0
   120
tmueller@20
   121
local function lookupname(tab, val)
tmueller@0
   122
	if tab then
tmueller@0
   123
		for i, v in ipairs(tab) do
tmueller@20
   124
			if v.name == val then
tmueller@0
   125
				return i
tmueller@0
   126
			end
tmueller@0
   127
		end
tmueller@0
   128
	end
tmueller@0
   129
end
tmueller@0
   130
tmueller@0
   131
tmueller@20
   132
--	Index sections, determine accessibility and visibility in menu
tmueller@20
   133
tmueller@20
   134
local function indexsections(s)
tmueller@0
   135
	for _, e in ipairs(s) do
tmueller@0
   136
		if e.subs then
tmueller@20
   137
			indexsections(e.subs)
tmueller@0
   138
		end
tmueller@0
   139
		e.notvalid = (not secure and e.secure) or 
tmueller@0
   140
			(not authuser and e.secret) or nil
tmueller@0
   141
		e.notvisible = e.notvalid or not authuser and e.hidden or nil
tmueller@47
   142
		assert(type(e.name) == "string") -- TODO?
tmueller@16
   143
		s[e.name] = e
tmueller@0
   144
	end
tmueller@0
   145
end
tmueller@0
   146
tmueller@0
   147
tmueller@20
   148
--	Decompose section path into a stack of sections, returning only up to
tmueller@0
   149
--	the last valid element in the path. additionally returns the table of
tmueller@0
   150
--	the last section path element (or the default section)
tmueller@0
   151
tmueller@27
   152
local function getsection(section, authuser, path, default)
tmueller@27
   153
	local tab = { { entries = sections, name = default } }
tmueller@27
   154
	local ss = sections
tmueller@0
   155
	local sectionpath
tmueller@0
   156
	(path or default):gsub("(%w+)/?", function(a)
tmueller@27
   157
		if ss then
tmueller@27
   158
			local s = ss[a]
tmueller@20
   159
			if s and not s.notvalid then
tmueller@20
   160
				sectionpath = s
tmueller@0
   161
				tab[#tab].name = a
tmueller@27
   162
				ss = s.subs
tmueller@27
   163
				if ss then
tmueller@27
   164
					table.insert(tab, { entries = ss })
tmueller@0
   165
				end
tmueller@0
   166
			else
tmueller@27
   167
				ss = nil -- stop.
tmueller@0
   168
			end
tmueller@0
   169
		end
tmueller@0
   170
	end)
tmueller@0
   171
	if not section and not sectionpath then
tmueller@27
   172
		sectionpath = sections[default]
tmueller@0
   173
		if sectionpath then
tmueller@0
   174
			table.insert(tab, { entries = sectionpath.subs })
tmueller@0
   175
		end
tmueller@0
   176
	end
tmueller@0
   177
	return tab, sectionpath
tmueller@0
   178
end
tmueller@0
   179
tmueller@0
   180
tmueller@27
   181
local function getpath(menus, delimiter)
tmueller@0
   182
	local t = { }
tmueller@27
   183
	for _, menu in ipairs(menus) do
tmueller@0
   184
		if menu.name then
tmueller@0
   185
			table.insert(t, menu.name)
tmueller@0
   186
		end
tmueller@0
   187
	end
tmueller@0
   188
	return table.concat(t, delimiter or "/")
tmueller@0
   189
end
tmueller@0
   190
tmueller@0
   191
tmueller@20
   192
--	Descending into the sections table alongside the current path,
tmueller@20
   193
--	return the filename to include, defaulting to its parent value
tmueller@20
   194
--	(or the default specified)
tmueller@0
   195
tmueller@27
   196
local function getsectionfile(menus, path, ext, default)
tmueller@20
   197
	local t, val = { }
tmueller@27
   198
	for _, menu in ipairs(menus) do
tmueller@20
   199
		if menu.entries and menu.entries[menu.name] then
tmueller@20
   200
			table.insert(t, menu.name)
tmueller@20
   201
			local fn = table.concat(t, "_") .. ext
tmueller@0
   202
			if posix.stat(path .. "/" .. fn, "mode") == "file" then
tmueller@0
   203
				val = fn
tmueller@0
   204
			end
tmueller@0
   205
		end
tmueller@0
   206
	end
tmueller@0
   207
	return val or default
tmueller@0
   208
end
tmueller@0
   209
tmueller@0
   210
tmueller@45
   211
local function deletesection(dir, fname)
tmueller@0
   212
	local fullname = dir .. "/" .. fname
tmueller@0
   213
	local success, msg = remove(fullname)
tmueller@0
   214
	if success then
tmueller@0
   215
		local pat = "^" .. 
tmueller@0
   216
			fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
tmueller@0
   217
		for e in tek.util.readdir(dir) do
tmueller@0
   218
			if e:match(pat) then
tmueller@0
   219
				remove(dir .. "/" .. e)
tmueller@0
   220
			end
tmueller@0
   221
		end
tmueller@0
   222
	end
tmueller@0
   223
	return success, msg
tmueller@0
   224
end
tmueller@0
   225
tmueller@0
   226
tmueller@20
   227
--	Add element to path
tmueller@0
   228
tmueller@20
   229
local function addtopath(tab, path, e)
tmueller@0
   230
	path:gsub("(%w+)/?", function(a)
tmueller@20
   231
		if tab then
tmueller@20
   232
			local s = tab[a]
tmueller@20
   233
			if s then
tmueller@20
   234
				if not s.subs then
tmueller@20
   235
					s.subs = { }
tmueller@0
   236
				end
tmueller@20
   237
				tab = s.subs
tmueller@0
   238
			else
tmueller@20
   239
				table.insert(tab, e)
tmueller@20
   240
				tab[a] = e
tmueller@20
   241
 				tab = nil -- stop
tmueller@0
   242
			end
tmueller@0
   243
		end
tmueller@0
   244
	end)
tmueller@0
   245
end
tmueller@0
   246
tmueller@0
   247
tmueller@20
   248
--	Remove element from path
tmueller@0
   249
tmueller@20
   250
local function rmpath(tab, path)
tmueller@20
   251
	local parent
tmueller@0
   252
	path:gsub("(%w+)/?", function(a)
tmueller@20
   253
		if tab then
tmueller@20
   254
			local idx = lookupname(tab, a)
tmueller@20
   255
			if idx then
tmueller@20
   256
				if tab[idx].subs then
tmueller@20
   257
					parent = tab[idx]
tmueller@20
   258
					tab = tab[idx].subs
tmueller@0
   259
				else
tmueller@20
   260
					table.remove(tab, idx)
tmueller@43
   261
					tab[a] = nil
tmueller@20
   262
					if #tab == 0 and parent then
tmueller@20
   263
						parent.subs = nil
tmueller@0
   264
					end
tmueller@20
   265
					tab = nil
tmueller@0
   266
				end
tmueller@0
   267
			end
tmueller@0
   268
		end
tmueller@0
   269
	end)
tmueller@0
   270
end
tmueller@0
   271
tmueller@0
   272
tmueller@0
   273
-------------------------------------------------------------------------------
tmueller@0
   274
tmueller@0
   275
tmueller@8
   276
--	Produce page title
tmueller@8
   277
tmueller@8
   278
function title()
tmueller@8
   279
	return section and (section.title or section.label or section.name) or ""
tmueller@8
   280
end
tmueller@8
   281
tmueller@8
   282
tmueller@47
   283
--	Create proxy for on-demand loading of locale strings
tmueller@0
   284
tmueller@47
   285
locale = { }
tmueller@47
   286
local locmt = { }
tmueller@47
   287
locmt.__index = function(_, key)
tmueller@47
   288
	for _, l in ipairs(langs) do
tmueller@47
   289
		locmt.__locale = source(config.localedir .. "/" .. l)
tmueller@47
   290
		if locmt.__locale then
tmueller@47
   291
			break
tmueller@0
   292
		end
tmueller@0
   293
	end
tmueller@47
   294
	locmt.__index = locmt.__locale
tmueller@47
   295
	return locmt.__locale[key]
tmueller@0
   296
end
tmueller@47
   297
setmetatable(locale, locmt)
tmueller@0
   298
tmueller@0
   299
tmueller@0
   300
--	Find element in path
tmueller@0
   301
tmueller@0
   302
function checkpath(tab, path)
tmueller@0
   303
	local res, idx
tmueller@0
   304
	path:gsub("(%w+)/?", function(a)
tmueller@0
   305
		if tab then
tmueller@20
   306
			local i = lookupname(tab, a)
tmueller@0
   307
			if i then
tmueller@0
   308
				res, idx = tab, i
tmueller@0
   309
				tab = tab[i].subs
tmueller@0
   310
			else
tmueller@0
   311
				res, idx = nil, nil
tmueller@0
   312
			end
tmueller@0
   313
		end
tmueller@0
   314
	end)
tmueller@0
   315
	return res, idx
tmueller@0
   316
end
tmueller@0
   317
tmueller@0
   318
tmueller@0
   319
--	Run a site function snippet, with full error recovery
tmueller@0
   320
--	(also recovers from errors in error handling function)
tmueller@0
   321
tmueller@23
   322
function dosnippet(config, func, errfunc)
tmueller@0
   323
	local ret = { tek.catch(func) }
tmueller@0
   324
	if ret[1] == 0 or (errfunc and tek.catch(errfunc) == 0) then
tmueller@0
   325
		return unpack(ret)
tmueller@0
   326
	end
tmueller@0
   327
	out("<h2>Error</h2>")
tmueller@14
   328
	out("<h3>" .. (ret[2] or "") .. "</h3>")
tmueller@23
   329
	if config.debug == true then
tmueller@0
   330
		if type(ret[3]) == "string" then
tmueller@14
   331
			out("<p>" .. ret[3] .. "</p>")
tmueller@0
   332
		end
tmueller@0
   333
		if ret[4] and config.debug == true then
tmueller@14
   334
			out("<pre>" .. ret[4] .. "</pre>")
tmueller@0
   335
		end
tmueller@0
   336
	end
tmueller@0
   337
end	
tmueller@0
   338
tmueller@0
   339
tmueller@0
   340
function lockfile(newfile)
tmueller@0
   341
	return not session and true or 
tmueller@0
   342
		posix.symlink(session.filename, newfile .. ".LOCK")
tmueller@0
   343
end
tmueller@0
   344
tmueller@0
   345
tmueller@0
   346
function unlockfile(dstfile)
tmueller@0
   347
	return not session and true or remove(dstfile .. ".LOCK")
tmueller@0
   348
end
tmueller@0
   349
tmueller@0
   350
tmueller@45
   351
function savesection(dir, fname, content)
tmueller@0
   352
	fname = dir .. "/" .. fname
tmueller@0
   353
	local f, msg = open(fname, "wb")
tmueller@20
   354
	assert(f, dbmsg("Could not open file for writing", msg))
tmueller@0
   355
	f:write(content or "")
tmueller@0
   356
	f:close()
tmueller@0
   357
end
tmueller@0
   358
tmueller@23
   359
tmueller@20
   360
--	Run extension in a controlled environment
tmueller@0
   361
tmueller@0
   362
function include(fname, ...)
tmueller@47
   363
	assert(not fname:match("%W"), dbmsg("Invalid include name", fname))
tmueller@47
   364
-- 	local fname2 = config.extdir .. "/" .. fname .. ".lua"
tmueller@47
   365
	local fname2 = ("%s/%s.lua"):format(config.extdir, fname)
tmueller@0
   366
	local f, msg = open(fname2)
tmueller@20
   367
	assert(f, dbmsg("Cannot open file", msg))
tmueller@0
   368
	local parsed, msg = loadhtml(f, "loona.out", fname2)
tmueller@20
   369
	assert(parsed, dbmsg("Syntax error", msg))
tmueller@0
   370
 	local fenv = {
tmueller@0
   371
 		arg = arg,
tmueller@0
   372
 		loona = {
tmueller@0
   373
 			out = out,
tmueller@0
   374
 			setheader = setheader,
tmueller@0
   375
			hidden = hidden,
tmueller@15
   376
			link = link,
tmueller@0
   377
			elink = elink,
tmueller@0
   378
			href = href,
tmueller@0
   379
			checkpath = checkpath,
tmueller@0
   380
			authuser = authuser,
tmueller@0
   381
			document = document,
tmueller@0
   382
			contentdir = contentdir,
tmueller@0
   383
			profile = profile,
tmueller@0
   384
			pubprofile = pubprofile,
tmueller@0
   385
			lang = lang,
tmueller@47
   386
			locale = locale,
tmueller@0
   387
			secure = secure,
tmueller@0
   388
			config = config,
tmueller@27
   389
			sectionpath = sectionpath,
tmueller@27
   390
			sections = sections,
tmueller@0
   391
			session = session,
tmueller@0
   392
		}
tmueller@0
   393
	}
tmueller@0
   394
 	setmetatable(fenv, { __index = boxed_G }) -- boxed global environment
tmueller@0
   395
	setfenv(parsed, fenv)
tmueller@0
   396
	return parsed()
tmueller@0
   397
end
tmueller@0
   398
tmueller@0
   399
tmueller@0
   400
function href(section, ...)
tmueller@0
   401
	local target = cgi.document.Name
tmueller@0
   402
	target = section and target .. "/" .. section or target
tmueller@47
   403
	if session or profile and profile ~= pubprofile then
tmueller@0
   404
		return tek.web.gethref(target, "lang", "profile", "session",
tmueller@0
   405
			unpack(arg))
tmueller@0
   406
	end
tmueller@0
   407
	return tek.web.gethref(target, "lang", unpack(arg))
tmueller@0
   408
end
tmueller@0
   409
tmueller@0
   410
tmueller@0
   411
function link(section, text, ...) -- normal link
tmueller@0
   412
	return '<a href="' .. href(section, unpack(arg)) .. '">' ..
tmueller@0
   413
		(text or section) .. '</a>'
tmueller@0
   414
end
tmueller@0
   415
tmueller@0
   416
tmueller@15
   417
function uilink(section, text, ...) -- active link
tmueller@15
   418
	return '<a class="loonaUILink" href="' .. href(section, unpack(arg)) ..
tmueller@0
   419
		'">' .. (text or section) .. '</a>'
tmueller@0
   420
end
tmueller@0
   421
tmueller@0
   422
tmueller@0
   423
function elink(target, text) -- external link
tmueller@15
   424
	return '<a class="loonaExtLink" href="' .. target .. 
tmueller@0
   425
		'" onclick="void(window.open(this.href, \'\', \'\')); return false;">'
tmueller@0
   426
		.. (text or target) .. '</a>'
tmueller@0
   427
end
tmueller@0
   428
tmueller@0
   429
tmueller@0
   430
function hidden(name, value)
tmueller@0
   431
	return not value and "" or 
tmueller@0
   432
		'<input type="hidden" name="' .. name .. '" value="' .. value .. '" />'
tmueller@0
   433
end
tmueller@0
   434
tmueller@0
   435
tmueller@0
   436
function getprofiles(contentdir, lang)
tmueller@0
   437
	local t = { }
tmueller@0
   438
	for f in tek.util.readdir(contentdir) do
tmueller@0
   439
		if posix.lstat(contentdir .. "/" .. f, "mode") == "directory" then
tmueller@0
   440
			local e = f:match("^(%w+)_" .. lang .. "$")
tmueller@0
   441
 			if e then
tmueller@0
   442
	 			t[e] = e
tmueller@0
   443
 	 		end
tmueller@0
   444
		end
tmueller@0
   445
	end
tmueller@0
   446
	return t
tmueller@0
   447
end
tmueller@0
   448
tmueller@0
   449
tmueller@0
   450
-- Init
tmueller@0
   451
tmueller@0
   452
local function init()
tmueller@0
   453
	
tmueller@0
   454
	-- get list of languages, in order of preference
tmueller@0
   455
	
tmueller@0
   456
	langs = { args.lang and args.lang:match("^%w+$") }
tmueller@0
   457
	if config.browserlang == true then
tmueller@0
   458
		local s = getenv("HTTP_ACCEPT_LANGUAGE")
tmueller@0
   459
		while s do
tmueller@0
   460
			local l, r = s:match("^([%w.=]+)[,;](.*)$")
tmueller@0
   461
			l = l or s
tmueller@0
   462
			s = r
tmueller@0
   463
			if l:match("^%w+$") then
tmueller@0
   464
				table.insert(langs, l)
tmueller@0
   465
			end
tmueller@0
   466
		end
tmueller@0
   467
	end
tmueller@0
   468
	table.insert(langs, config.deflang)
tmueller@0
   469
	
tmueller@0
   470
	-- get list of possible profiles
tmueller@0
   471
	
tmueller@0
   472
	local profiles = { }
tmueller@0
   473
	for e in tek.util.readdir(config.contentdir) do
tmueller@0
   474
		profiles[e] = e
tmueller@0
   475
	end
tmueller@0
   476
	
tmueller@0
   477
	-- get pubprofile
tmueller@0
   478
	
tmueller@0
   479
	for _, lang in ipairs(langs) do
tmueller@0
   480
		local p = posix.readlink(config.contentdir .. "/current_" .. lang)
tmueller@0
   481
		p = p and p:match("^(%w+)_" .. lang .. "$")
tmueller@0
   482
		if p then
tmueller@0
   483
			pubprofile = p
tmueller@0
   484
			break
tmueller@0
   485
		end
tmueller@0
   486
	end
tmueller@0
   487
	
tmueller@0
   488
	-- get profile
tmueller@0
   489
	
tmueller@0
   490
	local checkprofile = authuser and args.profile or pubprofile or "default"
tmueller@0
   491
	for _, l in ipairs(langs) do
tmueller@0
   492
		if profiles[checkprofile .. "_" .. l] then
tmueller@0
   493
			profile = checkprofile
tmueller@0
   494
			lang = l
tmueller@0
   495
			break
tmueller@0
   496
		end
tmueller@0
   497
	end
tmueller@0
   498
	
tmueller@20
   499
	assert(profile and lang, "Invalid profile or language")
tmueller@0
   500
	
tmueller@47
   501
	
tmueller@47
   502
	-- Write back language and profile
tmueller@0
   503
tmueller@0
   504
	args.lang = lang ~= config.deflang and lang or nil
tmueller@0
   505
	args.profile = profile
tmueller@0
   506
	
tmueller@47
   507
	
tmueller@0
   508
	-- determine content directory pathname and section filename
tmueller@0
   509
	
tmueller@0
   510
	contentdir = config.contentdir .. "/" .. profile .. "_" .. lang
tmueller@0
   511
	sectionfname = contentdir .. "/.sections"
tmueller@0
   512
	
tmueller@0
   513
	-- load sections
tmueller@0
   514
	
tmueller@27
   515
	sections = source(sectionfname)
tmueller@0
   516
	
tmueller@20
   517
	-- index sections, determine visibility in menu
tmueller@0
   518
	
tmueller@27
   519
	indexsections(sections)
tmueller@0
   520
	
tmueller@0
   521
	-- decompose section path, produce a stack of sections
tmueller@0
   522
	
tmueller@27
   523
	submenus, section = getsection(section, authuser, 
tmueller@0
   524
		cgi.document.VirtualPath or "", not authuser and config.defname)
tmueller@0
   525
tmueller@0
   526
	-- handle redirects if not logged on
tmueller@0
   527
	
tmueller@0
   528
	if not authuser and section and section.redirect then
tmueller@27
   529
		submenus, section = getsection(section, authuser, 
tmueller@0
   530
			section.redirect, not authuser and config.defname)
tmueller@0
   531
	end
tmueller@0
   532
			
tmueller@0
   533
	-- section path and document name (refined)
tmueller@0
   534
	
tmueller@0
   535
	sectionpath = getpath(submenus)
tmueller@0
   536
tmueller@0
   537
end
tmueller@0
   538
tmueller@0
   539
tmueller@20
   540
--	Handle state modifications (create/save/delete, profile management)
tmueller@0
   541
tmueller@0
   542
local function handlestate()
tmueller@23
   543
tmueller@0
   544
	if args.editkey == "main" then
tmueller@23
   545
		
tmueller@23
   546
		-- In main editable section:
tmueller@23
   547
		
tmueller@23
   548
		local save
tmueller@0
   549
		
tmueller@0
   550
		if args.actioncreate then
tmueller@45
   551
			-- Create new section
tmueller@0
   552
			local editname = args.editname:lower()
tmueller@20
   553
			assert(not editname:match("%W"),
tmueller@47
   554
				dbmsg("Invalid section name", editname))
tmueller@20
   555
			if not (section and (section.subs or section)[editname]) then
tmueller@20
   556
				local newpath = 
tmueller@20
   557
					(sectionpath and (sectionpath .. "/")) .. editname
tmueller@27
   558
				addtopath(sections, newpath, { name = editname,
tmueller@0
   559
					label = args.editlabel ~= "" and args.editlabel or nil,
tmueller@0
   560
					title = args.edittitle ~= "" and args.edittitle or nil,
tmueller@43
   561
					redirect = args.editredirect ~= "" and args.editredirect or nil,
tmueller@43
   562
					hidden = args.editvisibility and true,
tmueller@43
   563
					secret = args.editsecrecy and true,
tmueller@43
   564
					secure = args.editsecure and true,
tmueller@0
   565
					creator = authuser,
tmueller@20
   566
					creationdate = time() })
tmueller@23
   567
				save = true
tmueller@0
   568
			end
tmueller@20
   569
		
tmueller@0
   570
		elseif args.actionsave then
tmueller@45
   571
			-- Save section
tmueller@0
   572
			section.revisiondate = time()
tmueller@0
   573
			section.revisioner = authuser
tmueller@23
   574
			save = true
tmueller@20
   575
		
tmueller@0
   576
		elseif args.actiondelete then
tmueller@45
   577
			-- Delete section
tmueller@0
   578
			if not args.actionconfirm then
tmueller@20
   579
				useralert = {
tmueller@47
   580
					text = locale.ALERT_DELETE_SECTION,
tmueller@20
   581
					confirm =
tmueller@20
   582
						'<input type="submit" name="actiondelete" value="' .. 
tmueller@47
   583
						locale.DELETE .. '" /> ' ..
tmueller@20
   584
						hidden("actionconfirm", "true")
tmueller@20
   585
				}
tmueller@0
   586
			else
tmueller@45
   587
				deletesection(contentdir, sectionpath:gsub("/", "_"))
tmueller@27
   588
				rmpath(sections, sectionpath)
tmueller@23
   589
				save = true
tmueller@0
   590
			end
tmueller@20
   591
		
tmueller@0
   592
		elseif args.actionsaveprops then
tmueller@23
   593
			-- Save properties
tmueller@0
   594
			section.hidden = args.editvisibility and true
tmueller@0
   595
			section.secret = args.editsecrecy and true
tmueller@0
   596
			section.secure = args.editsecure and true
tmueller@0
   597
			section.label = args.editlabel ~= "" and args.editlabel or nil
tmueller@0
   598
			section.title = args.edittitle ~= "" and args.edittitle or nil
tmueller@20
   599
			section.redirect =
tmueller@20
   600
				args.editredirect ~= "" and args.editredirect or nil
tmueller@23
   601
			save = true
tmueller@20
   602
		
tmueller@0
   603
		elseif args.actionup then
tmueller@45
   604
			-- Move section up
tmueller@27
   605
			local t, i = checkpath(sections, sectionpath)
tmueller@0
   606
			if t and i > 1 then
tmueller@0
   607
				local item = table.remove(t, i)
tmueller@0
   608
				table.insert(t, i - 1, item)
tmueller@23
   609
				save = true
tmueller@0
   610
			end
tmueller@20
   611
		
tmueller@0
   612
		elseif args.actiondown then
tmueller@45
   613
			-- Move section down
tmueller@27
   614
			local t, i = checkpath(sections, sectionpath)
tmueller@0
   615
			if t and i < #t then
tmueller@0
   616
				local item = table.remove(t, i)
tmueller@0
   617
				table.insert(t, i + 1, item)
tmueller@23
   618
				save = true
tmueller@0
   619
			end
tmueller@20
   620
		
tmueller@0
   621
		elseif args.actioncreateprofile and args.createprofile then
tmueller@23
   622
			-- Create profile
tmueller@0
   623
			local c = checkprofilename(args.createprofile:lower())
tmueller@0
   624
			if c == profile then
tmueller@47
   625
				useralert = { text = locale.ALERT_CANNOT_COPY_PROFILE_TO_SELF }
tmueller@0
   626
			else
tmueller@0
   627
				local profiles = getprofiles(config.contentdir, lang)
tmueller@0
   628
				if profiles[c] and not args.actionconfirm then
tmueller@20
   629
					useralert = {
tmueller@20
   630
						text = c == pubprofile and 
tmueller@47
   631
							locale.ALERT_OVERWRITE_PUBLISHED_PROFILE or
tmueller@47
   632
							locale.ALERT_OVERWRITE_EXISTING_PROFILE,
tmueller@0
   633
						confirm =
tmueller@20
   634
							'<input type="submit" name="actioncreateprofile" value="' .. 
tmueller@47
   635
							locale.OVERWRITE .. '" /> ' ..
tmueller@20
   636
							hidden("actionconfirm", "true") .. 
tmueller@20
   637
							hidden("createprofile", c)
tmueller@20
   638
					}
tmueller@0
   639
				else
tmueller@0
   640
					if profiles[c] then
tmueller@0
   641
						deletedir(config.contentdir .. "/" .. c .. "_" .. lang)
tmueller@0
   642
					end
tmueller@0
   643
					copyprofile(config.contentdir, lang, profile, c)
tmueller@0
   644
				end
tmueller@0
   645
			end
tmueller@20
   646
		
tmueller@0
   647
		elseif args.actiondeleteprofile and args.deleteprofile then
tmueller@23
   648
			-- Delete profile
tmueller@0
   649
			local c = checkprofilename(args.deleteprofile:lower())
tmueller@47
   650
			assert(c ~= pubprofile, dbmsg("Cannot delete published profile", c))
tmueller@0
   651
			if args.actionconfirm then
tmueller@0
   652
				deletedir(config.contentdir .. "/" .. c .. "_" .. lang)
tmueller@0
   653
				profile = nil
tmueller@0
   654
				args.profile = nil
tmueller@23
   655
				init() -- get new sectionfname etc.
tmueller@23
   656
				save = true
tmueller@0
   657
			else
tmueller@20
   658
				useralert = { 
tmueller@47
   659
					text = locale.ALERT_DELETE_PROFILE,
tmueller@20
   660
					confirm = 
tmueller@20
   661
						'<input type="submit" name="actiondeleteprofile" value="' .. 
tmueller@47
   662
						locale.DELETE .. '" /> ' ..
tmueller@20
   663
						hidden("actionconfirm", "true") ..
tmueller@20
   664
						hidden("deleteprofile", c)
tmueller@20
   665
				}
tmueller@0
   666
			end
tmueller@20
   667
		
tmueller@0
   668
		elseif args.actionchangeprofile and args.changeprofile then
tmueller@23
   669
			-- Change profile
tmueller@0
   670
			local c = checkprofilename(args.changeprofile:lower())
tmueller@0
   671
			profile = c
tmueller@0
   672
			args.profile = c
tmueller@23
   673
			save = true
tmueller@20
   674
		
tmueller@0
   675
		elseif args.actionpublishprofile and args.publishprofile then
tmueller@23
   676
			-- Publish profile
tmueller@0
   677
			local c = checkprofilename(args.publishprofile:lower())
tmueller@0
   678
			if c ~= _publicprofile then
tmueller@0
   679
				if args.actionconfirm then
tmueller@0
   680
					publishprofile(config.contentdir, lang, c)
tmueller@23
   681
					save = true
tmueller@0
   682
				else
tmueller@20
   683
					useralert = {
tmueller@47
   684
						text = locale.ALERT_PUBLISH_PROFILE,
tmueller@20
   685
						confirm =
tmueller@20
   686
							'<input type="submit" name="actionpublishprofile" value="' ..
tmueller@47
   687
							locale.PUBLISH .. '" /> ' ..
tmueller@20
   688
							hidden("actionconfirm", "true") ..
tmueller@20
   689
							hidden("publishprofile", c)
tmueller@20
   690
					}
tmueller@20
   691
				end
tmueller@20
   692
			end
tmueller@0
   693
		end
tmueller@0
   694
		
tmueller@23
   695
		if save then
tmueller@23
   696
			-- Write sections atomically, reload
tmueller@23
   697
			local tempname = sectionfname .. "." .. session.sessionname
tmueller@0
   698
			local f, msg = open(tempname, "wb")
tmueller@20
   699
			assert(f, dbmsg("Error opening section file for writing", msg))
tmueller@27
   700
			tek.dump(sections, function(...)
tmueller@0
   701
				f:write(unpack(arg))
tmueller@0
   702
			end)
tmueller@0
   703
			f:close()
tmueller@0
   704
			local success, msg = rename(tempname, sectionfname)
tmueller@20
   705
			assert(success, dbmsg("Error renaming section file", msg))
tmueller@0
   706
			init()
tmueller@0
   707
		end
tmueller@0
   708
	
tmueller@47
   709
	elseif args.editkey and checkbodyname(args.editkey) then
tmueller@0
   710
		if args.actiondelete then
tmueller@45
   711
			-- Delete section in secondary editable body:
tmueller@45
   712
			deletesection(contentdir,
tmueller@20
   713
				sectionpath:gsub("/", "_") .. "." .. args.editkey)
tmueller@0
   714
		end
tmueller@0
   715
	end
tmueller@0
   716
end
tmueller@0
   717
tmueller@0
   718
tmueller@23
   719
--	Load configuration
tmueller@0
   720
tmueller@0
   721
config = source("../etc/config.lua") or { }
tmueller@20
   722
config.passwdfile = posix.abspath(config.passwdfile or "../etc/passwd.lua")
tmueller@0
   723
config.sessiondir = posix.abspath(config.sessiondir or "../var/sessions")
tmueller@0
   724
config.extdir = posix.abspath(config.extdir or "../extensions")
tmueller@20
   725
config.contentdir = posix.abspath(config.contentdir or "../content")
tmueller@20
   726
config.localedir = posix.abspath(config.localedir or "../locale")
tmueller@0
   727
config.defname = config.defname or "home"
tmueller@0
   728
config.deflang = config.deflang or "en"
tmueller@20
   729
config.sessionmaxage = config.sessionmaxage or 600
tmueller@0
   730
config.secureport = config.secureport or 443
tmueller@0
   731
tmueller@20
   732
tmueller@0
   733
--	manage login and establish session
tmueller@0
   734
tmueller@0
   735
session.init(config.sessiondir, args.session, config.sessionmaxage)
tmueller@0
   736
if args.login then
tmueller@0
   737
	if args.login == "false" then
tmueller@0
   738
		session.delete()
tmueller@0
   739
		session = nil
tmueller@0
   740
	elseif args.password then
tmueller@0
   741
		local pwddb = source(config.passwdfile)
tmueller@0
   742
		local pwdentry = pwddb[args.login]
tmueller@0
   743
		if pwdentry and pwdentry.password == args.password then
tmueller@0
   744
			session.data.authuser = pwdentry.username
tmueller@0
   745
			session.data.id = session.id
tmueller@0
   746
		end
tmueller@0
   747
	end
tmueller@0
   748
end
tmueller@0
   749
tmueller@20
   750
secure = cgi.request.Port == config.secureport
tmueller@20
   751
authuser = session and session.data.authuser
tmueller@20
   752
tmueller@20
   753
if not authuser then
tmueller@0
   754
	session = nil
tmueller@0
   755
	args.session = nil
tmueller@0
   756
end
tmueller@0
   757
tmueller@23
   758
tmueller@27
   759
-- get lang, locale, profile, section, handle modifications
tmueller@0
   760
tmueller@0
   761
init()
tmueller@0
   762
if authuser then
tmueller@0
   763
	handlestate()
tmueller@0
   764
end
tmueller@0
   765
tmueller@23
   766
tmueller@0
   767
-- current document
tmueller@20
   768
tmueller@47
   769
document = sectionpath and cgi.document.Name .. "/" .. sectionpath
tmueller@0
   770
tmueller@23
   771
tmueller@0
   772
-- get content filename from section path
tmueller@20
   773
tmueller@0
   774
local fname = sectionpath:gsub("/", "_")
tmueller@0
   775
tmueller@23
   776
tmueller@45
   777
-- add links for creating new sections
tmueller@20
   778
tmueller@0
   779
if authuser then
tmueller@20
   780
	local newent = { name = "new", 
tmueller@47
   781
		label = "[" .. locale.NEW .. "]",
tmueller@20
   782
		action = "actionnew=true" }
tmueller@0
   783
	for _, s in ipairs(submenus) do
tmueller@20
   784
		table.insert(s.entries, newent)
tmueller@0
   785
	end
tmueller@0
   786
	if submenus[#submenus].name then
tmueller@20
   787
		table.insert(submenus, {
tmueller@20
   788
			name = "new", 
tmueller@20
   789
			entries = { [1] = newent }
tmueller@20
   790
		})
tmueller@0
   791
	end
tmueller@0
   792
end
tmueller@0
   793
tmueller@23
   794
tmueller@0
   795
--	create section function
tmueller@0
   796
tmueller@0
   797
local func, msg = loadhtml(open("loona/editable.lua"),
tmueller@0
   798
	"tek.web.out", "loona/editable.lua")
tmueller@0
   799
tmueller@20
   800
assert(func, dbmsg("Syntax error", msg))
tmueller@0
   801
tmueller@0
   802
local editable = func()
tmueller@0
   803
tmueller@0
   804
function body(name)
tmueller@47
   805
	name = checkbodyname(name)
tmueller@0
   806
	if name == "main" then
tmueller@0
   807
		dosnippet(config, editable("main", contentdir, fname, fname))
tmueller@0
   808
	else
tmueller@0
   809
		local ext = "." .. name
tmueller@0
   810
		dosnippet(config, editable(name, contentdir,
tmueller@20
   811
			getsectionfile(submenus, contentdir, ext), fname .. ext))
tmueller@0
   812
	end
tmueller@0
   813
end
tmueller@0
   814
tmueller@23
   815
tmueller@0
   816
--	write back session state
tmueller@0
   817
tmueller@0
   818
if session then
tmueller@0
   819
	session.save()
tmueller@0
   820
end
tmueller@0
   821
tmueller@0
   822
tmueller@32
   823
-- do
tmueller@32
   824
-- 	local f = open("/tmp/foo", "wb")
tmueller@32
   825
-- 	tek.dump(getfenv(), function(s)
tmueller@32
   826
-- 		f:write(s)
tmueller@32
   827
-- 	end)
tmueller@32
   828
-- end
tmueller@32
   829
--