module("luci.cbi", package.seeall)
require("luci.template")
-require("luci.util")
+local util = require("luci.util")
require("luci.http")
-require("luci.uvl")
+
+--local event = require "luci.sys.event"
+local fs = require("nixio.fs")
local uci = require("luci.model.uci")
-local class = luci.util.class
-local instanceof = luci.util.instanceof
+local datatypes = require("luci.cbi.datatypes")
+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
-- Loads a CBI map from given file, creating an environment and returns it
function load(cbimap, ...)
- require("luci.fs")
+ local fs = require "nixio.fs"
local i18n = require "luci.i18n"
require("luci.config")
require("luci.util")
+ local upldir = "/lib/uci/upload/"
local cbidir = luci.util.libpath() .. "/model/cbi/"
- local func, err = loadfile(cbimap) or loadfile(cbidir..cbimap..".lua")
+ 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)
- luci.i18n.loadc("cbi")
- luci.i18n.loadc("uvl")
+ luci.i18n.loadc("base")
local env = {
translate=i18n.translate,
return rawget(tbl, key) or _M[key] or _G[key]
end}))
- local maps = {func()}
+ 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
- end
- end
-
- return maps
-end
-
-local function _uvl_validate_section(node, name)
- local co = node.map:get()
-
- luci.uvl.STRICT_UNKNOWN_OPTIONS = false
- luci.uvl.STRICT_UNKNOWN_SECTIONS = false
-
- local function tag_fields(e)
- if e.option and node.fields[e.option] then
- node.fields[e.option].error = e
- elseif e.childs then
- for _, c in ipairs(e.childs) do tag_fields(c) end
- end
- end
-
- local function tag_section(e)
- local s = { }
- for _, c in ipairs(e.childs) do
- if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
- table.insert( s, c.childs[1]:string() )
- else
- table.insert( s, c:string() )
+ 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 .. '.' ..
+ field.option
+ ] = true
+ end
end
end
- if #s > 0 then node.error = s end
- end
-
- local stat, err = node.map.validator:validate_section(node.config, name, co)
- if err then
- node.map.save = false
- tag_fields(err)
- tag_section(err)
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 )
+ if t and 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
-local function _uvl_strip_remote_dependencies(deps)
- local clean = {}
+ if field.name == cbid and fd then
+ fd:write(chunk)
+ end
- for k, v in pairs(deps) do
- k = k:gsub("%$config%.%$section%.", "")
- if k:match("^[%w_]+$") and type(v) == "string" then
- clean[k] = v
- end
+ if eof and fd then
+ fd:close()
+ fd = nil
+ cbid = nil
+ end
+ end
+ )
end
- return clean
+ return maps
end
self.template = "cbi/node"
end
--- i18n helper
-function Node._i18n(self, config, section, option, title, description)
-
- -- i18n loaded?
- if type(luci.i18n) == "table" then
-
- local key = config and config:gsub("[^%w]+", "") or ""
+-- hook helper
+function Node._run_hook(self, hook)
+ if type(self[hook]) == "function" then
+ return self[hook](self)
+ end
+end
- if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
- if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") 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
- self.title = title or luci.i18n.translate( key, option or section or config )
- self.description = description or luci.i18n.translate( key .. "_desc", "" )
+-- Prepare nodes
+function Node.prepare(self, ...)
+ for k, child in ipairs(self.children) do
+ child:prepare(...)
end
end
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
function Map.__init__(self, config, ...)
Node.__init__(self, ...)
- Node._i18n(self, config, nil, nil, ...)
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
+
if not self.uci:load(self.config) then
error("Unable to read UCI data: " .. self.config)
end
+end
- self.validator = luci.uvl.UVL()
- self.scheme = self.validator:get_scheme(self.config)
+function Map.formvalue(self, key)
+ return self.readinput and luci.http.formvalue(key)
+end
+function Map.formvaluetable(self, key)
+ return self.readinput and luci.http.formvaluetable(key) or {}
end
function Map.get_scheme(self, 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, ...)
+function Map.parse(self, readinput, ...)
+ self.readinput = (readinput ~= false)
+ self:_run_hooks("on_parse")
+
+ if self:formvalue("cbi.skip") then
+ self.state = FORM_SKIP
+ return self:state_handler(self.state)
+ end
+
Node.parse(self, ...)
if self.save then
for i, config in ipairs(self.parsechain) do
self.uci:save(config)
end
- if luci.http.formvalue("cbi.apply") then
+ if self:submitstate() and ((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)
- self.uci:apply(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
+ self._apply = function()
+ local cmd = self.uci:apply(self.parsechain, true)
+ return io.popen(cmd)
+ end
+ end
-- Reparse sections
- Node.parse(self, ...)
+ 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 self:submitstate() then
+ if not self.save then
+ self.state = FORM_INVALID
+ elseif self.proceed then
+ self.state = FORM_PROCEED
+ else
+ self.state = self.changed and FORM_CHANGED or FORM_VALID
+ end
+ else
+ self.state = FORM_NODATA
+ end
+
+ return self:state_handler(self.state)
+end
+
+function Map.render(self, ...)
+ self:_run_hooks("on_init")
+ Node.render(self, ...)
+ if false and self._apply then
+ local fp = self._apply()
+ fp:read("*a")
+ fp:close()
+ self:_run_hooks("on_apply")
end
end
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
self.data = data or {}
self.template = "cbi/simpleform"
self.dorender = true
+ self.pageaction = false
+ self.readinput = true
end
-function SimpleForm.parse(self, ...)
- if luci.http.formvalue("cbi.submit") then
+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
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 luci.http.formvalue("cbi.submit") and 0
- or valid and 1
- or -1
+ not self:submitstate() and FORM_NODATA
+ or valid and FORM_VALID
+ or FORM_INVALID
- self.dorender = not self.handle or self:handle(state, self.data) ~= false
+ 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, ...)
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, ...)
end
+Form = class(SimpleForm)
+
+function Form.__init__(self, ...)
+ SimpleForm.__init__(self, ...)
+ self.embedded = true
+end
+
--[[
AbstractSection
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, ...)
- -- Autodetect from UVL
- if class == true and self.map:get_scheme(self.sectiontype, option) then
- local vs = self.map:get_scheme(self.sectiontype, option)
- if vs.type == "boolean" then
- class = Flag
- elseif vs.type == "list" then
- class = DynamicList
- elseif vs.type == "enum" or vs.type == "reference" then
- class = ListValue
- else
- class = Value
- end
- end
-
if instanceof(class, AbstractValue) then
local obj = class(self.map, self, option, ...)
-
- Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
-
self:append(obj)
self.fields[option] = obj
return obj
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))
+
+ for _, node in ipairs(self.tabs[tab].childs) do
+ node:render(...)
+ end
+end
+
-- Parse optional options
function AbstractSection.parse_optionals(self, section)
if not self.optional then
self.optionals[section] = {}
- local field = luci.http.formvalue("cbi.opt."..self.config.."."..section)
+ local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
for k,v in ipairs(self.children) do
- if v.optional and not v:cfgvalue(section) then
+ 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
local arr = luci.util.clone(self:cfgvalue(section))
- local form = luci.http.formvaluetable("cbid."..self.config.."."..section)
+ local form = self.map:formvaluetable("cbid."..self.config.."."..section)
for k, v in pairs(form) do
arr[k] = v
end
end
if create and key:sub(1, 1) ~= "." then
+ self.map.proceed = true
self:add_dynamic(key, true)
end
end
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
local stat
if section then
- stat = self.map:set(section, nil, self.sectiontype)
+ stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
else
section = self.map:add(self.sectiontype)
stat = section
end
end
+ self.map.proceed = true
+
return stat
end
function Table.__init__(self, form, data, ...)
local datasource = {}
+ local tself = self
datasource.config = "table"
- self.data = data
+ self.data = data or {}
+
+ datasource.formvalue = Map.formvalue
+ datasource.formvaluetable = Map.formvaluetable
+ datasource.readinput = true
function datasource.get(self, section, option)
- return data[section] and data[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(...)
self.anonymous = true
end
-function Table.parse(self)
+function Table.parse(self, readinput)
+ self.map.readinput = (readinput ~= false)
for i, k in ipairs(self:cfgsections()) do
- if luci.http.formvalue("cbi.submit") then
+ if self.map:submitstate() then
Node.parse(self, k)
end
end
return sections
end
+function Table.update(self, data)
+ self.data = data
+end
+
--[[
function NamedSection.__init__(self, map, section, stype, ...)
AbstractSection.__init__(self, map, stype, ...)
- Node._i18n(self, map.config, section, nil, ...)
-- Defaults
self.addremove = false
-
- -- Use defaults from UVL
- if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
- local vs = self.map:get_scheme(self.sectiontype)
- self.addremove = not vs.unique and not vs.required
- self.dynamic = vs.dynamic
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- end
-
self.template = "cbi/nsection"
self.section = section
end
-function NamedSection.parse(self)
+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 luci.http.formvalue("cbi.rns."..path) and self:remove(s) then
+ if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
+ self:push_events()
return
end
else -- Create and apply default values
- if luci.http.formvalue("cbi.cns."..path) then
+ if self.map:formvalue("cbi.cns."..path) then
self:create(s)
return
end
if active then
AbstractSection.parse_dynamic(self, s)
- if luci.http.formvalue("cbi.submit") then
+ if self.map:submitstate() then
Node.parse(self, s)
-
- if not self.override_scheme and self.map.scheme then
- _uvl_validate_section(self, s)
- end
end
AbstractSection.parse_optionals(self, s)
+
+ if self.changed then
+ self:push_events()
+ end
end
end
function TypedSection.__init__(self, map, type, ...)
AbstractSection.__init__(self, map, type, ...)
- Node._i18n(self, map.config, type, nil, ...)
- self.template = "cbi/tsection"
+ self.template = "cbi/tsection"
self.deps = {}
self.anonymous = false
-
- -- Use defaults from UVL
- if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
- local vs = self.map:get_scheme(self.sectiontype)
- self.addremove = not vs.unique and not vs.required
- self.dynamic = vs.dynamic
- self.anonymous = not vs.named
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- end
end
-- Return all matching UCI sections for this TypedSection
table.insert(self.deps, {option=option, value=value})
end
-function TypedSection.parse(self)
+function TypedSection.parse(self, novld)
if self.addremove then
-- Remove
local crval = REMOVE_PREFIX .. self.config
- local name = luci.http.formvaluetable(crval)
+ 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)
local co
for i, k in ipairs(self:cfgsections()) do
AbstractSection.parse_dynamic(self, k)
- if luci.http.formvalue("cbi.submit") then
- Node.parse(self, k)
-
- if not self.override_scheme and self.map.scheme then
- _uvl_validate_section(self, k)
- end
+ if self.map:submitstate() then
+ Node.parse(self, k, novld)
end
AbstractSection.parse_optionals(self, k)
end
-- Create
local created
local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
- local name = luci.http.formvalue(crval)
+ local name = self.map:formvalue(crval)
if self.anonymous then
if name then
created = self:create()
if name and #name > 0 then
created = self:create(name) and name
+ if not created then
+ self.invalid_cts = true
+ end
end
end
end
AbstractSection.parse_optionals(self, created)
end
end
+
+ if created or self.changed then
+ self:push_events()
+ end
end
-- Verifies scope of sections
self.tag_reqerror = {}
self.tag_error = {}
self.deps = {}
- self.cast = "string"
+ self.subdeps = {}
+ --self.cast = "string"
self.track_missing = false
- self.rmempty = false
+ self.rmempty = true
self.default = nil
self.size = nil
self.optional = false
+end
- -- Use defaults from UVL
- 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)
- self.rmempty = not vs.required
- self.cast = (vs.type == "list") and "list" or "string"
- self.title = self.title or vs.title
- self.description = self.description or vs.descr
- self.default = vs.default
-
- if vs.depends and not self.override_dependencies then
- for i, deps in ipairs(vs.depends) do
- deps = _uvl_strip_remote_dependencies(deps)
- if next(deps) then
- self:depends(deps)
- end
- end
- end
- end
+function AbstractValue.prepare(self)
+ self.cast = self.cast or "string"
end
-- Add a dependencie to another section field
-- Return whether this object should be created
function AbstractValue.formcreated(self, section)
local key = "cbi.opt."..self.config.."."..section
- return (luci.http.formvalue(key) == self.option)
+ return (self.map:formvalue(key) == self.option)
end
-- Returns the formvalue for this object
function AbstractValue.formvalue(self, section)
- return luci.http.formvalue(self:cbid(section))
+ return self.map:formvalue(self:cbid(section))
end
function AbstractValue.additional(self, value)
self.rmempty = not value
end
-function AbstractValue.parse(self, section)
+function AbstractValue.parse(self, section, novld)
local fvalue = self:formvalue(section)
local cvalue = self:cfgvalue(section)
- if fvalue and fvalue ~= "" then -- If we have a form value, write it to UCI
+ -- 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
fvalue = self:transform(self:validate(fvalue, section))
- if not fvalue then
- self.tag_invalid[section] = true
+ if not fvalue and not novld then
+ if self.error then
+ self.error[section] = "invalid"
+ else
+ self.error = { [section] = "invalid" }
+ end
+ if self.section.error then
+ table.insert(self.section.error[section], "invalid")
+ else
+ self.section.error = {[section] = {"invalid"}}
+ end
+ self.map.save = false
end
if fvalue and not (fvalue == cvalue) then
- self:write(section, fvalue)
+ 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
- self:remove(section)
- elseif self.track_missing and (not fvalue or fvalue ~= cvalue) then
- self.tag_missing[section] = true
+ 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
+ self:write(section, fvalue or "")
+ if self.error then
+ self.error[section] = "missing"
+ else
+ self.error = { [section] = "missing" }
+ end
+ self.map.save = false
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:cfgvalue(s) or self:formcreated(s) then
+ 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)
scope.striptags = luci.util.striptags
+ scope.pcdata = luci.util.pcdata
scope.ifattr = function(cond,key,val)
if cond then
-- Return the UCI value of this object
function AbstractValue.cfgvalue(self, section)
- local value = self.map:get(section, self.option)
- if not self.cast or self.cast == type(value) then
+ local value = (self.error and self.error[section] == "invalid")
+ and self:formvalue(section) or self.map:get(section, self.option)
+ 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}
+ return luci.util.split(value, "%s+", nil, true)
end
end
-- Validate the form value
function AbstractValue.validate(self, value)
+ if self.datatype and value and datatypes[self.datatype] then
+ if type(value) == "table" then
+ local v
+ for _, v in ipairs(value) do
+ if v and #v > 0 and not datatypes[self.datatype](v) then
+ return nil
+ end
+ end
+ else
+ if not datatypes[self.datatype](value) then
+ return nil
+ end
+ end
+ end
return value
end
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
self.vallist = {}
self.size = 1
self.widget = "select"
-
- 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
- if self.rmempty or self.optional then
- self:value("")
- end
- for k, v in pairs(vs.values) do
- local deps = {}
- if not self.override_dependencies
- and vs.enum_depends and vs.enum_depends[k] then
- for i, dep in ipairs(vs.enum_depends[k]) do
- table.insert(deps, _uvl_strip_remote_dependencies(dep))
- end
- end
- self:value(k, v, unpack(deps))
- end
- end
- end
end
function ListValue.value(self, key, val, ...)
table.insert(self.vallist, tostring(val))
for i, deps in ipairs({...}) do
- table.insert(self.deps, {add = "-"..key, deps=deps})
+ self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
end
end
local valid = {}
for i, v in ipairs(value) do
- if luci.util.contains(self.valuelist, v) then
+ if luci.util.contains(self.keylist, v) then
table.insert(valid, v)
end
end
table.insert(self.vallist, tostring(val))
end
-function DynamicList.validate(self, value, section)
+function DynamicList.write(self, ...)
+ self.map.proceed = true
+ return AbstractValue.write(self, ...)
+end
+
+function DynamicList.formvalue(self, section)
+ local value = AbstractValue.formvalue(self, section)
value = (type(value) == "table") and value or {value}
local valid = {}
for i, v in ipairs(value) do
if v and #v > 0
- and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i)
- and not luci.http.formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
+ and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
+ and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
table.insert(valid, v)
end
end
self.inputstyle = nil
self.rmempty = true
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)
+ return AbstractValue.formcreated(self, section) or
+ self.map:formvalue("cbi.rlf."..section.."."..self.option) or
+ self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+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
+
+function FileUpload.formvalue(self, section)
+ local val = AbstractValue.formvalue(self, section)
+ if val 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
+ end
+ return nil
+end
+
+function FileUpload.remove(self, section)
+ local val = AbstractValue.formvalue(self, section)
+ if val and fs.access(val) then fs.unlink(val) end
+ return AbstractValue.remove(self, section)
+end
+
+
+FileBrowser = class(AbstractValue)
+
+function FileBrowser.__init__(self, ...)
+ AbstractValue.__init__(self, ...)
+ self.template = "cbi/browser"
+end