mirror of
				https://github.com/Ysurac/openmptcprouter-feeds.git
				synced 2025-03-09 15:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1942 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1942 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- Copyright 2008 Steven Barth <steven@midlink.org>
 | |
| -- Licensed to the public under the Apache License 2.0.
 | |
| 
 | |
| module("luci.cbi", package.seeall)
 | |
| 
 | |
| require("luci.template")
 | |
| local util = require("luci.util")
 | |
| require("luci.http")
 | |
| 
 | |
| 
 | |
| --local event      = require "luci.sys.event"
 | |
| local fs         = require("nixio.fs")
 | |
| local uci        = require("luci.model.uci")
 | |
| local datatypes  = require("luci.cbi.datatypes")
 | |
| local dispatcher = require("luci.dispatcher")
 | |
| local class      = util.class
 | |
| local instanceof = util.instanceof
 | |
| 
 | |
| FORM_NODATA  =  0
 | |
| FORM_PROCEED =  0
 | |
| FORM_VALID   =  1
 | |
| FORM_DONE	 =  1
 | |
| FORM_INVALID = -1
 | |
| FORM_CHANGED =  2
 | |
| FORM_SKIP    =  4
 | |
| 
 | |
| AUTO = true
 | |
| 
 | |
| CREATE_PREFIX = "cbi.cts."
 | |
| REMOVE_PREFIX = "cbi.rts."
 | |
| RESORT_PREFIX = "cbi.sts."
 | |
| FEXIST_PREFIX = "cbi.cbe."
 | |
| 
 | |
| -- Loads a CBI map from given file, creating an environment and returns it
 | |
| function load(cbimap, ...)
 | |
| 	local fs   = require "nixio.fs"
 | |
| 	local i18n = require "luci.i18n"
 | |
| 	require("luci.config")
 | |
| 	require("luci.util")
 | |
| 
 | |
| 	local upldir = "/etc/luci-uploads/"
 | |
| 	local cbidir = luci.util.libpath() .. "/model/cbi/"
 | |
| 	local func, err
 | |
| 
 | |
| 	if fs.access(cbidir..cbimap..".lua") then
 | |
| 		func, err = loadfile(cbidir..cbimap..".lua")
 | |
| 	elseif fs.access(cbimap) then
 | |
| 		func, err = loadfile(cbimap)
 | |
| 	else
 | |
| 		func, err = nil, "Model '" .. cbimap .. "' not found!"
 | |
| 	end
 | |
| 
 | |
| 	assert(func, err)
 | |
| 
 | |
| 	local env = {
 | |
| 		translate=i18n.translate,
 | |
| 		translatef=i18n.translatef,
 | |
| 	 	arg={...}
 | |
| 	}
 | |
| 
 | |
| 	setfenv(func, setmetatable(env, {__index =
 | |
| 		function(tbl, key)
 | |
| 			return rawget(tbl, key) or _M[key] or _G[key]
 | |
| 		end}))
 | |
| 
 | |
| 	local maps       = { func() }
 | |
| 	local uploads    = { }
 | |
| 	local has_upload = false
 | |
| 
 | |
| 	for i, map in ipairs(maps) do
 | |
| 		if not instanceof(map, Node) then
 | |
| 			error("CBI map returns no valid map object!")
 | |
| 			return nil
 | |
| 		else
 | |
| 			map:prepare()
 | |
| 			if map.upload_fields then
 | |
| 				has_upload = true
 | |
| 				for _, field in ipairs(map.upload_fields) do
 | |
| 					uploads[
 | |
| 						field.config .. '.' ..
 | |
| 						(field.section.sectiontype or '1') .. '.' ..
 | |
| 						field.option
 | |
| 					] = true
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if has_upload then
 | |
| 		local uci = luci.model.uci.cursor()
 | |
| 		local prm = luci.http.context.request.message.params
 | |
| 		local fd, cbid
 | |
| 
 | |
| 		luci.http.setfilehandler(
 | |
| 			function( field, chunk, eof )
 | |
| 				if not field then return end
 | |
| 				if field.name and not cbid then
 | |
| 					local c, s, o = field.name:gmatch(
 | |
| 						"cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
 | |
| 					)()
 | |
| 
 | |
| 					if c and s and o then
 | |
| 						local t = uci:get( c, s ) or s
 | |
| 						if uploads[c.."."..t.."."..o] then
 | |
| 							local path = upldir .. field.name
 | |
| 							fd = io.open(path, "w")
 | |
| 							if fd then
 | |
| 								cbid = field.name
 | |
| 								prm[cbid] = path
 | |
| 							end
 | |
| 						end
 | |
| 					end
 | |
| 				end
 | |
| 
 | |
| 				if field.name == cbid and fd then
 | |
| 					fd:write(chunk)
 | |
| 				end
 | |
| 
 | |
| 				if eof and fd then
 | |
| 					fd:close()
 | |
| 					fd   = nil
 | |
| 					cbid = nil
 | |
| 				end
 | |
| 			end
 | |
| 		)
 | |
| 	end
 | |
| 
 | |
| 	return maps
 | |
| end
 | |
| 
 | |
| --
 | |
| -- Compile a datatype specification into a parse tree for evaluation later on
 | |
| --
 | |
| local cdt_cache = { }
 | |
| 
 | |
| function compile_datatype(code)
 | |
| 	local i
 | |
| 	local pos = 0
 | |
| 	local esc = false
 | |
| 	local depth = 0
 | |
| 	local stack = { }
 | |
| 
 | |
| 	for i = 1, #code+1 do
 | |
| 		local byte = code:byte(i) or 44
 | |
| 		if esc then
 | |
| 			esc = false
 | |
| 		elseif byte == 92 then
 | |
| 			esc = true
 | |
| 		elseif byte == 40 or byte == 44 then
 | |
| 			if depth <= 0 then
 | |
| 				if pos < i then
 | |
| 					local label = code:sub(pos, i-1)
 | |
| 						:gsub("\\(.)", "%1")
 | |
| 						:gsub("^%s+", "")
 | |
| 						:gsub("%s+$", "")
 | |
| 
 | |
| 					if #label > 0 and tonumber(label) then
 | |
| 						stack[#stack+1] = tonumber(label)
 | |
| 					elseif label:match("^'.*'$") or label:match('^".*"$') then
 | |
| 						stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1")
 | |
| 					elseif type(datatypes[label]) == "function" then
 | |
| 						stack[#stack+1] = datatypes[label]
 | |
| 						stack[#stack+1] = { }
 | |
| 					else
 | |
| 						error("Datatype error, bad token %q" % label)
 | |
| 					end
 | |
| 				end
 | |
| 				pos = i + 1
 | |
| 			end
 | |
| 			depth = depth + (byte == 40 and 1 or 0)
 | |
| 		elseif byte == 41 then
 | |
| 			depth = depth - 1
 | |
| 			if depth <= 0 then
 | |
| 				if type(stack[#stack-1]) ~= "function" then
 | |
| 					error("Datatype error, argument list follows non-function")
 | |
| 				end
 | |
| 				stack[#stack] = compile_datatype(code:sub(pos, i-1))
 | |
| 				pos = i + 1
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return stack
 | |
| end
 | |
| 
 | |
| function verify_datatype(dt, value)
 | |
| 	if dt and #dt > 0 then
 | |
| 		if not cdt_cache[dt] then
 | |
| 			local c = compile_datatype(dt)
 | |
| 			if c and type(c[1]) == "function" then
 | |
| 				cdt_cache[dt] = c
 | |
| 			else
 | |
| 				error("Datatype error, not a function expression")
 | |
| 			end
 | |
| 		end
 | |
| 		if cdt_cache[dt] then
 | |
| 			return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2]))
 | |
| 		end
 | |
| 	end
 | |
| 	return true
 | |
| end
 | |
| 
 | |
| 
 | |
| -- Node pseudo abstract class
 | |
| Node = class()
 | |
| 
 | |
| function Node.__init__(self, title, description)
 | |
| 	self.children = {}
 | |
| 	self.title = title or ""
 | |
| 	self.description = description or ""
 | |
| 	self.template = "cbi/node"
 | |
| end
 | |
| 
 | |
| -- hook helper
 | |
| function Node._run_hook(self, hook)
 | |
| 	if type(self[hook]) == "function" then
 | |
| 		return self[hook](self)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Node._run_hooks(self, ...)
 | |
| 	local f
 | |
| 	local r = false
 | |
| 	for _, f in ipairs(arg) do
 | |
| 		if type(self[f]) == "function" then
 | |
| 			self[f](self)
 | |
| 			r = true
 | |
| 		end
 | |
| 	end
 | |
| 	return r
 | |
| end
 | |
| 
 | |
| -- Prepare nodes
 | |
| function Node.prepare(self, ...)
 | |
| 	for k, child in ipairs(self.children) do
 | |
| 		child:prepare(...)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Append child nodes
 | |
| function Node.append(self, obj)
 | |
| 	table.insert(self.children, obj)
 | |
| end
 | |
| 
 | |
| -- Parse this node and its children
 | |
| function Node.parse(self, ...)
 | |
| 	for k, child in ipairs(self.children) do
 | |
| 		child:parse(...)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Render this node
 | |
| function Node.render(self, scope)
 | |
| 	scope = scope or {}
 | |
| 	scope.self = self
 | |
| 
 | |
| 	luci.template.render(self.template, scope)
 | |
| end
 | |
| 
 | |
| -- Render the children
 | |
| function Node.render_children(self, ...)
 | |
| 	local k, node
 | |
| 	for k, node in ipairs(self.children) do
 | |
| 		node.last_child = (k == #self.children)
 | |
| 		node.index = k
 | |
| 		node:render(...)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| A simple template element
 | |
| ]]--
 | |
| Template = class(Node)
 | |
| 
 | |
| function Template.__init__(self, template)
 | |
| 	Node.__init__(self)
 | |
| 	self.template = template
 | |
| end
 | |
| 
 | |
| function Template.render(self)
 | |
| 	luci.template.render(self.template, {self=self})
 | |
| end
 | |
| 
 | |
| function Template.parse(self, readinput)
 | |
| 	self.readinput = (readinput ~= false)
 | |
| 	return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| Map - A map describing a configuration file
 | |
| ]]--
 | |
| Map = class(Node)
 | |
| 
 | |
| function Map.__init__(self, config, ...)
 | |
| 	Node.__init__(self, ...)
 | |
| 
 | |
| 	self.config = config
 | |
| 	self.parsechain = {self.config}
 | |
| 	self.template = "cbi/map"
 | |
| 	self.apply_on_parse = nil
 | |
| 	self.readinput = true
 | |
| 	self.proceed = false
 | |
| 	self.flow = {}
 | |
| 
 | |
| 	self.uci = uci.cursor()
 | |
| 	self.save = true
 | |
| 
 | |
| 	self.changed = false
 | |
| 
 | |
| 	local path = "%s/%s" %{ self.uci:get_confdir(), self.config }
 | |
| 	if fs.stat(path, "type") ~= "reg" then
 | |
| 		fs.writefile(path, "")
 | |
| 	end
 | |
| 
 | |
| 	local ok, err = self.uci:load(self.config)
 | |
| 	if not ok then
 | |
| 		local url = dispatcher.build_url(unpack(dispatcher.context.request))
 | |
| 		local source = self:formvalue("cbi.source")
 | |
| 		if type(source) == "string" then
 | |
| 			fs.writefile(path, source:gsub("\r\n", "\n"))
 | |
| 			ok, err = self.uci:load(self.config)
 | |
| 			if ok then
 | |
| 				luci.http.redirect(url)
 | |
| 			end
 | |
| 		end
 | |
| 		self.save = false
 | |
| 	end
 | |
| 
 | |
| 	if not ok then
 | |
| 		self.template   = "cbi/error"
 | |
| 		self.error      = err
 | |
| 		self.source     = fs.readfile(path) or ""
 | |
| 		self.pageaction = false
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Map.formvalue(self, key)
 | |
| 	return self.readinput and luci.http.formvalue(key) or nil
 | |
| end
 | |
| 
 | |
| function Map.formvaluetable(self, key)
 | |
| 	return self.readinput and luci.http.formvaluetable(key) or {}
 | |
| end
 | |
| 
 | |
| function Map.get_scheme(self, sectiontype, option)
 | |
| 	if not option then
 | |
| 		return self.scheme and self.scheme.sections[sectiontype]
 | |
| 	else
 | |
| 		return self.scheme and self.scheme.variables[sectiontype]
 | |
| 		 and self.scheme.variables[sectiontype][option]
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Map.submitstate(self)
 | |
| 	return self:formvalue("cbi.submit")
 | |
| end
 | |
| 
 | |
| -- Chain foreign config
 | |
| function Map.chain(self, config)
 | |
| 	table.insert(self.parsechain, config)
 | |
| end
 | |
| 
 | |
| function Map.state_handler(self, state)
 | |
| 	return state
 | |
| end
 | |
| 
 | |
| -- Use optimized UCI writing
 | |
| function Map.parse(self, readinput, ...)
 | |
| 	if self:formvalue("cbi.skip") then
 | |
| 		self.state = FORM_SKIP
 | |
| 	elseif not self.save then
 | |
| 		self.state = FORM_INVALID
 | |
| 	elseif not self:submitstate() then
 | |
| 		self.state = FORM_NODATA
 | |
| 	end
 | |
| 
 | |
| 	-- Back out early to prevent unauthorized changes on the subsequent parse
 | |
| 	if self.state ~= nil then
 | |
| 		return self:state_handler(self.state)
 | |
| 	end
 | |
| 
 | |
| 	self.readinput = (readinput ~= false)
 | |
| 	self:_run_hooks("on_parse")
 | |
| 
 | |
| 	Node.parse(self, ...)
 | |
| 
 | |
| 	if self.save then
 | |
| 		self:_run_hooks("on_save", "on_before_save")
 | |
| 		for i, config in ipairs(self.parsechain) do
 | |
| 			self.uci:save(config)
 | |
| 		end
 | |
| 		self:_run_hooks("on_after_save")
 | |
| 		if (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then
 | |
| 			self:_run_hooks("on_before_commit")
 | |
| 			for i, config in ipairs(self.parsechain) do
 | |
| 				self.uci:commit(config)
 | |
| 
 | |
| 				-- Refresh data because commit changes section names
 | |
| 				self.uci:load(config)
 | |
| 			end
 | |
| 			self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
 | |
| 			if self.apply_on_parse then
 | |
| 				self.uci:apply(self.parsechain)
 | |
| 				self:_run_hooks("on_apply", "on_after_apply")
 | |
| 			else
 | |
| 				-- This is evaluated by the dispatcher and delegated to the
 | |
| 				-- template which in turn fires XHR to perform the actual
 | |
| 				-- apply actions.
 | |
| 				self.apply_needed = true
 | |
| 			end
 | |
| 
 | |
| 			-- Reparse sections
 | |
| 			Node.parse(self, true)
 | |
| 		end
 | |
| 		for i, config in ipairs(self.parsechain) do
 | |
| 			self.uci:unload(config)
 | |
| 		end
 | |
| 		if type(self.commit_handler) == "function" then
 | |
| 			self:commit_handler(self:submitstate())
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if not self.save then
 | |
| 		self.state = FORM_INVALID
 | |
| 	elseif self.proceed then
 | |
| 		self.state = FORM_PROCEED
 | |
| 	elseif self.changed then
 | |
| 		self.state = FORM_CHANGED
 | |
| 	else
 | |
| 		self.state = FORM_VALID
 | |
| 	end
 | |
| 
 | |
| 	return self:state_handler(self.state)
 | |
| end
 | |
| 
 | |
| function Map.render(self, ...)
 | |
| 	self:_run_hooks("on_init")
 | |
| 	Node.render(self, ...)
 | |
| end
 | |
| 
 | |
| -- Creates a child section
 | |
| function Map.section(self, class, ...)
 | |
| 	if instanceof(class, AbstractSection) then
 | |
| 		local obj  = class(self, ...)
 | |
| 		self:append(obj)
 | |
| 		return obj
 | |
| 	else
 | |
| 		error("class must be a descendent of AbstractSection")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- UCI add
 | |
| function Map.add(self, sectiontype)
 | |
| 	return self.uci:add(self.config, sectiontype)
 | |
| end
 | |
| 
 | |
| -- UCI set
 | |
| function Map.set(self, section, option, value)
 | |
| 	if type(value) ~= "table" or #value > 0 then
 | |
| 		if option then
 | |
| 			return self.uci:set(self.config, section, option, value)
 | |
| 		else
 | |
| 			return self.uci:set(self.config, section, value)
 | |
| 		end
 | |
| 	else
 | |
| 		return Map.del(self, section, option)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- UCI del
 | |
| function Map.del(self, section, option)
 | |
| 	if option then
 | |
| 		return self.uci:delete(self.config, section, option)
 | |
| 	else
 | |
| 		return self.uci:delete(self.config, section)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- UCI get
 | |
| function Map.get(self, section, option)
 | |
| 	if not section then
 | |
| 		return self.uci:get_all(self.config)
 | |
| 	elseif option then
 | |
| 		return self.uci:get(self.config, section, option)
 | |
| 	else
 | |
| 		return self.uci:get_all(self.config, section)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| --[[
 | |
| Compound - Container
 | |
| ]]--
 | |
| Compound = class(Node)
 | |
| 
 | |
| function Compound.__init__(self, ...)
 | |
| 	Node.__init__(self)
 | |
| 	self.template = "cbi/compound"
 | |
| 	self.children = {...}
 | |
| end
 | |
| 
 | |
| function Compound.populate_delegator(self, delegator)
 | |
| 	for _, v in ipairs(self.children) do
 | |
| 		v.delegator = delegator
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Compound.parse(self, ...)
 | |
| 	local cstate, state = 0
 | |
| 
 | |
| 	for k, child in ipairs(self.children) do
 | |
| 		cstate = child:parse(...)
 | |
| 		state = (not state or cstate < state) and cstate or state
 | |
| 	end
 | |
| 
 | |
| 	return state
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| Delegator - Node controller
 | |
| ]]--
 | |
| Delegator = class(Node)
 | |
| function Delegator.__init__(self, ...)
 | |
| 	Node.__init__(self, ...)
 | |
| 	self.nodes = {}
 | |
| 	self.defaultpath = {}
 | |
| 	self.pageaction = false
 | |
| 	self.readinput = true
 | |
| 	self.allow_reset = false
 | |
| 	self.allow_cancel = false
 | |
| 	self.allow_back = false
 | |
| 	self.allow_finish = false
 | |
| 	self.template = "cbi/delegator"
 | |
| end
 | |
| 
 | |
| function Delegator.set(self, name, node)
 | |
| 	assert(not self.nodes[name], "Duplicate entry")
 | |
| 
 | |
| 	self.nodes[name] = node
 | |
| end
 | |
| 
 | |
| function Delegator.add(self, name, node)
 | |
| 	node = self:set(name, node)
 | |
| 	self.defaultpath[#self.defaultpath+1] = name
 | |
| end
 | |
| 
 | |
| function Delegator.insert_after(self, name, after)
 | |
| 	local n = #self.chain + 1
 | |
| 	for k, v in ipairs(self.chain) do
 | |
| 		if v == after then
 | |
| 			n = k + 1
 | |
| 			break
 | |
| 		end
 | |
| 	end
 | |
| 	table.insert(self.chain, n, name)
 | |
| end
 | |
| 
 | |
| function Delegator.set_route(self, ...)
 | |
| 	local n, chain, route = 0, self.chain, {...}
 | |
| 	for i = 1, #chain do
 | |
| 		if chain[i] == self.current then
 | |
| 			n = i
 | |
| 			break
 | |
| 		end
 | |
| 	end
 | |
| 	for i = 1, #route do
 | |
| 		n = n + 1
 | |
| 		chain[n] = route[i]
 | |
| 	end
 | |
| 	for i = n + 1, #chain do
 | |
| 		chain[i] = nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Delegator.get(self, name)
 | |
| 	local node = self.nodes[name]
 | |
| 
 | |
| 	if type(node) == "string" then
 | |
| 		node = load(node, name)
 | |
| 	end
 | |
| 
 | |
| 	if type(node) == "table" and getmetatable(node) == nil then
 | |
| 		node = Compound(unpack(node))
 | |
| 	end
 | |
| 
 | |
| 	return node
 | |
| end
 | |
| 
 | |
| function Delegator.parse(self, ...)
 | |
| 	if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
 | |
| 		if self:_run_hooks("on_cancel") then
 | |
| 			return FORM_DONE
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if not Map.formvalue(self, "cbi.delg.current") then
 | |
| 		self:_run_hooks("on_init")
 | |
| 	end
 | |
| 
 | |
| 	local newcurrent
 | |
| 	self.chain = self.chain or self:get_chain()
 | |
| 	self.current = self.current or self:get_active()
 | |
| 	self.active = self.active or self:get(self.current)
 | |
| 	assert(self.active, "Invalid state")
 | |
| 
 | |
| 	local stat = FORM_DONE
 | |
| 	if type(self.active) ~= "function" then
 | |
| 		self.active:populate_delegator(self)
 | |
| 		stat = self.active:parse()
 | |
| 	else
 | |
| 		self:active()
 | |
| 	end
 | |
| 
 | |
| 	if stat > FORM_PROCEED then
 | |
| 		if Map.formvalue(self, "cbi.delg.back") then
 | |
| 			newcurrent = self:get_prev(self.current)
 | |
| 		else
 | |
| 			newcurrent = self:get_next(self.current)
 | |
| 		end
 | |
| 	elseif stat < FORM_PROCEED then
 | |
| 		return stat
 | |
| 	end
 | |
| 
 | |
| 
 | |
| 	if not Map.formvalue(self, "cbi.submit") then
 | |
| 		return FORM_NODATA
 | |
| 	elseif stat > FORM_PROCEED
 | |
| 	and (not newcurrent or not self:get(newcurrent)) then
 | |
| 		return self:_run_hook("on_done") or FORM_DONE
 | |
| 	else
 | |
| 		self.current = newcurrent or self.current
 | |
| 		self.active = self:get(self.current)
 | |
| 		if type(self.active) ~= "function" then
 | |
| 			self.active:populate_delegator(self)
 | |
| 			local stat = self.active:parse(false)
 | |
| 			if stat == FORM_SKIP then
 | |
| 				return self:parse(...)
 | |
| 			else
 | |
| 				return FORM_PROCEED
 | |
| 			end
 | |
| 		else
 | |
| 			return self:parse(...)
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Delegator.get_next(self, state)
 | |
| 	for k, v in ipairs(self.chain) do
 | |
| 		if v == state then
 | |
| 			return self.chain[k+1]
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Delegator.get_prev(self, state)
 | |
| 	for k, v in ipairs(self.chain) do
 | |
| 		if v == state then
 | |
| 			return self.chain[k-1]
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Delegator.get_chain(self)
 | |
| 	local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
 | |
| 	return type(x) == "table" and x or {x}
 | |
| end
 | |
| 
 | |
| function Delegator.get_active(self)
 | |
| 	return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
 | |
| end
 | |
| 
 | |
| --[[
 | |
| Page - A simple node
 | |
| ]]--
 | |
| 
 | |
| Page = class(Node)
 | |
| Page.__init__ = Node.__init__
 | |
| Page.parse    = function() end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| SimpleForm - A Simple non-UCI form
 | |
| ]]--
 | |
| SimpleForm = class(Node)
 | |
| 
 | |
| function SimpleForm.__init__(self, config, title, description, data)
 | |
| 	Node.__init__(self, title, description)
 | |
| 	self.config = config
 | |
| 	self.data = data or {}
 | |
| 	self.template = "cbi/simpleform"
 | |
| 	self.dorender = true
 | |
| 	self.pageaction = false
 | |
| 	self.readinput = true
 | |
| end
 | |
| 
 | |
| SimpleForm.formvalue = Map.formvalue
 | |
| SimpleForm.formvaluetable = Map.formvaluetable
 | |
| 
 | |
| function SimpleForm.parse(self, readinput, ...)
 | |
| 	self.readinput = (readinput ~= false)
 | |
| 
 | |
| 	if self:formvalue("cbi.skip") then
 | |
| 		return FORM_SKIP
 | |
| 	end
 | |
| 
 | |
| 	if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
 | |
| 		return FORM_DONE
 | |
| 	end
 | |
| 
 | |
| 	if self:submitstate() then
 | |
| 		Node.parse(self, 1, ...)
 | |
| 	end
 | |
| 
 | |
| 	local valid = true
 | |
| 	for k, j in ipairs(self.children) do
 | |
| 		for i, v in ipairs(j.children) do
 | |
| 			valid = valid
 | |
| 			 and (not v.tag_missing or not v.tag_missing[1])
 | |
| 			 and (not v.tag_invalid or not v.tag_invalid[1])
 | |
| 			 and (not v.error)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	local state =
 | |
| 		not self:submitstate() and FORM_NODATA
 | |
| 		or valid and FORM_VALID
 | |
| 		or FORM_INVALID
 | |
| 
 | |
| 	self.dorender = not self.handle
 | |
| 	if self.handle then
 | |
| 		local nrender, nstate = self:handle(state, self.data)
 | |
| 		self.dorender = self.dorender or (nrender ~= false)
 | |
| 		state = nstate or state
 | |
| 	end
 | |
| 	return state
 | |
| end
 | |
| 
 | |
| function SimpleForm.render(self, ...)
 | |
| 	if self.dorender then
 | |
| 		Node.render(self, ...)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function SimpleForm.submitstate(self)
 | |
| 	return self:formvalue("cbi.submit")
 | |
| end
 | |
| 
 | |
| function SimpleForm.section(self, class, ...)
 | |
| 	if instanceof(class, AbstractSection) then
 | |
| 		local obj  = class(self, ...)
 | |
| 		self:append(obj)
 | |
| 		return obj
 | |
| 	else
 | |
| 		error("class must be a descendent of AbstractSection")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Creates a child field
 | |
| function SimpleForm.field(self, class, ...)
 | |
| 	local section
 | |
| 	for k, v in ipairs(self.children) do
 | |
| 		if instanceof(v, SimpleSection) then
 | |
| 			section = v
 | |
| 			break
 | |
| 		end
 | |
| 	end
 | |
| 	if not section then
 | |
| 		section = self:section(SimpleSection)
 | |
| 	end
 | |
| 
 | |
| 	if instanceof(class, AbstractValue) then
 | |
| 		local obj  = class(self, section, ...)
 | |
| 		obj.track_missing = true
 | |
| 		section:append(obj)
 | |
| 		return obj
 | |
| 	else
 | |
| 		error("class must be a descendent of AbstractValue")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function SimpleForm.set(self, section, option, value)
 | |
| 	self.data[option] = value
 | |
| end
 | |
| 
 | |
| 
 | |
| function SimpleForm.del(self, section, option)
 | |
| 	self.data[option] = nil
 | |
| end
 | |
| 
 | |
| 
 | |
| function SimpleForm.get(self, section, option)
 | |
| 	return self.data[option]
 | |
| end
 | |
| 
 | |
| 
 | |
| function SimpleForm.get_scheme()
 | |
| 	return nil
 | |
| end
 | |
| 
 | |
| 
 | |
| Form = class(SimpleForm)
 | |
| 
 | |
| function Form.__init__(self, ...)
 | |
| 	SimpleForm.__init__(self, ...)
 | |
| 	self.embedded = true
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| AbstractSection
 | |
| ]]--
 | |
| AbstractSection = class(Node)
 | |
| 
 | |
| function AbstractSection.__init__(self, map, sectiontype, ...)
 | |
| 	Node.__init__(self, ...)
 | |
| 	self.sectiontype = sectiontype
 | |
| 	self.map = map
 | |
| 	self.config = map.config
 | |
| 	self.optionals = {}
 | |
| 	self.defaults = {}
 | |
| 	self.fields = {}
 | |
| 	self.tag_error = {}
 | |
| 	self.tag_invalid = {}
 | |
| 	self.tag_deperror = {}
 | |
| 	self.changed = false
 | |
| 
 | |
| 	self.optional = true
 | |
| 	self.addremove = false
 | |
| 	self.dynamic = false
 | |
| end
 | |
| 
 | |
| -- Define a tab for the section
 | |
| function AbstractSection.tab(self, tab, title, desc)
 | |
| 	self.tabs      = self.tabs      or { }
 | |
| 	self.tab_names = self.tab_names or { }
 | |
| 
 | |
| 	self.tab_names[#self.tab_names+1] = tab
 | |
| 	self.tabs[tab] = {
 | |
| 		title       = title,
 | |
| 		description = desc,
 | |
| 		childs      = { }
 | |
| 	}
 | |
| end
 | |
| 
 | |
| -- Check whether the section has tabs
 | |
| function AbstractSection.has_tabs(self)
 | |
| 	return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
 | |
| end
 | |
| 
 | |
| -- Appends a new option
 | |
| function AbstractSection.option(self, class, option, ...)
 | |
| 	if instanceof(class, AbstractValue) then
 | |
| 		local obj  = class(self.map, self, option, ...)
 | |
| 		self:append(obj)
 | |
| 		self.fields[option] = obj
 | |
| 		return obj
 | |
| 	elseif class == true then
 | |
| 		error("No valid class was given and autodetection failed.")
 | |
| 	else
 | |
| 		error("class must be a descendant of AbstractValue")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Appends a new tabbed option
 | |
| function AbstractSection.taboption(self, tab, ...)
 | |
| 
 | |
| 	assert(tab and self.tabs and self.tabs[tab],
 | |
| 		"Cannot assign option to not existing tab %q" % tostring(tab))
 | |
| 
 | |
| 	local l = self.tabs[tab].childs
 | |
| 	local o = AbstractSection.option(self, ...)
 | |
| 
 | |
| 	if o then l[#l+1] = o end
 | |
| 
 | |
| 	return o
 | |
| end
 | |
| 
 | |
| -- Render a single tab
 | |
| function AbstractSection.render_tab(self, tab, ...)
 | |
| 
 | |
| 	assert(tab and self.tabs and self.tabs[tab],
 | |
| 		"Cannot render not existing tab %q" % tostring(tab))
 | |
| 
 | |
| 	local k, node
 | |
| 	for k, node in ipairs(self.tabs[tab].childs) do
 | |
| 		node.last_child = (k == #self.tabs[tab].childs)
 | |
| 		node.index = k
 | |
| 		node:render(...)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Parse optional options
 | |
| function AbstractSection.parse_optionals(self, section, noparse)
 | |
| 	if not self.optional then
 | |
| 		return
 | |
| 	end
 | |
| 
 | |
| 	self.optionals[section] = {}
 | |
| 
 | |
| 	local field = nil
 | |
| 	if not noparse then
 | |
| 		field = self.map:formvalue("cbi.opt."..self.config.."."..section)
 | |
| 	end
 | |
| 
 | |
| 	for k,v in ipairs(self.children) do
 | |
| 		if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
 | |
| 			if field == v.option then
 | |
| 				field = nil
 | |
| 				self.map.proceed = true
 | |
| 			else
 | |
| 				table.insert(self.optionals[section], v)
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if field and #field > 0 and self.dynamic then
 | |
| 		self:add_dynamic(field)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Add a dynamic option
 | |
| function AbstractSection.add_dynamic(self, field, optional)
 | |
| 	local o = self:option(Value, field, field)
 | |
| 	o.optional = optional
 | |
| end
 | |
| 
 | |
| -- Parse all dynamic options
 | |
| function AbstractSection.parse_dynamic(self, section)
 | |
| 	if not self.dynamic then
 | |
| 		return
 | |
| 	end
 | |
| 
 | |
| 	local arr  = luci.util.clone(self:cfgvalue(section))
 | |
| 	local form = self.map:formvaluetable("cbid."..self.config.."."..section)
 | |
| 	for k, v in pairs(form) do
 | |
| 		arr[k] = v
 | |
| 	end
 | |
| 
 | |
| 	for key,val in pairs(arr) do
 | |
| 		local create = true
 | |
| 
 | |
| 		for i,c in ipairs(self.children) do
 | |
| 			if c.option == key then
 | |
| 				create = false
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		if create and key:sub(1, 1) ~= "." then
 | |
| 			self.map.proceed = true
 | |
| 			self:add_dynamic(key, true)
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Returns the section's UCI table
 | |
| function AbstractSection.cfgvalue(self, section)
 | |
| 	return self.map:get(section)
 | |
| end
 | |
| 
 | |
| -- Push events
 | |
| function AbstractSection.push_events(self)
 | |
| 	--luci.util.append(self.map.events, self.events)
 | |
| 	self.map.changed = true
 | |
| end
 | |
| 
 | |
| -- Removes the section
 | |
| function AbstractSection.remove(self, section)
 | |
| 	self.map.proceed = true
 | |
| 	return self.map:del(section)
 | |
| end
 | |
| 
 | |
| -- Creates the section
 | |
| function AbstractSection.create(self, section)
 | |
| 	local stat
 | |
| 
 | |
| 	if section then
 | |
| 		stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
 | |
| 	else
 | |
| 		section = self.map:add(self.sectiontype)
 | |
| 		stat = section
 | |
| 	end
 | |
| 
 | |
| 	if stat then
 | |
| 		for k,v in pairs(self.children) do
 | |
| 			if v.default then
 | |
| 				self.map:set(section, v.option, v.default)
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		for k,v in pairs(self.defaults) do
 | |
| 			self.map:set(section, k, v)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	self.map.proceed = true
 | |
| 
 | |
| 	return stat
 | |
| end
 | |
| 
 | |
| 
 | |
| SimpleSection = class(AbstractSection)
 | |
| 
 | |
| function SimpleSection.__init__(self, form, ...)
 | |
| 	AbstractSection.__init__(self, form, nil, ...)
 | |
| 	self.template = "cbi/nullsection"
 | |
| end
 | |
| 
 | |
| 
 | |
| Table = class(AbstractSection)
 | |
| 
 | |
| function Table.__init__(self, form, data, ...)
 | |
| 	local datasource = {}
 | |
| 	local tself = self
 | |
| 	datasource.config = "table"
 | |
| 	self.data = data or {}
 | |
| 
 | |
| 	datasource.formvalue = Map.formvalue
 | |
| 	datasource.formvaluetable = Map.formvaluetable
 | |
| 	datasource.readinput = true
 | |
| 
 | |
| 	function datasource.get(self, section, option)
 | |
| 		return tself.data[section] and tself.data[section][option]
 | |
| 	end
 | |
| 
 | |
| 	function datasource.submitstate(self)
 | |
| 		return Map.formvalue(self, "cbi.submit")
 | |
| 	end
 | |
| 
 | |
| 	function datasource.del(...)
 | |
| 		return true
 | |
| 	end
 | |
| 
 | |
| 	function datasource.get_scheme()
 | |
| 		return nil
 | |
| 	end
 | |
| 
 | |
| 	AbstractSection.__init__(self, datasource, "table", ...)
 | |
| 	self.template = "cbi/tblsection"
 | |
| 	self.rowcolors = true
 | |
| 	self.anonymous = true
 | |
| end
 | |
| 
 | |
| function Table.parse(self, readinput)
 | |
| 	self.map.readinput = (readinput ~= false)
 | |
| 	for i, k in ipairs(self:cfgsections()) do
 | |
| 		if self.map:submitstate() then
 | |
| 			Node.parse(self, k)
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Table.cfgsections(self)
 | |
| 	local sections = {}
 | |
| 
 | |
| 	for i, v in luci.util.kspairs(self.data) do
 | |
| 		table.insert(sections, i)
 | |
| 	end
 | |
| 
 | |
| 	return sections
 | |
| end
 | |
| 
 | |
| function Table.update(self, data)
 | |
| 	self.data = data
 | |
| end
 | |
| 
 | |
| 
 | |
| 
 | |
| --[[
 | |
| NamedSection - A fixed configuration section defined by its name
 | |
| ]]--
 | |
| NamedSection = class(AbstractSection)
 | |
| 
 | |
| function NamedSection.__init__(self, map, section, stype, ...)
 | |
| 	AbstractSection.__init__(self, map, stype, ...)
 | |
| 
 | |
| 	-- Defaults
 | |
| 	self.addremove = false
 | |
| 	self.template = "cbi/nsection"
 | |
| 	self.section = section
 | |
| end
 | |
| 
 | |
| function NamedSection.prepare(self)
 | |
| 	AbstractSection.prepare(self)
 | |
| 	AbstractSection.parse_optionals(self, self.section, true)
 | |
| end
 | |
| 
 | |
| function NamedSection.parse(self, novld)
 | |
| 	local s = self.section
 | |
| 	local active = self:cfgvalue(s)
 | |
| 
 | |
| 	if self.addremove then
 | |
| 		local path = self.config.."."..s
 | |
| 		if active then -- Remove the section
 | |
| 			if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
 | |
| 				self:push_events()
 | |
| 				return
 | |
| 			end
 | |
| 		else           -- Create and apply default values
 | |
| 			if self.map:formvalue("cbi.cns."..path) then
 | |
| 				self:create(s)
 | |
| 				return
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if active then
 | |
| 		AbstractSection.parse_dynamic(self, s)
 | |
| 		if self.map:submitstate() then
 | |
| 			Node.parse(self, s)
 | |
| 		end
 | |
| 		AbstractSection.parse_optionals(self, s)
 | |
| 
 | |
| 		if self.changed then
 | |
| 			self:push_events()
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| TypedSection - A (set of) configuration section(s) defined by the type
 | |
| 	addremove: 	Defines whether the user can add/remove sections of this type
 | |
| 	anonymous:  Allow creating anonymous sections
 | |
| 	validate: 	a validation function returning nil if the section is invalid
 | |
| ]]--
 | |
| TypedSection = class(AbstractSection)
 | |
| 
 | |
| function TypedSection.__init__(self, map, type, ...)
 | |
| 	AbstractSection.__init__(self, map, type, ...)
 | |
| 
 | |
| 	self.template = "cbi/tsection"
 | |
| 	self.deps = {}
 | |
| 	self.anonymous = false
 | |
| end
 | |
| 
 | |
| function TypedSection.prepare(self)
 | |
| 	AbstractSection.prepare(self)
 | |
| 
 | |
| 	local i, s
 | |
| 	for i, s in ipairs(self:cfgsections()) do
 | |
| 		AbstractSection.parse_optionals(self, s, true)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Return all matching UCI sections for this TypedSection
 | |
| function TypedSection.cfgsections(self)
 | |
| 	local sections = {}
 | |
| 	self.map.uci:foreach(self.map.config, self.sectiontype,
 | |
| 		function (section)
 | |
| 			if self:checkscope(section[".name"]) then
 | |
| 				table.insert(sections, section[".name"])
 | |
| 			end
 | |
| 		end)
 | |
| 
 | |
| 	return sections
 | |
| end
 | |
| 
 | |
| -- Limits scope to sections that have certain option => value pairs
 | |
| function TypedSection.depends(self, option, value)
 | |
| 	table.insert(self.deps, {option=option, value=value})
 | |
| end
 | |
| 
 | |
| function TypedSection.parse(self, novld)
 | |
| 	if self.addremove then
 | |
| 		-- Remove
 | |
| 		local crval = REMOVE_PREFIX .. self.config
 | |
| 		local name = self.map:formvaluetable(crval)
 | |
| 		for k,v in pairs(name) do
 | |
| 			if k:sub(-2) == ".x" then
 | |
| 				k = k:sub(1, #k - 2)
 | |
| 			end
 | |
| 			if self:cfgvalue(k) and self:checkscope(k) then
 | |
| 				self:remove(k)
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	local co
 | |
| 	for i, k in ipairs(self:cfgsections()) do
 | |
| 		AbstractSection.parse_dynamic(self, k)
 | |
| 		if self.map:submitstate() then
 | |
| 			Node.parse(self, k, novld)
 | |
| 		end
 | |
| 		AbstractSection.parse_optionals(self, k)
 | |
| 	end
 | |
| 
 | |
| 	if self.addremove then
 | |
| 		-- Create
 | |
| 		local created
 | |
| 		local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
 | |
| 		local origin, name = next(self.map:formvaluetable(crval))
 | |
| 		if self.anonymous then
 | |
| 			if name then
 | |
| 				created = self:create(nil, origin)
 | |
| 			end
 | |
| 		else
 | |
| 			if name then
 | |
| 				-- Ignore if it already exists
 | |
| 				if self:cfgvalue(name) then
 | |
| 					name = nil;
 | |
| 				end
 | |
| 
 | |
| 				name = self:checkscope(name)
 | |
| 
 | |
| 				if not name then
 | |
| 					self.err_invalid = true
 | |
| 				end
 | |
| 
 | |
| 				if name and #name > 0 then
 | |
| 					created = self:create(name, origin) and name
 | |
| 					if not created then
 | |
| 						self.invalid_cts = true
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		if created then
 | |
| 			AbstractSection.parse_optionals(self, created)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if self.sortable then
 | |
| 		local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
 | |
| 		local order = self.map:formvalue(stval)
 | |
| 		if order and #order > 0 then
 | |
| 			local sid
 | |
| 			local num = 0
 | |
| 			for sid in util.imatch(order) do
 | |
| 				self.map.uci:reorder(self.config, sid, num)
 | |
| 				num = num + 1
 | |
| 			end
 | |
| 			self.changed = (num > 0)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if created or self.changed then
 | |
| 		self:push_events()
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Verifies scope of sections
 | |
| function TypedSection.checkscope(self, section)
 | |
| 	-- Check if we are not excluded
 | |
| 	if self.filter and not self:filter(section) then
 | |
| 		return nil
 | |
| 	end
 | |
| 
 | |
| 	-- Check if at least one dependency is met
 | |
| 	if #self.deps > 0 and self:cfgvalue(section) then
 | |
| 		local stat = false
 | |
| 
 | |
| 		for k, v in ipairs(self.deps) do
 | |
| 			if self:cfgvalue(section)[v.option] == v.value then
 | |
| 				stat = true
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		if not stat then
 | |
| 			return nil
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return self:validate(section)
 | |
| end
 | |
| 
 | |
| 
 | |
| -- Dummy validate function
 | |
| function TypedSection.validate(self, section)
 | |
| 	return section
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| AbstractValue - An abstract Value Type
 | |
| 	null:		Value can be empty
 | |
| 	valid:		A function returning the value if it is valid otherwise nil
 | |
| 	depends:	A table of option => value pairs of which one must be true
 | |
| 	default:	The default value
 | |
| 	size:		The size of the input fields
 | |
| 	rmempty:	Unset value if empty
 | |
| 	optional:	This value is optional (see AbstractSection.optionals)
 | |
| ]]--
 | |
| AbstractValue = class(Node)
 | |
| 
 | |
| function AbstractValue.__init__(self, map, section, option, ...)
 | |
| 	Node.__init__(self, ...)
 | |
| 	self.section = section
 | |
| 	self.option  = option
 | |
| 	self.map     = map
 | |
| 	self.config  = map.config
 | |
| 	self.tag_invalid = {}
 | |
| 	self.tag_missing = {}
 | |
| 	self.tag_reqerror = {}
 | |
| 	self.tag_error = {}
 | |
| 	self.deps = {}
 | |
| 	--self.cast = "string"
 | |
| 
 | |
| 	self.track_missing = false
 | |
| 	self.rmempty   = true
 | |
| 	self.default   = nil
 | |
| 	self.size      = nil
 | |
| 	self.optional  = false
 | |
| end
 | |
| 
 | |
| function AbstractValue.prepare(self)
 | |
| 	self.cast = self.cast or "string"
 | |
| end
 | |
| 
 | |
| -- Add a dependencie to another section field
 | |
| function AbstractValue.depends(self, field, value)
 | |
| 	local deps
 | |
| 	if type(field) == "string" then
 | |
| 		deps = {}
 | |
| 	 	deps[field] = value
 | |
| 	else
 | |
| 		deps = field
 | |
| 	end
 | |
| 
 | |
| 	table.insert(self.deps, deps)
 | |
| end
 | |
| 
 | |
| -- Serialize dependencies
 | |
| function AbstractValue.deplist2json(self, section, deplist)
 | |
| 	local deps, i, d = { }
 | |
| 
 | |
| 	if type(self.deps) == "table" then
 | |
| 		for i, d in ipairs(deplist or self.deps) do
 | |
| 			local a, k, v = { }
 | |
| 			for k, v in pairs(d) do
 | |
| 				if k:find("!", 1, true) then
 | |
| 					a[k] = v
 | |
| 				elseif k:find(".", 1, true) then
 | |
| 					a['cbid.%s' % k] = v
 | |
| 				else
 | |
| 					a['cbid.%s.%s.%s' %{ self.config, section, k }] = v
 | |
| 				end
 | |
| 			end
 | |
| 			deps[#deps+1] = a
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return util.serialize_json(deps)
 | |
| end
 | |
| 
 | |
| -- Generates the unique CBID
 | |
| function AbstractValue.cbid(self, section)
 | |
| 	return "cbid."..self.map.config.."."..section.."."..self.option
 | |
| end
 | |
| 
 | |
| -- Return whether this object should be created
 | |
| function AbstractValue.formcreated(self, section)
 | |
| 	local key = "cbi.opt."..self.config.."."..section
 | |
| 	return (self.map:formvalue(key) == self.option)
 | |
| end
 | |
| 
 | |
| -- Returns the formvalue for this object
 | |
| function AbstractValue.formvalue(self, section)
 | |
| 	return self.map:formvalue(self:cbid(section))
 | |
| end
 | |
| 
 | |
| function AbstractValue.additional(self, value)
 | |
| 	self.optional = value
 | |
| end
 | |
| 
 | |
| function AbstractValue.mandatory(self, value)
 | |
| 	self.rmempty = not value
 | |
| end
 | |
| 
 | |
| function AbstractValue.add_error(self, section, type, msg)
 | |
| 	self.error = self.error or { }
 | |
| 	self.error[section] = msg or type
 | |
| 
 | |
| 	self.section.error = self.section.error or { }
 | |
| 	self.section.error[section] = self.section.error[section] or { }
 | |
| 	table.insert(self.section.error[section], msg or type)
 | |
| 
 | |
| 	if type == "invalid" then
 | |
| 		self.tag_invalid[section] = true
 | |
| 	elseif type == "missing" then
 | |
| 		self.tag_missing[section] = true
 | |
| 	end
 | |
| 
 | |
| 	self.tag_error[section] = true
 | |
| 	self.map.save = false
 | |
| end
 | |
| 
 | |
| function AbstractValue.parse(self, section, novld)
 | |
| 	local fvalue = self:formvalue(section)
 | |
| 	local cvalue = self:cfgvalue(section)
 | |
| 
 | |
| 	-- If favlue and cvalue are both tables and have the same content
 | |
| 	-- make them identical
 | |
| 	if type(fvalue) == "table" and type(cvalue) == "table" then
 | |
| 		local equal = #fvalue == #cvalue
 | |
| 		if equal then
 | |
| 			for i=1, #fvalue do
 | |
| 				if cvalue[i] ~= fvalue[i] then
 | |
| 					equal = false
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		if equal then
 | |
| 			fvalue = cvalue
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
 | |
| 		local val_err
 | |
| 		fvalue, val_err = self:validate(fvalue, section)
 | |
| 		fvalue = self:transform(fvalue)
 | |
| 
 | |
| 		if not fvalue and not novld then
 | |
| 			self:add_error(section, "invalid", val_err)
 | |
| 		end
 | |
| 
 | |
| 		if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
 | |
| 			if self:write(section, fvalue) then
 | |
| 				-- Push events
 | |
| 				self.section.changed = true
 | |
| 				--luci.util.append(self.map.events, self.events)
 | |
| 			end
 | |
| 		end
 | |
| 	else							-- Unset the UCI or error
 | |
| 		if self.rmempty or self.optional then
 | |
| 			if self:remove(section) then
 | |
| 				-- Push events
 | |
| 				self.section.changed = true
 | |
| 				--luci.util.append(self.map.events, self.events)
 | |
| 			end
 | |
| 		elseif cvalue ~= fvalue and not novld then
 | |
| 			-- trigger validator with nil value to get custom user error msg.
 | |
| 			local _, val_err = self:validate(nil, section)
 | |
| 			self:add_error(section, "missing", val_err)
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Render if this value exists or if it is mandatory
 | |
| function AbstractValue.render(self, s, scope)
 | |
| 	if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
 | |
| 		scope = scope or {}
 | |
| 		scope.section = s
 | |
| 		scope.cbid    = self:cbid(s)
 | |
| 		Node.render(self, scope)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Return the UCI value of this object
 | |
| function AbstractValue.cfgvalue(self, section)
 | |
| 	local value
 | |
| 	if self.tag_error[section] then
 | |
| 		value = self:formvalue(section)
 | |
| 	else
 | |
| 		value = self.map:get(section, self.option)
 | |
| 	end
 | |
| 
 | |
| 	if not value then
 | |
| 		return nil
 | |
| 	elseif not self.cast or self.cast == type(value) then
 | |
| 		return value
 | |
| 	elseif self.cast == "string" then
 | |
| 		if type(value) == "table" then
 | |
| 			return value[1]
 | |
| 		end
 | |
| 	elseif self.cast == "table" then
 | |
| 		return { value }
 | |
| 	end
 | |
| end
 | |
| 
 | |
| -- Validate the form value
 | |
| function AbstractValue.validate(self, value)
 | |
| 	if self.datatype and value then
 | |
| 		if type(value) == "table" then
 | |
| 			local v
 | |
| 			for _, v in ipairs(value) do
 | |
| 				if v and #v > 0 and not verify_datatype(self.datatype, v) then
 | |
| 					return nil
 | |
| 				end
 | |
| 			end
 | |
| 		else
 | |
| 			if not verify_datatype(self.datatype, value) then
 | |
| 				return nil
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return value
 | |
| end
 | |
| 
 | |
| AbstractValue.transform = AbstractValue.validate
 | |
| 
 | |
| 
 | |
| -- Write to UCI
 | |
| function AbstractValue.write(self, section, value)
 | |
| 	return self.map:set(section, self.option, value)
 | |
| end
 | |
| 
 | |
| -- Remove from UCI
 | |
| function AbstractValue.remove(self, section)
 | |
| 	return self.map:del(section, self.option)
 | |
| end
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| --[[
 | |
| Value - A one-line value
 | |
| 	maxlength:	The maximum length
 | |
| ]]--
 | |
| Value = class(AbstractValue)
 | |
| 
 | |
| function Value.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/value"
 | |
| 	self.keylist = {}
 | |
| 	self.vallist = {}
 | |
| 	self.readonly = nil
 | |
| end
 | |
| 
 | |
| function Value.reset_values(self)
 | |
| 	self.keylist = {}
 | |
| 	self.vallist = {}
 | |
| end
 | |
| 
 | |
| function Value.value(self, key, val)
 | |
| 	val = val or key
 | |
| 	table.insert(self.keylist, tostring(key))
 | |
| 	table.insert(self.vallist, tostring(val))
 | |
| end
 | |
| 
 | |
| function Value.parse(self, section, novld)
 | |
| 	if self.readonly then return end
 | |
| 	AbstractValue.parse(self, section, novld)
 | |
| end
 | |
| 
 | |
| -- DummyValue - This does nothing except being there
 | |
| DummyValue = class(AbstractValue)
 | |
| 
 | |
| function DummyValue.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template = "cbi/dvalue"
 | |
| 	self.value = nil
 | |
| end
 | |
| 
 | |
| function DummyValue.cfgvalue(self, section)
 | |
| 	local value
 | |
| 	if self.value then
 | |
| 		if type(self.value) == "function" then
 | |
| 			value = self:value(section)
 | |
| 		else
 | |
| 			value = self.value
 | |
| 		end
 | |
| 	else
 | |
| 		value = AbstractValue.cfgvalue(self, section)
 | |
| 	end
 | |
| 	return value
 | |
| end
 | |
| 
 | |
| function DummyValue.parse(self)
 | |
| 
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| Flag - A flag being enabled or disabled
 | |
| ]]--
 | |
| Flag = class(AbstractValue)
 | |
| 
 | |
| function Flag.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/fvalue"
 | |
| 
 | |
| 	self.enabled  = "1"
 | |
| 	self.disabled = "0"
 | |
| 	self.default  = self.disabled
 | |
| end
 | |
| 
 | |
| -- A flag can only have two states: set or unset
 | |
| function Flag.parse(self, section, novld)
 | |
| 	local fexists = self.map:formvalue(
 | |
| 		FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
 | |
| 
 | |
| 	if fexists then
 | |
| 		local fvalue = self:formvalue(section) and self.enabled or self.disabled
 | |
| 		local cvalue = self:cfgvalue(section)
 | |
| 		local val_err
 | |
| 		fvalue, val_err = self:validate(fvalue, section)
 | |
| 		if not fvalue then
 | |
| 			if not novld then
 | |
| 				self:add_error(section, "invalid", val_err)
 | |
| 			end
 | |
| 			return
 | |
| 		end
 | |
| 		if fvalue == self.default and (self.optional or self.rmempty) then
 | |
| 			self:remove(section)
 | |
| 		else
 | |
| 			self:write(section, fvalue)
 | |
| 		end
 | |
| 		if (fvalue ~= cvalue) then self.section.changed = true end
 | |
| 	else
 | |
| 		self:remove(section)
 | |
| 		self.section.changed = true
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function Flag.cfgvalue(self, section)
 | |
| 	return AbstractValue.cfgvalue(self, section) or self.default
 | |
| end
 | |
| function Flag.validate(self, value)
 | |
| 	return value
 | |
| end
 | |
| 
 | |
| --[[
 | |
| ListValue - A one-line value predefined in a list
 | |
| 	widget: The widget that will be used (select, radio)
 | |
| ]]--
 | |
| ListValue = class(AbstractValue)
 | |
| 
 | |
| function ListValue.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/lvalue"
 | |
| 
 | |
| 	self.size   = 1
 | |
| 	self.widget = "select"
 | |
| 
 | |
| 	self:reset_values()
 | |
| end
 | |
| 
 | |
| function ListValue.reset_values(self)
 | |
| 	self.keylist = {}
 | |
| 	self.vallist = {}
 | |
| 	self.deplist = {}
 | |
| end
 | |
| 
 | |
| function ListValue.value(self, key, val, ...)
 | |
| 	if luci.util.contains(self.keylist, key) then
 | |
| 		return
 | |
| 	end
 | |
| 
 | |
| 	val = val or key
 | |
| 	table.insert(self.keylist, tostring(key))
 | |
| 	table.insert(self.vallist, tostring(val))
 | |
| 	table.insert(self.deplist, {...})
 | |
| end
 | |
| 
 | |
| function ListValue.validate(self, val)
 | |
| 	if luci.util.contains(self.keylist, val) then
 | |
| 		return val
 | |
| 	else
 | |
| 		return nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| 
 | |
| 
 | |
| --[[
 | |
| MultiValue - Multiple delimited values
 | |
| 	widget: The widget that will be used (select, checkbox)
 | |
| 	delimiter: The delimiter that will separate the values (default: " ")
 | |
| ]]--
 | |
| MultiValue = class(AbstractValue)
 | |
| 
 | |
| function MultiValue.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template = "cbi/mvalue"
 | |
| 
 | |
| 	self.widget = "checkbox"
 | |
| 	self.delimiter = " "
 | |
| 
 | |
| 	self:reset_values()
 | |
| end
 | |
| 
 | |
| function MultiValue.render(self, ...)
 | |
| 	if self.widget == "select" and not self.size then
 | |
| 		self.size = #self.vallist
 | |
| 	end
 | |
| 
 | |
| 	AbstractValue.render(self, ...)
 | |
| end
 | |
| 
 | |
| function MultiValue.reset_values(self)
 | |
| 	self.keylist = {}
 | |
| 	self.vallist = {}
 | |
| 	self.deplist = {}
 | |
| end
 | |
| 
 | |
| function MultiValue.value(self, key, val)
 | |
| 	if luci.util.contains(self.keylist, key) then
 | |
| 		return
 | |
| 	end
 | |
| 
 | |
| 	val = val or key
 | |
| 	table.insert(self.keylist, tostring(key))
 | |
| 	table.insert(self.vallist, tostring(val))
 | |
| end
 | |
| 
 | |
| function MultiValue.valuelist(self, section)
 | |
| 	local val = self:cfgvalue(section)
 | |
| 
 | |
| 	if not(type(val) == "string") then
 | |
| 		return {}
 | |
| 	end
 | |
| 
 | |
| 	return luci.util.split(val, self.delimiter)
 | |
| end
 | |
| 
 | |
| function MultiValue.validate(self, val)
 | |
| 	val = (type(val) == "table") and val or {val}
 | |
| 
 | |
| 	local result
 | |
| 
 | |
| 	for i, value in ipairs(val) do
 | |
| 		if luci.util.contains(self.keylist, value) then
 | |
| 			result = result and (result .. self.delimiter .. value) or value
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return result
 | |
| end
 | |
| 
 | |
| 
 | |
| StaticList = class(MultiValue)
 | |
| 
 | |
| function StaticList.__init__(self, ...)
 | |
| 	MultiValue.__init__(self, ...)
 | |
| 	self.cast = "table"
 | |
| 	self.valuelist = self.cfgvalue
 | |
| 
 | |
| 	if not self.override_scheme
 | |
| 	 and self.map:get_scheme(self.section.sectiontype, self.option) then
 | |
| 		local vs = self.map:get_scheme(self.section.sectiontype, self.option)
 | |
| 		if self.value and vs.values and not self.override_values then
 | |
| 			for k, v in pairs(vs.values) do
 | |
| 				self:value(k, v)
 | |
| 			end
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function StaticList.validate(self, value)
 | |
| 	value = (type(value) == "table") and value or {value}
 | |
| 
 | |
| 	local valid = {}
 | |
| 	for i, v in ipairs(value) do
 | |
| 		if luci.util.contains(self.keylist, v) then
 | |
| 			table.insert(valid, v)
 | |
| 		end
 | |
| 	end
 | |
| 	return valid
 | |
| end
 | |
| 
 | |
| 
 | |
| DynamicList = class(AbstractValue)
 | |
| 
 | |
| function DynamicList.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/dynlist"
 | |
| 	self.cast = "table"
 | |
| 	self:reset_values()
 | |
| end
 | |
| 
 | |
| function DynamicList.reset_values(self)
 | |
| 	self.keylist = {}
 | |
| 	self.vallist = {}
 | |
| end
 | |
| 
 | |
| function DynamicList.value(self, key, val)
 | |
| 	val = val or key
 | |
| 	table.insert(self.keylist, tostring(key))
 | |
| 	table.insert(self.vallist, tostring(val))
 | |
| end
 | |
| 
 | |
| function DynamicList.write(self, section, value)
 | |
| 	local t = { }
 | |
| 
 | |
| 	if type(value) == "table" then
 | |
| 		local x
 | |
| 		for _, x in ipairs(value) do
 | |
| 			if x and #x > 0 then
 | |
| 				t[#t+1] = x
 | |
| 			end
 | |
| 		end
 | |
| 	else
 | |
| 		t = { value }
 | |
| 	end
 | |
| 
 | |
| 	if self.cast == "string" then
 | |
| 		value = table.concat(t, " ")
 | |
| 	else
 | |
| 		value = t
 | |
| 	end
 | |
| 
 | |
| 	return AbstractValue.write(self, section, value)
 | |
| end
 | |
| 
 | |
| function DynamicList.cfgvalue(self, section)
 | |
| 	local value = AbstractValue.cfgvalue(self, section)
 | |
| 
 | |
| 	if type(value) == "string" then
 | |
| 		local x
 | |
| 		local t = { }
 | |
| 		for x in value:gmatch("%S+") do
 | |
| 			if #x > 0 then
 | |
| 				t[#t+1] = x
 | |
| 			end
 | |
| 		end
 | |
| 		value = t
 | |
| 	end
 | |
| 
 | |
| 	return value
 | |
| end
 | |
| 
 | |
| function DynamicList.formvalue(self, section)
 | |
| 	local value = AbstractValue.formvalue(self, section)
 | |
| 
 | |
| 	if type(value) == "string" then
 | |
| 		if self.cast == "string" then
 | |
| 			local x
 | |
| 			local t = { }
 | |
| 			for x in value:gmatch("%S+") do
 | |
| 				t[#t+1] = x
 | |
| 			end
 | |
| 			value = t
 | |
| 		else
 | |
| 			value = { value }
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return value
 | |
| end
 | |
| 
 | |
| 
 | |
| --[[
 | |
| TextValue - A multi-line value
 | |
| 	rows:	Rows
 | |
| ]]--
 | |
| TextValue = class(AbstractValue)
 | |
| 
 | |
| function TextValue.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/tvalue"
 | |
| end
 | |
| 
 | |
| --[[
 | |
| Button
 | |
| ]]--
 | |
| Button = class(AbstractValue)
 | |
| 
 | |
| function Button.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template  = "cbi/button"
 | |
| 	self.inputstyle = nil
 | |
| 	self.rmempty = true
 | |
|         self.unsafeupload = false
 | |
| end
 | |
| 
 | |
| 
 | |
| FileUpload = class(AbstractValue)
 | |
| 
 | |
| function FileUpload.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template = "cbi/upload"
 | |
| 	if not self.map.upload_fields then
 | |
| 		self.map.upload_fields = { self }
 | |
| 	else
 | |
| 		self.map.upload_fields[#self.map.upload_fields+1] = self
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function FileUpload.formcreated(self, section)
 | |
| 	if self.unsafeupload then
 | |
| 		return AbstractValue.formcreated(self, section) or
 | |
| 			self.map:formvalue("cbi.rlf."..section.."."..self.option) or
 | |
| 			self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") or
 | |
| 			self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
 | |
| 	else
 | |
| 		return AbstractValue.formcreated(self, section) or
 | |
| 			self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
 | |
| 	end
 | |
| end
 | |
| 
 | |
| function FileUpload.cfgvalue(self, section)
 | |
| 	local val = AbstractValue.cfgvalue(self, section)
 | |
| 	if val and fs.access(val) then
 | |
| 		return val
 | |
| 	end
 | |
| 	return nil
 | |
| end
 | |
| 
 | |
| -- If we have a new value, use it
 | |
| -- otherwise use old value
 | |
| -- deletion should be managed by a separate button object
 | |
| -- unless self.unsafeupload is set in which case if the user
 | |
| -- choose to remove the old file we do so.
 | |
| -- Also, allow to specify (via textbox) a file already on router
 | |
| function FileUpload.formvalue(self, section)
 | |
| 	local val = AbstractValue.formvalue(self, section)
 | |
| 	if val then
 | |
| 		if self.unsafeupload then
 | |
| 			if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
 | |
| 		   	    not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
 | |
| 			then
 | |
| 				return val
 | |
| 			end
 | |
| 			fs.unlink(val)
 | |
| 			self.value = nil
 | |
| 			return nil
 | |
|                 elseif val ~= "" then
 | |
| 			return val
 | |
|                 end
 | |
| 	end
 | |
| 	val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
 | |
| 	if val == "" then
 | |
| 		val = nil
 | |
| 	end
 | |
|         if not self.unsafeupload then
 | |
| 		if not val then
 | |
| 			val = self.map:formvalue("cbi.rlf."..section.."."..self.option)
 | |
| 		end
 | |
|         end
 | |
| 	return val
 | |
| end
 | |
| 
 | |
| function FileUpload.remove(self, section)
 | |
| 	if self.unsafeupload then
 | |
| 		local val = AbstractValue.formvalue(self, section)
 | |
| 		if val and fs.access(val) then fs.unlink(val) end
 | |
| 		return AbstractValue.remove(self, section)
 | |
| 	else
 | |
| 		return nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| FileBrowser = class(AbstractValue)
 | |
| 
 | |
| function FileBrowser.__init__(self, ...)
 | |
| 	AbstractValue.__init__(self, ...)
 | |
| 	self.template = "cbi/browser"
 | |
| end
 |