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