2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("base")
80 translate=i18n.translate,
81 translatef=i18n.translatef,
85 setfenv(func, setmetatable(env, {__index =
87 return rawget(tbl, key) or _M[key] or _G[key]
90 local maps = { func() }
92 local has_upload = false
94 for i, map in ipairs(maps) do
95 if not instanceof(map, Node) then
96 error("CBI map returns no valid map object!")
100 if map.upload_fields then
102 for _, field in ipairs(map.upload_fields) do
104 field.config .. '.' ..
105 field.section.sectiontype .. '.' ..
114 local uci = luci.model.uci.cursor()
115 local prm = luci.http.context.request.message.params
118 luci.http.setfilehandler(
119 function( field, chunk, eof )
120 if not field then return end
121 if field.name and not cbid then
122 local c, s, o = field.name:gmatch(
123 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
126 if c and s and o then
127 local t = uci:get( c, s )
128 if t and uploads[c.."."..t.."."..o] then
129 local path = upldir .. field.name
130 fd = io.open(path, "w")
139 if field.name == cbid and fd then
155 local function _uvl_validate_section(node, name)
156 local co = node.map:get()
158 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
159 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161 local function tag_fields(e)
162 if e.option and node.fields[e.option] then
163 if node.fields[e.option].error then
164 node.fields[e.option].error[name] = e
166 node.fields[e.option].error = { [name] = e }
169 for _, c in ipairs(e.childs) do tag_fields(c) end
173 local function tag_section(e)
175 for _, c in ipairs(e.childs or { e }) do
176 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
177 table.insert( s, c.childs[1]:string() )
179 table.insert( s, c:string() )
186 node.error = { [name] = s }
191 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 node.map.save = false
200 local function _uvl_strip_remote_dependencies(deps)
203 for k, v in pairs(deps) do
204 k = k:gsub("%$config%.%$section%.", "")
205 if k:match("^[%w_]+$") and type(v) == "string" then
214 -- Node pseudo abstract class
217 function Node.__init__(self, title, description)
219 self.title = title or ""
220 self.description = description or ""
221 self.template = "cbi/node"
225 function Node._run_hooks(self, ...)
228 for _, f in ipairs(arg) do
229 if type(self[f]) == "function" then
238 function Node.prepare(self, ...)
239 for k, child in ipairs(self.children) do
244 -- Append child nodes
245 function Node.append(self, obj)
246 table.insert(self.children, obj)
249 -- Parse this node and its children
250 function Node.parse(self, ...)
251 for k, child in ipairs(self.children) do
257 function Node.render(self, scope)
261 luci.template.render(self.template, scope)
264 -- Render the children
265 function Node.render_children(self, ...)
266 for k, node in ipairs(self.children) do
273 A simple template element
275 Template = class(Node)
277 function Template.__init__(self, template)
279 self.template = template
282 function Template.render(self)
283 luci.template.render(self.template, {self=self})
286 function Template.parse(self, readinput)
287 self.readinput = (readinput ~= false)
288 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
293 Map - A map describing a configuration file
297 function Map.__init__(self, config, ...)
298 Node.__init__(self, ...)
301 self.parsechain = {self.config}
302 self.template = "cbi/map"
303 self.apply_on_parse = nil
304 self.readinput = true
308 self.uci = uci.cursor()
313 if not self.uci:load(self.config) then
314 error("Unable to read UCI data: " .. self.config)
317 self.validator = luci.uvl.UVL()
318 self.scheme = self.validator:get_scheme(self.config)
321 function Map.formvalue(self, key)
322 return self.readinput and luci.http.formvalue(key)
325 function Map.formvaluetable(self, key)
326 return self.readinput and luci.http.formvaluetable(key) or {}
329 function Map.get_scheme(self, sectiontype, option)
331 return self.scheme and self.scheme.sections[sectiontype]
333 return self.scheme and self.scheme.variables[sectiontype]
334 and self.scheme.variables[sectiontype][option]
338 function Map.submitstate(self)
339 return self:formvalue("cbi.submit")
342 -- Chain foreign config
343 function Map.chain(self, config)
344 table.insert(self.parsechain, config)
347 function Map.state_handler(self, state)
351 -- Use optimized UCI writing
352 function Map.parse(self, readinput, ...)
353 self.readinput = (readinput ~= false)
355 if self:formvalue("cbi.skip") then
356 self.state = FORM_SKIP
357 return self:state_handler(self.state)
360 Node.parse(self, ...)
363 for i, config in ipairs(self.parsechain) do
364 self.uci:save(config)
366 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
367 self:_run_hooks("on_before_commit")
368 for i, config in ipairs(self.parsechain) do
369 self.uci:commit(config)
371 -- Refresh data because commit changes section names
372 self.uci:load(config)
374 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
375 if self.apply_on_parse then
376 self.uci:apply(self.parsechain)
377 self:_run_hooks("on_apply", "on_after_apply")
379 self._apply = function()
380 local cmd = self.uci:apply(self.parsechain, true)
386 Node.parse(self, true)
389 for i, config in ipairs(self.parsechain) do
390 self.uci:unload(config)
392 if type(self.commit_handler) == "function" then
393 self:commit_handler(self:submitstate())
397 if self:submitstate() then
398 if not self.save then
399 self.state = FORM_INVALID
400 elseif self.proceed then
401 self.state = FORM_PROCEED
403 self.state = self.changed and FORM_CHANGED or FORM_VALID
406 self.state = FORM_NODATA
409 return self:state_handler(self.state)
412 function Map.render(self, ...)
413 self:_run_hooks("on_init")
414 Node.render(self, ...)
416 local fp = self._apply()
419 self:_run_hooks("on_apply")
423 -- Creates a child section
424 function Map.section(self, class, ...)
425 if instanceof(class, AbstractSection) then
426 local obj = class(self, ...)
430 error("class must be a descendent of AbstractSection")
435 function Map.add(self, sectiontype)
436 return self.uci:add(self.config, sectiontype)
440 function Map.set(self, section, option, value)
442 return self.uci:set(self.config, section, option, value)
444 return self.uci:set(self.config, section, value)
449 function Map.del(self, section, option)
451 return self.uci:delete(self.config, section, option)
453 return self.uci:delete(self.config, section)
458 function Map.get(self, section, option)
460 return self.uci:get_all(self.config)
462 return self.uci:get(self.config, section, option)
464 return self.uci:get_all(self.config, section)
471 Compound = class(Node)
473 function Compound.__init__(self, ...)
475 self.template = "cbi/compound"
476 self.children = {...}
479 function Compound.populate_delegator(self, delegator)
480 for _, v in ipairs(self.children) do
481 v.delegator = delegator
485 function Compound.parse(self, ...)
486 local cstate, state = 0
488 for k, child in ipairs(self.children) do
489 cstate = child:parse(...)
490 state = (not state or cstate < state) and cstate or state
498 Delegator - Node controller
500 Delegator = class(Node)
501 function Delegator.__init__(self, ...)
502 Node.__init__(self, ...)
504 self.defaultpath = {}
505 self.pageaction = false
506 self.readinput = true
507 self.allow_reset = false
508 self.allow_cancel = false
509 self.allow_back = false
510 self.allow_finish = false
511 self.template = "cbi/delegator"
514 function Delegator.set(self, name, node)
515 if type(node) == "table" and getmetatable(node) == nil then
516 node = Compound(unpack(node))
518 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
519 assert(not self.nodes[name], "Duplicate entry")
521 self.nodes[name] = node
524 function Delegator.add(self, name, node)
525 node = self:set(name, node)
526 self.defaultpath[#self.defaultpath+1] = name
529 function Delegator.insert_after(self, name, after)
530 local n = #self.chain
531 for k, v in ipairs(self.chain) do
537 table.insert(self.chain, n, name)
540 function Delegator.set_route(self, ...)
541 local n, chain, route = 0, self.chain, {...}
543 if chain[i] == self.current then
552 for i = n + 1, #chain do
557 function Delegator.get(self, name)
558 return self.nodes[name]
561 function Delegator.parse(self, ...)
562 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
563 if self:_run_hooks("on_cancel") then
568 if not Map.formvalue(self, "cbi.delg.current") then
569 self:_run_hooks("on_init")
573 self.chain = self.chain or self:get_chain()
574 self.current = self.current or self:get_active()
575 self.active = self.active or self:get(self.current)
576 assert(self.active, "Invalid state")
578 local stat = FORM_DONE
579 if type(self.active) ~= "function" then
580 self.active:populate_delegator(self)
581 stat = self.active:parse()
586 if stat > FORM_PROCEED then
587 if Map.formvalue(self, "cbi.delg.back") then
588 newcurrent = self:get_prev(self.current)
590 newcurrent = self:get_next(self.current)
592 elseif stat < FORM_PROCEED then
597 if not Map.formvalue(self, "cbi.submit") then
599 elseif stat > FORM_PROCEED
600 and (not newcurrent or not self:get(newcurrent)) then
601 self:_run_hooks("on_done")
604 self.current = newcurrent or self.current
605 self.active = self:get(self.current)
606 if type(self.active) ~= "function" then
607 self.active:parse(false)
610 return self:parse(...)
615 function Delegator.get_next(self, state)
616 for k, v in ipairs(self.chain) do
618 return self.chain[k+1]
623 function Delegator.get_prev(self, state)
624 for k, v in ipairs(self.chain) do
626 return self.chain[k-1]
631 function Delegator.get_chain(self)
632 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
633 return type(x) == "table" and x or {x}
636 function Delegator.get_active(self)
637 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
645 Page.__init__ = Node.__init__
646 Page.parse = function() end
650 SimpleForm - A Simple non-UCI form
652 SimpleForm = class(Node)
654 function SimpleForm.__init__(self, config, title, description, data)
655 Node.__init__(self, title, description)
657 self.data = data or {}
658 self.template = "cbi/simpleform"
660 self.pageaction = false
661 self.readinput = true
664 SimpleForm.formvalue = Map.formvalue
665 SimpleForm.formvaluetable = Map.formvaluetable
667 function SimpleForm.parse(self, readinput, ...)
668 self.readinput = (readinput ~= false)
670 if self:formvalue("cbi.skip") then
674 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
678 if self:submitstate() then
679 Node.parse(self, 1, ...)
683 for k, j in ipairs(self.children) do
684 for i, v in ipairs(j.children) do
686 and (not v.tag_missing or not v.tag_missing[1])
687 and (not v.tag_invalid or not v.tag_invalid[1])
693 not self:submitstate() and FORM_NODATA
694 or valid and FORM_VALID
697 self.dorender = not self.handle
699 local nrender, nstate = self:handle(state, self.data)
700 self.dorender = self.dorender or (nrender ~= false)
701 state = nstate or state
706 function SimpleForm.render(self, ...)
707 if self.dorender then
708 Node.render(self, ...)
712 function SimpleForm.submitstate(self)
713 return self:formvalue("cbi.submit")
716 function SimpleForm.section(self, class, ...)
717 if instanceof(class, AbstractSection) then
718 local obj = class(self, ...)
722 error("class must be a descendent of AbstractSection")
726 -- Creates a child field
727 function SimpleForm.field(self, class, ...)
729 for k, v in ipairs(self.children) do
730 if instanceof(v, SimpleSection) then
736 section = self:section(SimpleSection)
739 if instanceof(class, AbstractValue) then
740 local obj = class(self, section, ...)
741 obj.track_missing = true
745 error("class must be a descendent of AbstractValue")
749 function SimpleForm.set(self, section, option, value)
750 self.data[option] = value
754 function SimpleForm.del(self, section, option)
755 self.data[option] = nil
759 function SimpleForm.get(self, section, option)
760 return self.data[option]
764 function SimpleForm.get_scheme()
769 Form = class(SimpleForm)
771 function Form.__init__(self, ...)
772 SimpleForm.__init__(self, ...)
780 AbstractSection = class(Node)
782 function AbstractSection.__init__(self, map, sectiontype, ...)
783 Node.__init__(self, ...)
784 self.sectiontype = sectiontype
786 self.config = map.config
791 self.tag_invalid = {}
792 self.tag_deperror = {}
796 self.addremove = false
800 -- Define a tab for the section
801 function AbstractSection.tab(self, tab, title, desc)
802 self.tabs = self.tabs or { }
803 self.tab_names = self.tab_names or { }
805 self.tab_names[#self.tab_names+1] = tab
813 -- Appends a new option
814 function AbstractSection.option(self, class, option, ...)
815 -- Autodetect from UVL
816 if class == true and self.map:get_scheme(self.sectiontype, option) then
817 local vs = self.map:get_scheme(self.sectiontype, option)
818 if vs.type == "boolean" then
820 elseif vs.type == "list" then
822 elseif vs.type == "enum" or vs.type == "reference" then
829 if instanceof(class, AbstractValue) then
830 local obj = class(self.map, self, option, ...)
832 self.fields[option] = obj
834 elseif class == true then
835 error("No valid class was given and autodetection failed.")
837 error("class must be a descendant of AbstractValue")
841 -- Appends a new tabbed option
842 function AbstractSection.taboption(self, tab, ...)
844 assert(tab and self.tabs and self.tabs[tab],
845 "Cannot assign option to not existing tab %q" % tostring(tab))
847 local l = self.tabs[tab].childs
848 local o = AbstractSection.option(self, ...)
850 if o then l[#l+1] = o end
855 -- Render a single tab
856 function AbstractSection.render_tab(self, tab, ...)
858 assert(tab and self.tabs and self.tabs[tab],
859 "Cannot render not existing tab %q" % tostring(tab))
861 for _, node in ipairs(self.tabs[tab].childs) do
866 -- Parse optional options
867 function AbstractSection.parse_optionals(self, section)
868 if not self.optional then
872 self.optionals[section] = {}
874 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
875 for k,v in ipairs(self.children) do
876 if v.optional and not v:cfgvalue(section) then
877 if field == v.option then
879 self.map.proceed = true
881 table.insert(self.optionals[section], v)
886 if field and #field > 0 and self.dynamic then
887 self:add_dynamic(field)
891 -- Add a dynamic option
892 function AbstractSection.add_dynamic(self, field, optional)
893 local o = self:option(Value, field, field)
894 o.optional = optional
897 -- Parse all dynamic options
898 function AbstractSection.parse_dynamic(self, section)
899 if not self.dynamic then
903 local arr = luci.util.clone(self:cfgvalue(section))
904 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
905 for k, v in pairs(form) do
909 for key,val in pairs(arr) do
912 for i,c in ipairs(self.children) do
913 if c.option == key then
918 if create and key:sub(1, 1) ~= "." then
919 self.map.proceed = true
920 self:add_dynamic(key, true)
925 -- Returns the section's UCI table
926 function AbstractSection.cfgvalue(self, section)
927 return self.map:get(section)
931 function AbstractSection.push_events(self)
932 --luci.util.append(self.map.events, self.events)
933 self.map.changed = true
936 -- Removes the section
937 function AbstractSection.remove(self, section)
938 self.map.proceed = true
939 return self.map:del(section)
942 -- Creates the section
943 function AbstractSection.create(self, section)
947 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
949 section = self.map:add(self.sectiontype)
954 for k,v in pairs(self.children) do
956 self.map:set(section, v.option, v.default)
960 for k,v in pairs(self.defaults) do
961 self.map:set(section, k, v)
965 self.map.proceed = true
971 SimpleSection = class(AbstractSection)
973 function SimpleSection.__init__(self, form, ...)
974 AbstractSection.__init__(self, form, nil, ...)
975 self.template = "cbi/nullsection"
979 Table = class(AbstractSection)
981 function Table.__init__(self, form, data, ...)
982 local datasource = {}
984 datasource.config = "table"
985 self.data = data or {}
987 datasource.formvalue = Map.formvalue
988 datasource.formvaluetable = Map.formvaluetable
989 datasource.readinput = true
991 function datasource.get(self, section, option)
992 return tself.data[section] and tself.data[section][option]
995 function datasource.submitstate(self)
996 return Map.formvalue(self, "cbi.submit")
999 function datasource.del(...)
1003 function datasource.get_scheme()
1007 AbstractSection.__init__(self, datasource, "table", ...)
1008 self.template = "cbi/tblsection"
1009 self.rowcolors = true
1010 self.anonymous = true
1013 function Table.parse(self, readinput)
1014 self.map.readinput = (readinput ~= false)
1015 for i, k in ipairs(self:cfgsections()) do
1016 if self.map:submitstate() then
1022 function Table.cfgsections(self)
1025 for i, v in luci.util.kspairs(self.data) do
1026 table.insert(sections, i)
1032 function Table.update(self, data)
1039 NamedSection - A fixed configuration section defined by its name
1041 NamedSection = class(AbstractSection)
1043 function NamedSection.__init__(self, map, section, stype, ...)
1044 AbstractSection.__init__(self, map, stype, ...)
1047 self.addremove = false
1049 -- Use defaults from UVL
1050 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1051 local vs = self.map:get_scheme(self.sectiontype)
1052 self.addremove = not vs.unique and not vs.required
1053 self.dynamic = vs.dynamic
1054 self.title = self.title or vs.title
1055 self.description = self.description or vs.descr
1058 self.template = "cbi/nsection"
1059 self.section = section
1062 function NamedSection.parse(self, novld)
1063 local s = self.section
1064 local active = self:cfgvalue(s)
1066 if self.addremove then
1067 local path = self.config.."."..s
1068 if active then -- Remove the section
1069 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1073 else -- Create and apply default values
1074 if self.map:formvalue("cbi.cns."..path) then
1082 AbstractSection.parse_dynamic(self, s)
1083 if self.map:submitstate() then
1086 if not novld and not self.override_scheme and self.map.scheme then
1087 _uvl_validate_section(self, s)
1090 AbstractSection.parse_optionals(self, s)
1092 if self.changed then
1100 TypedSection - A (set of) configuration section(s) defined by the type
1101 addremove: Defines whether the user can add/remove sections of this type
1102 anonymous: Allow creating anonymous sections
1103 validate: a validation function returning nil if the section is invalid
1105 TypedSection = class(AbstractSection)
1107 function TypedSection.__init__(self, map, type, ...)
1108 AbstractSection.__init__(self, map, type, ...)
1110 self.template = "cbi/tsection"
1112 self.anonymous = false
1114 -- Use defaults from UVL
1115 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1116 local vs = self.map:get_scheme(self.sectiontype)
1117 self.addremove = not vs.unique and not vs.required
1118 self.dynamic = vs.dynamic
1119 self.anonymous = not vs.named
1120 self.title = self.title or vs.title
1121 self.description = self.description or vs.descr
1125 -- Return all matching UCI sections for this TypedSection
1126 function TypedSection.cfgsections(self)
1128 self.map.uci:foreach(self.map.config, self.sectiontype,
1130 if self:checkscope(section[".name"]) then
1131 table.insert(sections, section[".name"])
1138 -- Limits scope to sections that have certain option => value pairs
1139 function TypedSection.depends(self, option, value)
1140 table.insert(self.deps, {option=option, value=value})
1143 function TypedSection.parse(self, novld)
1144 if self.addremove then
1146 local crval = REMOVE_PREFIX .. self.config
1147 local name = self.map:formvaluetable(crval)
1148 for k,v in pairs(name) do
1149 if k:sub(-2) == ".x" then
1150 k = k:sub(1, #k - 2)
1152 if self:cfgvalue(k) and self:checkscope(k) then
1159 for i, k in ipairs(self:cfgsections()) do
1160 AbstractSection.parse_dynamic(self, k)
1161 if self.map:submitstate() then
1162 Node.parse(self, k, novld)
1164 if not novld and not self.override_scheme and self.map.scheme then
1165 _uvl_validate_section(self, k)
1168 AbstractSection.parse_optionals(self, k)
1171 if self.addremove then
1174 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1175 local name = self.map:formvalue(crval)
1176 if self.anonymous then
1178 created = self:create()
1182 -- Ignore if it already exists
1183 if self:cfgvalue(name) then
1187 name = self:checkscope(name)
1190 self.err_invalid = true
1193 if name and #name > 0 then
1194 created = self:create(name) and name
1196 self.invalid_cts = true
1203 AbstractSection.parse_optionals(self, created)
1207 if created or self.changed then
1212 -- Verifies scope of sections
1213 function TypedSection.checkscope(self, section)
1214 -- Check if we are not excluded
1215 if self.filter and not self:filter(section) then
1219 -- Check if at least one dependency is met
1220 if #self.deps > 0 and self:cfgvalue(section) then
1223 for k, v in ipairs(self.deps) do
1224 if self:cfgvalue(section)[v.option] == v.value then
1234 return self:validate(section)
1238 -- Dummy validate function
1239 function TypedSection.validate(self, section)
1245 AbstractValue - An abstract Value Type
1246 null: Value can be empty
1247 valid: A function returning the value if it is valid otherwise nil
1248 depends: A table of option => value pairs of which one must be true
1249 default: The default value
1250 size: The size of the input fields
1251 rmempty: Unset value if empty
1252 optional: This value is optional (see AbstractSection.optionals)
1254 AbstractValue = class(Node)
1256 function AbstractValue.__init__(self, map, section, option, ...)
1257 Node.__init__(self, ...)
1258 self.section = section
1259 self.option = option
1261 self.config = map.config
1262 self.tag_invalid = {}
1263 self.tag_missing = {}
1264 self.tag_reqerror = {}
1267 --self.cast = "string"
1269 self.track_missing = false
1273 self.optional = false
1276 function AbstractValue.prepare(self)
1277 -- Use defaults from UVL
1278 if not self.override_scheme
1279 and self.map:get_scheme(self.section.sectiontype, self.option) then
1280 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1281 if self.cast == nil then
1282 self.cast = (vs.type == "list") and "list" or "string"
1284 self.title = self.title or vs.title
1285 self.description = self.description or vs.descr
1286 if self.default == nil then
1287 self.default = vs.default
1290 if vs.depends and not self.override_dependencies then
1291 for i, deps in ipairs(vs.depends) do
1292 deps = _uvl_strip_remote_dependencies(deps)
1300 self.cast = self.cast or "string"
1303 -- Add a dependencie to another section field
1304 function AbstractValue.depends(self, field, value)
1306 if type(field) == "string" then
1313 table.insert(self.deps, {deps=deps, add=""})
1316 -- Generates the unique CBID
1317 function AbstractValue.cbid(self, section)
1318 return "cbid."..self.map.config.."."..section.."."..self.option
1321 -- Return whether this object should be created
1322 function AbstractValue.formcreated(self, section)
1323 local key = "cbi.opt."..self.config.."."..section
1324 return (self.map:formvalue(key) == self.option)
1327 -- Returns the formvalue for this object
1328 function AbstractValue.formvalue(self, section)
1329 return self.map:formvalue(self:cbid(section))
1332 function AbstractValue.additional(self, value)
1333 self.optional = value
1336 function AbstractValue.mandatory(self, value)
1337 self.rmempty = not value
1340 function AbstractValue.parse(self, section, novld)
1341 local fvalue = self:formvalue(section)
1342 local cvalue = self:cfgvalue(section)
1344 -- If favlue and cvalue are both tables and have the same content
1345 -- make them identical
1346 if type(fvalue) == "table" and type(cvalue) == "table" then
1347 local equal = #fvalue == #cvalue
1350 if cvalue[i] ~= fvalue[i] then
1360 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1361 fvalue = self:transform(self:validate(fvalue, section))
1362 if not fvalue and not novld then
1364 self.error[section] = "invalid"
1366 self.error = { [section] = "invalid" }
1368 if self.section.error then
1369 table.insert(self.section.error[section], "invalid")
1371 self.section.error = {[section] = {"invalid"}}
1373 self.map.save = false
1375 if fvalue and not (fvalue == cvalue) then
1376 if self:write(section, fvalue) then
1378 self.section.changed = true
1379 --luci.util.append(self.map.events, self.events)
1382 else -- Unset the UCI or error
1383 if self.rmempty or self.optional then
1384 if self:remove(section) then
1386 self.section.changed = true
1387 --luci.util.append(self.map.events, self.events)
1389 elseif cvalue ~= fvalue and not novld then
1390 self:write(section, fvalue or "")
1392 self.error[section] = "missing"
1394 self.error = { [section] = "missing" }
1396 self.map.save = false
1401 -- Render if this value exists or if it is mandatory
1402 function AbstractValue.render(self, s, scope)
1403 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1406 scope.cbid = self:cbid(s)
1407 scope.striptags = luci.util.striptags
1408 scope.pcdata = luci.util.pcdata
1410 scope.ifattr = function(cond,key,val)
1412 return string.format(
1413 ' %s="%s"', tostring(key),
1414 luci.util.pcdata(tostring( val
1416 or (type(self[key]) ~= "function" and self[key])
1424 scope.attr = function(...)
1425 return scope.ifattr( true, ... )
1428 Node.render(self, scope)
1432 -- Return the UCI value of this object
1433 function AbstractValue.cfgvalue(self, section)
1434 local value = self.map:get(section, self.option)
1437 elseif not self.cast or self.cast == type(value) then
1439 elseif self.cast == "string" then
1440 if type(value) == "table" then
1443 elseif self.cast == "table" then
1444 return luci.util.split(value, "%s+", nil, true)
1448 -- Validate the form value
1449 function AbstractValue.validate(self, value)
1453 AbstractValue.transform = AbstractValue.validate
1457 function AbstractValue.write(self, section, value)
1458 return self.map:set(section, self.option, value)
1462 function AbstractValue.remove(self, section)
1463 return self.map:del(section, self.option)
1470 Value - A one-line value
1471 maxlength: The maximum length
1473 Value = class(AbstractValue)
1475 function Value.__init__(self, ...)
1476 AbstractValue.__init__(self, ...)
1477 self.template = "cbi/value"
1482 function Value.value(self, key, val)
1484 table.insert(self.keylist, tostring(key))
1485 table.insert(self.vallist, tostring(val))
1489 -- DummyValue - This does nothing except being there
1490 DummyValue = class(AbstractValue)
1492 function DummyValue.__init__(self, ...)
1493 AbstractValue.__init__(self, ...)
1494 self.template = "cbi/dvalue"
1498 function DummyValue.cfgvalue(self, section)
1501 if type(self.value) == "function" then
1502 value = self:value(section)
1507 value = AbstractValue.cfgvalue(self, section)
1512 function DummyValue.parse(self)
1518 Flag - A flag being enabled or disabled
1520 Flag = class(AbstractValue)
1522 function Flag.__init__(self, ...)
1523 AbstractValue.__init__(self, ...)
1524 self.template = "cbi/fvalue"
1530 -- A flag can only have two states: set or unset
1531 function Flag.parse(self, section)
1532 local fvalue = self:formvalue(section)
1535 fvalue = self.enabled
1537 fvalue = self.disabled
1540 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1541 if not(fvalue == self:cfgvalue(section)) then
1542 self:write(section, fvalue)
1545 self:remove(section)
1552 ListValue - A one-line value predefined in a list
1553 widget: The widget that will be used (select, radio)
1555 ListValue = class(AbstractValue)
1557 function ListValue.__init__(self, ...)
1558 AbstractValue.__init__(self, ...)
1559 self.template = "cbi/lvalue"
1564 self.widget = "select"
1567 function ListValue.prepare(self, ...)
1568 AbstractValue.prepare(self, ...)
1569 if not self.override_scheme
1570 and self.map:get_scheme(self.section.sectiontype, self.option) then
1571 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1572 if self.value and vs.valuelist and not self.override_values then
1573 for k, v in ipairs(vs.valuelist) do
1575 if not self.override_dependencies
1576 and vs.enum_depends and vs.enum_depends[v.value] then
1577 for i, dep in ipairs(vs.enum_depends[v.value]) do
1578 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1581 self:value(v.value, v.title or v.value, unpack(deps))
1587 function ListValue.value(self, key, val, ...)
1588 if luci.util.contains(self.keylist, key) then
1593 table.insert(self.keylist, tostring(key))
1594 table.insert(self.vallist, tostring(val))
1596 for i, deps in ipairs({...}) do
1597 table.insert(self.deps, {add = "-"..key, deps=deps})
1601 function ListValue.validate(self, val)
1602 if luci.util.contains(self.keylist, val) then
1612 MultiValue - Multiple delimited values
1613 widget: The widget that will be used (select, checkbox)
1614 delimiter: The delimiter that will separate the values (default: " ")
1616 MultiValue = class(AbstractValue)
1618 function MultiValue.__init__(self, ...)
1619 AbstractValue.__init__(self, ...)
1620 self.template = "cbi/mvalue"
1625 self.widget = "checkbox"
1626 self.delimiter = " "
1629 function MultiValue.render(self, ...)
1630 if self.widget == "select" and not self.size then
1631 self.size = #self.vallist
1634 AbstractValue.render(self, ...)
1637 function MultiValue.value(self, key, val)
1638 if luci.util.contains(self.keylist, key) then
1643 table.insert(self.keylist, tostring(key))
1644 table.insert(self.vallist, tostring(val))
1647 function MultiValue.valuelist(self, section)
1648 local val = self:cfgvalue(section)
1650 if not(type(val) == "string") then
1654 return luci.util.split(val, self.delimiter)
1657 function MultiValue.validate(self, val)
1658 val = (type(val) == "table") and val or {val}
1662 for i, value in ipairs(val) do
1663 if luci.util.contains(self.keylist, value) then
1664 result = result and (result .. self.delimiter .. value) or value
1672 StaticList = class(MultiValue)
1674 function StaticList.__init__(self, ...)
1675 MultiValue.__init__(self, ...)
1677 self.valuelist = self.cfgvalue
1679 if not self.override_scheme
1680 and self.map:get_scheme(self.section.sectiontype, self.option) then
1681 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1682 if self.value and vs.values and not self.override_values then
1683 for k, v in pairs(vs.values) do
1690 function StaticList.validate(self, value)
1691 value = (type(value) == "table") and value or {value}
1694 for i, v in ipairs(value) do
1695 if luci.util.contains(self.keylist, v) then
1696 table.insert(valid, v)
1703 DynamicList = class(AbstractValue)
1705 function DynamicList.__init__(self, ...)
1706 AbstractValue.__init__(self, ...)
1707 self.template = "cbi/dynlist"
1713 function DynamicList.value(self, key, val)
1715 table.insert(self.keylist, tostring(key))
1716 table.insert(self.vallist, tostring(val))
1719 function DynamicList.write(self, ...)
1720 self.map.proceed = true
1721 return AbstractValue.write(self, ...)
1724 function DynamicList.formvalue(self, section)
1725 local value = AbstractValue.formvalue(self, section)
1726 value = (type(value) == "table") and value or {value}
1729 for i, v in ipairs(value) do
1731 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1732 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1733 table.insert(valid, v)
1742 TextValue - A multi-line value
1745 TextValue = class(AbstractValue)
1747 function TextValue.__init__(self, ...)
1748 AbstractValue.__init__(self, ...)
1749 self.template = "cbi/tvalue"
1755 Button = class(AbstractValue)
1757 function Button.__init__(self, ...)
1758 AbstractValue.__init__(self, ...)
1759 self.template = "cbi/button"
1760 self.inputstyle = nil
1765 FileUpload = class(AbstractValue)
1767 function FileUpload.__init__(self, ...)
1768 AbstractValue.__init__(self, ...)
1769 self.template = "cbi/upload"
1770 if not self.map.upload_fields then
1771 self.map.upload_fields = { self }
1773 self.map.upload_fields[#self.map.upload_fields+1] = self
1777 function FileUpload.formcreated(self, section)
1778 return AbstractValue.formcreated(self, section) or
1779 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1780 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1783 function FileUpload.cfgvalue(self, section)
1784 local val = AbstractValue.cfgvalue(self, section)
1785 if val and fs.access(val) then
1791 function FileUpload.formvalue(self, section)
1792 local val = AbstractValue.formvalue(self, section)
1794 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1795 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1805 function FileUpload.remove(self, section)
1806 local val = AbstractValue.formvalue(self, section)
1807 if val and fs.access(val) then fs.unlink(val) end
1808 return AbstractValue.remove(self, section)
1812 FileBrowser = class(AbstractValue)
1814 function FileBrowser.__init__(self, ...)
1815 AbstractValue.__init__(self, ...)
1816 self.template = "cbi/browser"