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:submitstate() then
675 Node.parse(self, 1, ...)
679 for k, j in ipairs(self.children) do
680 for i, v in ipairs(j.children) do
682 and (not v.tag_missing or not v.tag_missing[1])
683 and (not v.tag_invalid or not v.tag_invalid[1])
689 not self:submitstate() and FORM_NODATA
690 or valid and FORM_VALID
693 self.dorender = not self.handle
695 local nrender, nstate = self:handle(state, self.data)
696 self.dorender = self.dorender or (nrender ~= false)
697 state = nstate or state
702 function SimpleForm.render(self, ...)
703 if self.dorender then
704 Node.render(self, ...)
708 function SimpleForm.submitstate(self)
709 return self:formvalue("cbi.submit")
712 function SimpleForm.section(self, class, ...)
713 if instanceof(class, AbstractSection) then
714 local obj = class(self, ...)
718 error("class must be a descendent of AbstractSection")
722 -- Creates a child field
723 function SimpleForm.field(self, class, ...)
725 for k, v in ipairs(self.children) do
726 if instanceof(v, SimpleSection) then
732 section = self:section(SimpleSection)
735 if instanceof(class, AbstractValue) then
736 local obj = class(self, section, ...)
737 obj.track_missing = true
741 error("class must be a descendent of AbstractValue")
745 function SimpleForm.set(self, section, option, value)
746 self.data[option] = value
750 function SimpleForm.del(self, section, option)
751 self.data[option] = nil
755 function SimpleForm.get(self, section, option)
756 return self.data[option]
760 function SimpleForm.get_scheme()
765 Form = class(SimpleForm)
767 function Form.__init__(self, ...)
768 SimpleForm.__init__(self, ...)
776 AbstractSection = class(Node)
778 function AbstractSection.__init__(self, map, sectiontype, ...)
779 Node.__init__(self, ...)
780 self.sectiontype = sectiontype
782 self.config = map.config
787 self.tag_invalid = {}
788 self.tag_deperror = {}
792 self.addremove = false
796 -- Define a tab for the section
797 function AbstractSection.tab(self, tab, title, desc)
798 self.tabs = self.tabs or { }
799 self.tab_names = self.tab_names or { }
801 self.tab_names[#self.tab_names+1] = tab
809 -- Appends a new option
810 function AbstractSection.option(self, class, option, ...)
811 -- Autodetect from UVL
812 if class == true and self.map:get_scheme(self.sectiontype, option) then
813 local vs = self.map:get_scheme(self.sectiontype, option)
814 if vs.type == "boolean" then
816 elseif vs.type == "list" then
818 elseif vs.type == "enum" or vs.type == "reference" then
825 if instanceof(class, AbstractValue) then
826 local obj = class(self.map, self, option, ...)
828 self.fields[option] = obj
830 elseif class == true then
831 error("No valid class was given and autodetection failed.")
833 error("class must be a descendant of AbstractValue")
837 -- Appends a new tabbed option
838 function AbstractSection.taboption(self, tab, ...)
840 assert(tab and self.tabs and self.tabs[tab],
841 "Cannot assign option to not existing tab %q" % tostring(tab))
843 local l = self.tabs[tab].childs
844 local o = AbstractSection.option(self, ...)
846 if o then l[#l+1] = o end
851 -- Render a single tab
852 function AbstractSection.render_tab(self, tab, ...)
854 assert(tab and self.tabs and self.tabs[tab],
855 "Cannot render not existing tab %q" % tostring(tab))
857 for _, node in ipairs(self.tabs[tab].childs) do
862 -- Parse optional options
863 function AbstractSection.parse_optionals(self, section)
864 if not self.optional then
868 self.optionals[section] = {}
870 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
871 for k,v in ipairs(self.children) do
872 if v.optional and not v:cfgvalue(section) then
873 if field == v.option then
875 self.map.proceed = true
877 table.insert(self.optionals[section], v)
882 if field and #field > 0 and self.dynamic then
883 self:add_dynamic(field)
887 -- Add a dynamic option
888 function AbstractSection.add_dynamic(self, field, optional)
889 local o = self:option(Value, field, field)
890 o.optional = optional
893 -- Parse all dynamic options
894 function AbstractSection.parse_dynamic(self, section)
895 if not self.dynamic then
899 local arr = luci.util.clone(self:cfgvalue(section))
900 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
901 for k, v in pairs(form) do
905 for key,val in pairs(arr) do
908 for i,c in ipairs(self.children) do
909 if c.option == key then
914 if create and key:sub(1, 1) ~= "." then
915 self.map.proceed = true
916 self:add_dynamic(key, true)
921 -- Returns the section's UCI table
922 function AbstractSection.cfgvalue(self, section)
923 return self.map:get(section)
927 function AbstractSection.push_events(self)
928 --luci.util.append(self.map.events, self.events)
929 self.map.changed = true
932 -- Removes the section
933 function AbstractSection.remove(self, section)
934 self.map.proceed = true
935 return self.map:del(section)
938 -- Creates the section
939 function AbstractSection.create(self, section)
943 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
945 section = self.map:add(self.sectiontype)
950 for k,v in pairs(self.children) do
952 self.map:set(section, v.option, v.default)
956 for k,v in pairs(self.defaults) do
957 self.map:set(section, k, v)
961 self.map.proceed = true
967 SimpleSection = class(AbstractSection)
969 function SimpleSection.__init__(self, form, ...)
970 AbstractSection.__init__(self, form, nil, ...)
971 self.template = "cbi/nullsection"
975 Table = class(AbstractSection)
977 function Table.__init__(self, form, data, ...)
978 local datasource = {}
980 datasource.config = "table"
981 self.data = data or {}
983 datasource.formvalue = Map.formvalue
984 datasource.formvaluetable = Map.formvaluetable
985 datasource.readinput = true
987 function datasource.get(self, section, option)
988 return tself.data[section] and tself.data[section][option]
991 function datasource.submitstate(self)
992 return Map.formvalue(self, "cbi.submit")
995 function datasource.del(...)
999 function datasource.get_scheme()
1003 AbstractSection.__init__(self, datasource, "table", ...)
1004 self.template = "cbi/tblsection"
1005 self.rowcolors = true
1006 self.anonymous = true
1009 function Table.parse(self, readinput)
1010 self.map.readinput = (readinput ~= false)
1011 for i, k in ipairs(self:cfgsections()) do
1012 if self.map:submitstate() then
1018 function Table.cfgsections(self)
1021 for i, v in luci.util.kspairs(self.data) do
1022 table.insert(sections, i)
1028 function Table.update(self, data)
1035 NamedSection - A fixed configuration section defined by its name
1037 NamedSection = class(AbstractSection)
1039 function NamedSection.__init__(self, map, section, stype, ...)
1040 AbstractSection.__init__(self, map, stype, ...)
1043 self.addremove = false
1045 -- Use defaults from UVL
1046 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1047 local vs = self.map:get_scheme(self.sectiontype)
1048 self.addremove = not vs.unique and not vs.required
1049 self.dynamic = vs.dynamic
1050 self.title = self.title or vs.title
1051 self.description = self.description or vs.descr
1054 self.template = "cbi/nsection"
1055 self.section = section
1058 function NamedSection.parse(self, novld)
1059 local s = self.section
1060 local active = self:cfgvalue(s)
1062 if self.addremove then
1063 local path = self.config.."."..s
1064 if active then -- Remove the section
1065 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1069 else -- Create and apply default values
1070 if self.map:formvalue("cbi.cns."..path) then
1078 AbstractSection.parse_dynamic(self, s)
1079 if self.map:submitstate() then
1082 if not novld and not self.override_scheme and self.map.scheme then
1083 _uvl_validate_section(self, s)
1086 AbstractSection.parse_optionals(self, s)
1088 if self.changed then
1096 TypedSection - A (set of) configuration section(s) defined by the type
1097 addremove: Defines whether the user can add/remove sections of this type
1098 anonymous: Allow creating anonymous sections
1099 validate: a validation function returning nil if the section is invalid
1101 TypedSection = class(AbstractSection)
1103 function TypedSection.__init__(self, map, type, ...)
1104 AbstractSection.__init__(self, map, type, ...)
1106 self.template = "cbi/tsection"
1108 self.anonymous = false
1110 -- Use defaults from UVL
1111 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1112 local vs = self.map:get_scheme(self.sectiontype)
1113 self.addremove = not vs.unique and not vs.required
1114 self.dynamic = vs.dynamic
1115 self.anonymous = not vs.named
1116 self.title = self.title or vs.title
1117 self.description = self.description or vs.descr
1121 -- Return all matching UCI sections for this TypedSection
1122 function TypedSection.cfgsections(self)
1124 self.map.uci:foreach(self.map.config, self.sectiontype,
1126 if self:checkscope(section[".name"]) then
1127 table.insert(sections, section[".name"])
1134 -- Limits scope to sections that have certain option => value pairs
1135 function TypedSection.depends(self, option, value)
1136 table.insert(self.deps, {option=option, value=value})
1139 function TypedSection.parse(self, novld)
1140 if self.addremove then
1142 local crval = REMOVE_PREFIX .. self.config
1143 local name = self.map:formvaluetable(crval)
1144 for k,v in pairs(name) do
1145 if k:sub(-2) == ".x" then
1146 k = k:sub(1, #k - 2)
1148 if self:cfgvalue(k) and self:checkscope(k) then
1155 for i, k in ipairs(self:cfgsections()) do
1156 AbstractSection.parse_dynamic(self, k)
1157 if self.map:submitstate() then
1158 Node.parse(self, k, novld)
1160 if not novld and not self.override_scheme and self.map.scheme then
1161 _uvl_validate_section(self, k)
1164 AbstractSection.parse_optionals(self, k)
1167 if self.addremove then
1170 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1171 local name = self.map:formvalue(crval)
1172 if self.anonymous then
1174 created = self:create()
1178 -- Ignore if it already exists
1179 if self:cfgvalue(name) then
1183 name = self:checkscope(name)
1186 self.err_invalid = true
1189 if name and #name > 0 then
1190 created = self:create(name) and name
1192 self.invalid_cts = true
1199 AbstractSection.parse_optionals(self, created)
1203 if created or self.changed then
1208 -- Verifies scope of sections
1209 function TypedSection.checkscope(self, section)
1210 -- Check if we are not excluded
1211 if self.filter and not self:filter(section) then
1215 -- Check if at least one dependency is met
1216 if #self.deps > 0 and self:cfgvalue(section) then
1219 for k, v in ipairs(self.deps) do
1220 if self:cfgvalue(section)[v.option] == v.value then
1230 return self:validate(section)
1234 -- Dummy validate function
1235 function TypedSection.validate(self, section)
1241 AbstractValue - An abstract Value Type
1242 null: Value can be empty
1243 valid: A function returning the value if it is valid otherwise nil
1244 depends: A table of option => value pairs of which one must be true
1245 default: The default value
1246 size: The size of the input fields
1247 rmempty: Unset value if empty
1248 optional: This value is optional (see AbstractSection.optionals)
1250 AbstractValue = class(Node)
1252 function AbstractValue.__init__(self, map, section, option, ...)
1253 Node.__init__(self, ...)
1254 self.section = section
1255 self.option = option
1257 self.config = map.config
1258 self.tag_invalid = {}
1259 self.tag_missing = {}
1260 self.tag_reqerror = {}
1263 --self.cast = "string"
1265 self.track_missing = false
1269 self.optional = false
1272 function AbstractValue.prepare(self)
1273 -- Use defaults from UVL
1274 if not self.override_scheme
1275 and self.map:get_scheme(self.section.sectiontype, self.option) then
1276 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1277 if self.cast == nil then
1278 self.cast = (vs.type == "list") and "list" or "string"
1280 self.title = self.title or vs.title
1281 self.description = self.description or vs.descr
1282 if self.default == nil then
1283 self.default = vs.default
1286 if vs.depends and not self.override_dependencies then
1287 for i, deps in ipairs(vs.depends) do
1288 deps = _uvl_strip_remote_dependencies(deps)
1296 self.cast = self.cast or "string"
1299 -- Add a dependencie to another section field
1300 function AbstractValue.depends(self, field, value)
1302 if type(field) == "string" then
1309 table.insert(self.deps, {deps=deps, add=""})
1312 -- Generates the unique CBID
1313 function AbstractValue.cbid(self, section)
1314 return "cbid."..self.map.config.."."..section.."."..self.option
1317 -- Return whether this object should be created
1318 function AbstractValue.formcreated(self, section)
1319 local key = "cbi.opt."..self.config.."."..section
1320 return (self.map:formvalue(key) == self.option)
1323 -- Returns the formvalue for this object
1324 function AbstractValue.formvalue(self, section)
1325 return self.map:formvalue(self:cbid(section))
1328 function AbstractValue.additional(self, value)
1329 self.optional = value
1332 function AbstractValue.mandatory(self, value)
1333 self.rmempty = not value
1336 function AbstractValue.parse(self, section, novld)
1337 local fvalue = self:formvalue(section)
1338 local cvalue = self:cfgvalue(section)
1340 -- If favlue and cvalue are both tables and have the same content
1341 -- make them identical
1342 if type(fvalue) == "table" and type(cvalue) == "table" then
1343 local equal = #fvalue == #cvalue
1346 if cvalue[i] ~= fvalue[i] then
1356 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1357 fvalue = self:transform(self:validate(fvalue, section))
1358 if not fvalue and not novld then
1360 self.error[section] = "invalid"
1362 self.error = { [section] = "invalid" }
1364 if self.section.error then
1365 table.insert(self.section.error[section], "invalid")
1367 self.section.error = {[section] = {"invalid"}}
1369 self.map.save = false
1371 if fvalue and not (fvalue == cvalue) then
1372 if self:write(section, fvalue) then
1374 self.section.changed = true
1375 --luci.util.append(self.map.events, self.events)
1378 else -- Unset the UCI or error
1379 if self.rmempty or self.optional then
1380 if self:remove(section) then
1382 self.section.changed = true
1383 --luci.util.append(self.map.events, self.events)
1385 elseif cvalue ~= fvalue and not novld then
1386 self:write(section, fvalue or "")
1388 self.error[section] = "missing"
1390 self.error = { [section] = "missing" }
1392 self.map.save = false
1397 -- Render if this value exists or if it is mandatory
1398 function AbstractValue.render(self, s, scope)
1399 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1402 scope.cbid = self:cbid(s)
1403 scope.striptags = luci.util.striptags
1404 scope.pcdata = luci.util.pcdata
1406 scope.ifattr = function(cond,key,val)
1408 return string.format(
1409 ' %s="%s"', tostring(key),
1410 luci.util.pcdata(tostring( val
1412 or (type(self[key]) ~= "function" and self[key])
1420 scope.attr = function(...)
1421 return scope.ifattr( true, ... )
1424 Node.render(self, scope)
1428 -- Return the UCI value of this object
1429 function AbstractValue.cfgvalue(self, section)
1430 local value = self.map:get(section, self.option)
1433 elseif not self.cast or self.cast == type(value) then
1435 elseif self.cast == "string" then
1436 if type(value) == "table" then
1439 elseif self.cast == "table" then
1440 return luci.util.split(value, "%s+", nil, true)
1444 -- Validate the form value
1445 function AbstractValue.validate(self, value)
1449 AbstractValue.transform = AbstractValue.validate
1453 function AbstractValue.write(self, section, value)
1454 return self.map:set(section, self.option, value)
1458 function AbstractValue.remove(self, section)
1459 return self.map:del(section, self.option)
1466 Value - A one-line value
1467 maxlength: The maximum length
1469 Value = class(AbstractValue)
1471 function Value.__init__(self, ...)
1472 AbstractValue.__init__(self, ...)
1473 self.template = "cbi/value"
1478 function Value.value(self, key, val)
1480 table.insert(self.keylist, tostring(key))
1481 table.insert(self.vallist, tostring(val))
1485 -- DummyValue - This does nothing except being there
1486 DummyValue = class(AbstractValue)
1488 function DummyValue.__init__(self, ...)
1489 AbstractValue.__init__(self, ...)
1490 self.template = "cbi/dvalue"
1494 function DummyValue.cfgvalue(self, section)
1497 if type(self.value) == "function" then
1498 value = self:value(section)
1503 value = AbstractValue.cfgvalue(self, section)
1508 function DummyValue.parse(self)
1514 Flag - A flag being enabled or disabled
1516 Flag = class(AbstractValue)
1518 function Flag.__init__(self, ...)
1519 AbstractValue.__init__(self, ...)
1520 self.template = "cbi/fvalue"
1526 -- A flag can only have two states: set or unset
1527 function Flag.parse(self, section)
1528 local fvalue = self:formvalue(section)
1531 fvalue = self.enabled
1533 fvalue = self.disabled
1536 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1537 if not(fvalue == self:cfgvalue(section)) then
1538 self:write(section, fvalue)
1541 self:remove(section)
1548 ListValue - A one-line value predefined in a list
1549 widget: The widget that will be used (select, radio)
1551 ListValue = class(AbstractValue)
1553 function ListValue.__init__(self, ...)
1554 AbstractValue.__init__(self, ...)
1555 self.template = "cbi/lvalue"
1560 self.widget = "select"
1563 function ListValue.prepare(self, ...)
1564 AbstractValue.prepare(self, ...)
1565 if not self.override_scheme
1566 and self.map:get_scheme(self.section.sectiontype, self.option) then
1567 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1568 if self.value and vs.valuelist and not self.override_values then
1569 for k, v in ipairs(vs.valuelist) do
1571 if not self.override_dependencies
1572 and vs.enum_depends and vs.enum_depends[v.value] then
1573 for i, dep in ipairs(vs.enum_depends[v.value]) do
1574 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1577 self:value(v.value, v.title or v.value, unpack(deps))
1583 function ListValue.value(self, key, val, ...)
1584 if luci.util.contains(self.keylist, key) then
1589 table.insert(self.keylist, tostring(key))
1590 table.insert(self.vallist, tostring(val))
1592 for i, deps in ipairs({...}) do
1593 table.insert(self.deps, {add = "-"..key, deps=deps})
1597 function ListValue.validate(self, val)
1598 if luci.util.contains(self.keylist, val) then
1608 MultiValue - Multiple delimited values
1609 widget: The widget that will be used (select, checkbox)
1610 delimiter: The delimiter that will separate the values (default: " ")
1612 MultiValue = class(AbstractValue)
1614 function MultiValue.__init__(self, ...)
1615 AbstractValue.__init__(self, ...)
1616 self.template = "cbi/mvalue"
1621 self.widget = "checkbox"
1622 self.delimiter = " "
1625 function MultiValue.render(self, ...)
1626 if self.widget == "select" and not self.size then
1627 self.size = #self.vallist
1630 AbstractValue.render(self, ...)
1633 function MultiValue.value(self, key, val)
1634 if luci.util.contains(self.keylist, key) then
1639 table.insert(self.keylist, tostring(key))
1640 table.insert(self.vallist, tostring(val))
1643 function MultiValue.valuelist(self, section)
1644 local val = self:cfgvalue(section)
1646 if not(type(val) == "string") then
1650 return luci.util.split(val, self.delimiter)
1653 function MultiValue.validate(self, val)
1654 val = (type(val) == "table") and val or {val}
1658 for i, value in ipairs(val) do
1659 if luci.util.contains(self.keylist, value) then
1660 result = result and (result .. self.delimiter .. value) or value
1668 StaticList = class(MultiValue)
1670 function StaticList.__init__(self, ...)
1671 MultiValue.__init__(self, ...)
1673 self.valuelist = self.cfgvalue
1675 if not self.override_scheme
1676 and self.map:get_scheme(self.section.sectiontype, self.option) then
1677 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1678 if self.value and vs.values and not self.override_values then
1679 for k, v in pairs(vs.values) do
1686 function StaticList.validate(self, value)
1687 value = (type(value) == "table") and value or {value}
1690 for i, v in ipairs(value) do
1691 if luci.util.contains(self.keylist, v) then
1692 table.insert(valid, v)
1699 DynamicList = class(AbstractValue)
1701 function DynamicList.__init__(self, ...)
1702 AbstractValue.__init__(self, ...)
1703 self.template = "cbi/dynlist"
1709 function DynamicList.value(self, key, val)
1711 table.insert(self.keylist, tostring(key))
1712 table.insert(self.vallist, tostring(val))
1715 function DynamicList.write(self, ...)
1716 self.map.proceed = true
1717 return AbstractValue.write(self, ...)
1720 function DynamicList.formvalue(self, section)
1721 local value = AbstractValue.formvalue(self, section)
1722 value = (type(value) == "table") and value or {value}
1725 for i, v in ipairs(value) do
1727 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1728 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1729 table.insert(valid, v)
1738 TextValue - A multi-line value
1741 TextValue = class(AbstractValue)
1743 function TextValue.__init__(self, ...)
1744 AbstractValue.__init__(self, ...)
1745 self.template = "cbi/tvalue"
1751 Button = class(AbstractValue)
1753 function Button.__init__(self, ...)
1754 AbstractValue.__init__(self, ...)
1755 self.template = "cbi/button"
1756 self.inputstyle = nil
1761 FileUpload = class(AbstractValue)
1763 function FileUpload.__init__(self, ...)
1764 AbstractValue.__init__(self, ...)
1765 self.template = "cbi/upload"
1766 if not self.map.upload_fields then
1767 self.map.upload_fields = { self }
1769 self.map.upload_fields[#self.map.upload_fields+1] = self
1773 function FileUpload.formcreated(self, section)
1774 return AbstractValue.formcreated(self, section) or
1775 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1776 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1779 function FileUpload.cfgvalue(self, section)
1780 local val = AbstractValue.cfgvalue(self, section)
1781 if val and fs.access(val) then
1787 function FileUpload.formvalue(self, section)
1788 local val = AbstractValue.formvalue(self, section)
1790 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1791 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1801 function FileUpload.remove(self, section)
1802 local val = AbstractValue.formvalue(self, section)
1803 if val and fs.access(val) then fs.unlink(val) end
1804 return AbstractValue.remove(self, section)
1808 FileBrowser = class(AbstractValue)
1810 function FileBrowser.__init__(self, ...)
1811 AbstractValue.__init__(self, ...)
1812 self.template = "cbi/browser"