OpenWrt_Luci_Lua/1_1.mi_Lua/luci/cbi.lua

1851 lines
39 KiB
Lua
Raw Permalink Normal View History

2015-06-28 06:39:56 +00:00
--[[
LuCI - Configuration Bind Interface
Description:
Offers an interface for binding configuration values to certain
data types. Supports value and range validation and basic dependencies.
FileId:
$Id: cbi.lua 9558 2012-12-18 13:58:22Z jow $
License:
Copyright 2008 Steven Barth <steven@midlink.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--
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 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 = "/lib/uci/upload/"
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: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
if not self.uci:load(self.config) then
error("Unable to read UCI data: " .. self.config)
end
end
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)
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, ...)
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
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 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)
-- 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 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, ...)
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:render(...)
end
end
-- Parse optional options
function AbstractSection.parse_optionals(self, section)
if not self.optional then
return
end
self.optionals[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) 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.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
-- 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.subdeps = {}
--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=deps, add=""})
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 = {}
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
-- 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)
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
if fvalue ~= self.default or (not self.optional and not self.rmempty) then
self:write(section, fvalue)
else
self:remove(section)
end
else
self:remove(section)
end
end
function Flag.cfgvalue(self, section)
return AbstractValue.cfgvalue(self, section) or self.default
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.keylist = {}
self.vallist = {}
self.size = 1
self.widget = "select"
end
function ListValue.reset_values(self)
self.keylist = {}
self.vallist = {}
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))
for i, deps in ipairs({...}) do
self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
end
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.keylist = {}
self.vallist = {}
self.widget = "checkbox"
self.delimiter = " "
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 = {}
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.keylist = {}
self.vallist = {}
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
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