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")
34 --local event = require "luci.sys.event"
35 local fs = require("nixio.fs")
36 local uci = require("luci.model.uci")
37 local datatypes = require("luci.cbi.datatypes")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
53 RESORT_PREFIX = "cbi.sts."
54 FEXIST_PREFIX = "cbi.cbe."
56 -- Loads a CBI map from given file, creating an environment and returns it
57 function load(cbimap, ...)
58 local fs = require "nixio.fs"
59 local i18n = require "luci.i18n"
60 require("luci.config")
63 local upldir = "/lib/uci/upload/"
64 local cbidir = luci.util.libpath() .. "/model/cbi/"
67 if fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbimap) then
70 func, err = loadfile(cbimap)
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 or '1') .. '.' ..
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 ) or s
128 if 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
156 -- Compile a datatype specification into a parse tree for evaluation later on
158 local cdt_cache = { }
160 function compile_datatype(code)
167 for i = 1, #code+1 do
168 local byte = code:byte(i) or 44
171 elseif byte == 92 then
173 elseif byte == 40 or byte == 44 then
176 local label = code:sub(pos, i-1)
181 if #label > 0 and tonumber(label) then
182 stack[#stack+1] = tonumber(label)
183 elseif label:match("^'.+'$") or label:match('^".+"$') then
184 stack[#stack+1] = label:gsub("[\"'](.+)[\"']", "%1")
185 elseif type(datatypes[label]) == "function" then
186 stack[#stack+1] = datatypes[label]
187 stack[#stack+1] = { }
189 error("Datatype error, bad token %q" % label)
194 depth = depth + (byte == 40 and 1 or 0)
195 elseif byte == 41 then
198 if type(stack[#stack-1]) ~= "function" then
199 error("Datatype error, argument list follows non-function")
201 stack[#stack] = compile_datatype(code:sub(pos, i-1))
210 function verify_datatype(dt, value)
211 if dt and #dt > 0 then
212 if not cdt_cache[dt] then
213 local c = compile_datatype(dt)
214 if c and type(c[1]) == "function" then
217 error("Datatype error, not a function expression")
220 if cdt_cache[dt] then
221 return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2]))
228 -- Node pseudo abstract class
231 function Node.__init__(self, title, description)
233 self.title = title or ""
234 self.description = description or ""
235 self.template = "cbi/node"
239 function Node._run_hook(self, hook)
240 if type(self[hook]) == "function" then
241 return self[hook](self)
245 function Node._run_hooks(self, ...)
248 for _, f in ipairs(arg) do
249 if type(self[f]) == "function" then
258 function Node.prepare(self, ...)
259 for k, child in ipairs(self.children) do
264 -- Append child nodes
265 function Node.append(self, obj)
266 table.insert(self.children, obj)
269 -- Parse this node and its children
270 function Node.parse(self, ...)
271 for k, child in ipairs(self.children) do
277 function Node.render(self, scope)
281 luci.template.render(self.template, scope)
284 -- Render the children
285 function Node.render_children(self, ...)
287 for k, node in ipairs(self.children) do
288 node.last_child = (k == #self.children)
295 A simple template element
297 Template = class(Node)
299 function Template.__init__(self, template)
301 self.template = template
304 function Template.render(self)
305 luci.template.render(self.template, {self=self})
308 function Template.parse(self, readinput)
309 self.readinput = (readinput ~= false)
310 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
315 Map - A map describing a configuration file
319 function Map.__init__(self, config, ...)
320 Node.__init__(self, ...)
323 self.parsechain = {self.config}
324 self.template = "cbi/map"
325 self.apply_on_parse = nil
326 self.readinput = true
330 self.uci = uci.cursor()
335 if not self.uci:load(self.config) then
336 error("Unable to read UCI data: " .. self.config)
340 function Map.formvalue(self, key)
341 return self.readinput and luci.http.formvalue(key)
344 function Map.formvaluetable(self, key)
345 return self.readinput and luci.http.formvaluetable(key) or {}
348 function Map.get_scheme(self, sectiontype, option)
350 return self.scheme and self.scheme.sections[sectiontype]
352 return self.scheme and self.scheme.variables[sectiontype]
353 and self.scheme.variables[sectiontype][option]
357 function Map.submitstate(self)
358 return self:formvalue("cbi.submit")
361 -- Chain foreign config
362 function Map.chain(self, config)
363 table.insert(self.parsechain, config)
366 function Map.state_handler(self, state)
370 -- Use optimized UCI writing
371 function Map.parse(self, readinput, ...)
372 self.readinput = (readinput ~= false)
373 self:_run_hooks("on_parse")
375 if self:formvalue("cbi.skip") then
376 self.state = FORM_SKIP
377 return self:state_handler(self.state)
380 Node.parse(self, ...)
383 self:_run_hooks("on_save", "on_before_save")
384 for i, config in ipairs(self.parsechain) do
385 self.uci:save(config)
387 self:_run_hooks("on_after_save")
388 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
389 self:_run_hooks("on_before_commit")
390 for i, config in ipairs(self.parsechain) do
391 self.uci:commit(config)
393 -- Refresh data because commit changes section names
394 self.uci:load(config)
396 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
397 if self.apply_on_parse then
398 self.uci:apply(self.parsechain)
399 self:_run_hooks("on_apply", "on_after_apply")
401 -- This is evaluated by the dispatcher and delegated to the
402 -- template which in turn fires XHR to perform the actual
404 self.apply_needed = true
408 Node.parse(self, true)
411 for i, config in ipairs(self.parsechain) do
412 self.uci:unload(config)
414 if type(self.commit_handler) == "function" then
415 self:commit_handler(self:submitstate())
419 if self:submitstate() then
420 if not self.save then
421 self.state = FORM_INVALID
422 elseif self.proceed then
423 self.state = FORM_PROCEED
425 self.state = self.changed and FORM_CHANGED or FORM_VALID
428 self.state = FORM_NODATA
431 return self:state_handler(self.state)
434 function Map.render(self, ...)
435 self:_run_hooks("on_init")
436 Node.render(self, ...)
439 -- Creates a child section
440 function Map.section(self, class, ...)
441 if instanceof(class, AbstractSection) then
442 local obj = class(self, ...)
446 error("class must be a descendent of AbstractSection")
451 function Map.add(self, sectiontype)
452 return self.uci:add(self.config, sectiontype)
456 function Map.set(self, section, option, value)
457 if type(value) ~= "table" or #value > 0 then
459 return self.uci:set(self.config, section, option, value)
461 return self.uci:set(self.config, section, value)
464 return Map.del(self, section, option)
469 function Map.del(self, section, option)
471 return self.uci:delete(self.config, section, option)
473 return self.uci:delete(self.config, section)
478 function Map.get(self, section, option)
480 return self.uci:get_all(self.config)
482 return self.uci:get(self.config, section, option)
484 return self.uci:get_all(self.config, section)
491 Compound = class(Node)
493 function Compound.__init__(self, ...)
495 self.template = "cbi/compound"
496 self.children = {...}
499 function Compound.populate_delegator(self, delegator)
500 for _, v in ipairs(self.children) do
501 v.delegator = delegator
505 function Compound.parse(self, ...)
506 local cstate, state = 0
508 for k, child in ipairs(self.children) do
509 cstate = child:parse(...)
510 state = (not state or cstate < state) and cstate or state
518 Delegator - Node controller
520 Delegator = class(Node)
521 function Delegator.__init__(self, ...)
522 Node.__init__(self, ...)
524 self.defaultpath = {}
525 self.pageaction = false
526 self.readinput = true
527 self.allow_reset = false
528 self.allow_cancel = false
529 self.allow_back = false
530 self.allow_finish = false
531 self.template = "cbi/delegator"
534 function Delegator.set(self, name, node)
535 assert(not self.nodes[name], "Duplicate entry")
537 self.nodes[name] = node
540 function Delegator.add(self, name, node)
541 node = self:set(name, node)
542 self.defaultpath[#self.defaultpath+1] = name
545 function Delegator.insert_after(self, name, after)
546 local n = #self.chain + 1
547 for k, v in ipairs(self.chain) do
553 table.insert(self.chain, n, name)
556 function Delegator.set_route(self, ...)
557 local n, chain, route = 0, self.chain, {...}
559 if chain[i] == self.current then
568 for i = n + 1, #chain do
573 function Delegator.get(self, name)
574 local node = self.nodes[name]
576 if type(node) == "string" then
577 node = load(node, name)
580 if type(node) == "table" and getmetatable(node) == nil then
581 node = Compound(unpack(node))
587 function Delegator.parse(self, ...)
588 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
589 if self:_run_hooks("on_cancel") then
594 if not Map.formvalue(self, "cbi.delg.current") then
595 self:_run_hooks("on_init")
599 self.chain = self.chain or self:get_chain()
600 self.current = self.current or self:get_active()
601 self.active = self.active or self:get(self.current)
602 assert(self.active, "Invalid state")
604 local stat = FORM_DONE
605 if type(self.active) ~= "function" then
606 self.active:populate_delegator(self)
607 stat = self.active:parse()
612 if stat > FORM_PROCEED then
613 if Map.formvalue(self, "cbi.delg.back") then
614 newcurrent = self:get_prev(self.current)
616 newcurrent = self:get_next(self.current)
618 elseif stat < FORM_PROCEED then
623 if not Map.formvalue(self, "cbi.submit") then
625 elseif stat > FORM_PROCEED
626 and (not newcurrent or not self:get(newcurrent)) then
627 return self:_run_hook("on_done") or FORM_DONE
629 self.current = newcurrent or self.current
630 self.active = self:get(self.current)
631 if type(self.active) ~= "function" then
632 self.active:populate_delegator(self)
633 local stat = self.active:parse(false)
634 if stat == FORM_SKIP then
635 return self:parse(...)
640 return self:parse(...)
645 function Delegator.get_next(self, state)
646 for k, v in ipairs(self.chain) do
648 return self.chain[k+1]
653 function Delegator.get_prev(self, state)
654 for k, v in ipairs(self.chain) do
656 return self.chain[k-1]
661 function Delegator.get_chain(self)
662 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
663 return type(x) == "table" and x or {x}
666 function Delegator.get_active(self)
667 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
675 Page.__init__ = Node.__init__
676 Page.parse = function() end
680 SimpleForm - A Simple non-UCI form
682 SimpleForm = class(Node)
684 function SimpleForm.__init__(self, config, title, description, data)
685 Node.__init__(self, title, description)
687 self.data = data or {}
688 self.template = "cbi/simpleform"
690 self.pageaction = false
691 self.readinput = true
694 SimpleForm.formvalue = Map.formvalue
695 SimpleForm.formvaluetable = Map.formvaluetable
697 function SimpleForm.parse(self, readinput, ...)
698 self.readinput = (readinput ~= false)
700 if self:formvalue("cbi.skip") then
704 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
708 if self:submitstate() then
709 Node.parse(self, 1, ...)
713 for k, j in ipairs(self.children) do
714 for i, v in ipairs(j.children) do
716 and (not v.tag_missing or not v.tag_missing[1])
717 and (not v.tag_invalid or not v.tag_invalid[1])
723 not self:submitstate() and FORM_NODATA
724 or valid and FORM_VALID
727 self.dorender = not self.handle
729 local nrender, nstate = self:handle(state, self.data)
730 self.dorender = self.dorender or (nrender ~= false)
731 state = nstate or state
736 function SimpleForm.render(self, ...)
737 if self.dorender then
738 Node.render(self, ...)
742 function SimpleForm.submitstate(self)
743 return self:formvalue("cbi.submit")
746 function SimpleForm.section(self, class, ...)
747 if instanceof(class, AbstractSection) then
748 local obj = class(self, ...)
752 error("class must be a descendent of AbstractSection")
756 -- Creates a child field
757 function SimpleForm.field(self, class, ...)
759 for k, v in ipairs(self.children) do
760 if instanceof(v, SimpleSection) then
766 section = self:section(SimpleSection)
769 if instanceof(class, AbstractValue) then
770 local obj = class(self, section, ...)
771 obj.track_missing = true
775 error("class must be a descendent of AbstractValue")
779 function SimpleForm.set(self, section, option, value)
780 self.data[option] = value
784 function SimpleForm.del(self, section, option)
785 self.data[option] = nil
789 function SimpleForm.get(self, section, option)
790 return self.data[option]
794 function SimpleForm.get_scheme()
799 Form = class(SimpleForm)
801 function Form.__init__(self, ...)
802 SimpleForm.__init__(self, ...)
810 AbstractSection = class(Node)
812 function AbstractSection.__init__(self, map, sectiontype, ...)
813 Node.__init__(self, ...)
814 self.sectiontype = sectiontype
816 self.config = map.config
821 self.tag_invalid = {}
822 self.tag_deperror = {}
826 self.addremove = false
830 -- Define a tab for the section
831 function AbstractSection.tab(self, tab, title, desc)
832 self.tabs = self.tabs or { }
833 self.tab_names = self.tab_names or { }
835 self.tab_names[#self.tab_names+1] = tab
843 -- Check whether the section has tabs
844 function AbstractSection.has_tabs(self)
845 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
848 -- Appends a new option
849 function AbstractSection.option(self, class, option, ...)
850 if instanceof(class, AbstractValue) then
851 local obj = class(self.map, self, option, ...)
853 self.fields[option] = obj
855 elseif class == true then
856 error("No valid class was given and autodetection failed.")
858 error("class must be a descendant of AbstractValue")
862 -- Appends a new tabbed option
863 function AbstractSection.taboption(self, tab, ...)
865 assert(tab and self.tabs and self.tabs[tab],
866 "Cannot assign option to not existing tab %q" % tostring(tab))
868 local l = self.tabs[tab].childs
869 local o = AbstractSection.option(self, ...)
871 if o then l[#l+1] = o end
876 -- Render a single tab
877 function AbstractSection.render_tab(self, tab, ...)
879 assert(tab and self.tabs and self.tabs[tab],
880 "Cannot render not existing tab %q" % tostring(tab))
883 for k, node in ipairs(self.tabs[tab].childs) do
884 node.last_child = (k == #self.tabs[tab].childs)
889 -- Parse optional options
890 function AbstractSection.parse_optionals(self, section)
891 if not self.optional then
895 self.optionals[section] = {}
897 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
898 for k,v in ipairs(self.children) do
899 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
900 if field == v.option then
902 self.map.proceed = true
904 table.insert(self.optionals[section], v)
909 if field and #field > 0 and self.dynamic then
910 self:add_dynamic(field)
914 -- Add a dynamic option
915 function AbstractSection.add_dynamic(self, field, optional)
916 local o = self:option(Value, field, field)
917 o.optional = optional
920 -- Parse all dynamic options
921 function AbstractSection.parse_dynamic(self, section)
922 if not self.dynamic then
926 local arr = luci.util.clone(self:cfgvalue(section))
927 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
928 for k, v in pairs(form) do
932 for key,val in pairs(arr) do
935 for i,c in ipairs(self.children) do
936 if c.option == key then
941 if create and key:sub(1, 1) ~= "." then
942 self.map.proceed = true
943 self:add_dynamic(key, true)
948 -- Returns the section's UCI table
949 function AbstractSection.cfgvalue(self, section)
950 return self.map:get(section)
954 function AbstractSection.push_events(self)
955 --luci.util.append(self.map.events, self.events)
956 self.map.changed = true
959 -- Removes the section
960 function AbstractSection.remove(self, section)
961 self.map.proceed = true
962 return self.map:del(section)
965 -- Creates the section
966 function AbstractSection.create(self, section)
970 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
972 section = self.map:add(self.sectiontype)
977 for k,v in pairs(self.children) do
979 self.map:set(section, v.option, v.default)
983 for k,v in pairs(self.defaults) do
984 self.map:set(section, k, v)
988 self.map.proceed = true
994 SimpleSection = class(AbstractSection)
996 function SimpleSection.__init__(self, form, ...)
997 AbstractSection.__init__(self, form, nil, ...)
998 self.template = "cbi/nullsection"
1002 Table = class(AbstractSection)
1004 function Table.__init__(self, form, data, ...)
1005 local datasource = {}
1007 datasource.config = "table"
1008 self.data = data or {}
1010 datasource.formvalue = Map.formvalue
1011 datasource.formvaluetable = Map.formvaluetable
1012 datasource.readinput = true
1014 function datasource.get(self, section, option)
1015 return tself.data[section] and tself.data[section][option]
1018 function datasource.submitstate(self)
1019 return Map.formvalue(self, "cbi.submit")
1022 function datasource.del(...)
1026 function datasource.get_scheme()
1030 AbstractSection.__init__(self, datasource, "table", ...)
1031 self.template = "cbi/tblsection"
1032 self.rowcolors = true
1033 self.anonymous = true
1036 function Table.parse(self, readinput)
1037 self.map.readinput = (readinput ~= false)
1038 for i, k in ipairs(self:cfgsections()) do
1039 if self.map:submitstate() then
1045 function Table.cfgsections(self)
1048 for i, v in luci.util.kspairs(self.data) do
1049 table.insert(sections, i)
1055 function Table.update(self, data)
1062 NamedSection - A fixed configuration section defined by its name
1064 NamedSection = class(AbstractSection)
1066 function NamedSection.__init__(self, map, section, stype, ...)
1067 AbstractSection.__init__(self, map, stype, ...)
1070 self.addremove = false
1071 self.template = "cbi/nsection"
1072 self.section = section
1075 function NamedSection.parse(self, novld)
1076 local s = self.section
1077 local active = self:cfgvalue(s)
1079 if self.addremove then
1080 local path = self.config.."."..s
1081 if active then -- Remove the section
1082 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1086 else -- Create and apply default values
1087 if self.map:formvalue("cbi.cns."..path) then
1095 AbstractSection.parse_dynamic(self, s)
1096 if self.map:submitstate() then
1099 AbstractSection.parse_optionals(self, s)
1101 if self.changed then
1109 TypedSection - A (set of) configuration section(s) defined by the type
1110 addremove: Defines whether the user can add/remove sections of this type
1111 anonymous: Allow creating anonymous sections
1112 validate: a validation function returning nil if the section is invalid
1114 TypedSection = class(AbstractSection)
1116 function TypedSection.__init__(self, map, type, ...)
1117 AbstractSection.__init__(self, map, type, ...)
1119 self.template = "cbi/tsection"
1121 self.anonymous = false
1124 -- Return all matching UCI sections for this TypedSection
1125 function TypedSection.cfgsections(self)
1127 self.map.uci:foreach(self.map.config, self.sectiontype,
1129 if self:checkscope(section[".name"]) then
1130 table.insert(sections, section[".name"])
1137 -- Limits scope to sections that have certain option => value pairs
1138 function TypedSection.depends(self, option, value)
1139 table.insert(self.deps, {option=option, value=value})
1142 function TypedSection.parse(self, novld)
1143 if self.addremove then
1145 local crval = REMOVE_PREFIX .. self.config
1146 local name = self.map:formvaluetable(crval)
1147 for k,v in pairs(name) do
1148 if k:sub(-2) == ".x" then
1149 k = k:sub(1, #k - 2)
1151 if self:cfgvalue(k) and self:checkscope(k) then
1158 for i, k in ipairs(self:cfgsections()) do
1159 AbstractSection.parse_dynamic(self, k)
1160 if self.map:submitstate() then
1161 Node.parse(self, k, novld)
1163 AbstractSection.parse_optionals(self, k)
1166 if self.addremove then
1169 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1170 local origin, name = next(self.map:formvaluetable(crval))
1171 if self.anonymous then
1173 created = self:create(nil, origin)
1177 -- Ignore if it already exists
1178 if self:cfgvalue(name) then
1182 name = self:checkscope(name)
1185 self.err_invalid = true
1188 if name and #name > 0 then
1189 created = self:create(name, origin) and name
1191 self.invalid_cts = true
1198 AbstractSection.parse_optionals(self, created)
1202 if self.sortable then
1203 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1204 local order = self.map:formvalue(stval)
1205 if order and #order > 0 then
1208 for sid in util.imatch(order) do
1209 self.map.uci:reorder(self.config, sid, num)
1212 self.changed = (num > 0)
1216 if created or self.changed then
1221 -- Verifies scope of sections
1222 function TypedSection.checkscope(self, section)
1223 -- Check if we are not excluded
1224 if self.filter and not self:filter(section) then
1228 -- Check if at least one dependency is met
1229 if #self.deps > 0 and self:cfgvalue(section) then
1232 for k, v in ipairs(self.deps) do
1233 if self:cfgvalue(section)[v.option] == v.value then
1243 return self:validate(section)
1247 -- Dummy validate function
1248 function TypedSection.validate(self, section)
1254 AbstractValue - An abstract Value Type
1255 null: Value can be empty
1256 valid: A function returning the value if it is valid otherwise nil
1257 depends: A table of option => value pairs of which one must be true
1258 default: The default value
1259 size: The size of the input fields
1260 rmempty: Unset value if empty
1261 optional: This value is optional (see AbstractSection.optionals)
1263 AbstractValue = class(Node)
1265 function AbstractValue.__init__(self, map, section, option, ...)
1266 Node.__init__(self, ...)
1267 self.section = section
1268 self.option = option
1270 self.config = map.config
1271 self.tag_invalid = {}
1272 self.tag_missing = {}
1273 self.tag_reqerror = {}
1277 --self.cast = "string"
1279 self.track_missing = false
1283 self.optional = false
1286 function AbstractValue.prepare(self)
1287 self.cast = self.cast or "string"
1290 -- Add a dependencie to another section field
1291 function AbstractValue.depends(self, field, value)
1293 if type(field) == "string" then
1300 table.insert(self.deps, {deps=deps, add=""})
1303 -- Generates the unique CBID
1304 function AbstractValue.cbid(self, section)
1305 return "cbid."..self.map.config.."."..section.."."..self.option
1308 -- Return whether this object should be created
1309 function AbstractValue.formcreated(self, section)
1310 local key = "cbi.opt."..self.config.."."..section
1311 return (self.map:formvalue(key) == self.option)
1314 -- Returns the formvalue for this object
1315 function AbstractValue.formvalue(self, section)
1316 return self.map:formvalue(self:cbid(section))
1319 function AbstractValue.additional(self, value)
1320 self.optional = value
1323 function AbstractValue.mandatory(self, value)
1324 self.rmempty = not value
1327 function AbstractValue.add_error(self, section, type, msg)
1328 self.error = self.error or { }
1329 self.error[section] = msg or type
1331 self.section.error = self.section.error or { }
1332 self.section.error[section] = self.section.error[section] or { }
1333 table.insert(self.section.error[section], msg or type)
1335 if type == "invalid" then
1336 self.tag_invalid[section] = true
1337 elseif type == "missing" then
1338 self.tag_missing[section] = true
1341 self.tag_error[section] = true
1342 self.map.save = false
1345 function AbstractValue.parse(self, section, novld)
1346 local fvalue = self:formvalue(section)
1347 local cvalue = self:cfgvalue(section)
1349 -- If favlue and cvalue are both tables and have the same content
1350 -- make them identical
1351 if type(fvalue) == "table" and type(cvalue) == "table" then
1352 local equal = #fvalue == #cvalue
1355 if cvalue[i] ~= fvalue[i] then
1365 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1367 fvalue, val_err = self:validate(fvalue, section)
1368 fvalue = self:transform(fvalue)
1370 if not fvalue and not novld then
1371 self:add_error(section, "invalid", val_err)
1374 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1375 if self:write(section, fvalue) then
1377 self.section.changed = true
1378 --luci.util.append(self.map.events, self.events)
1381 else -- Unset the UCI or error
1382 if self.rmempty or self.optional then
1383 if self:remove(section) then
1385 self.section.changed = true
1386 --luci.util.append(self.map.events, self.events)
1388 elseif cvalue ~= fvalue and not novld then
1389 -- trigger validator with nil value to get custom user error msg.
1390 local _, val_err = self:validate(nil, section)
1391 self:add_error(section, "missing", val_err)
1396 -- Render if this value exists or if it is mandatory
1397 function AbstractValue.render(self, s, scope)
1398 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1401 scope.cbid = self:cbid(s)
1402 Node.render(self, scope)
1406 -- Return the UCI value of this object
1407 function AbstractValue.cfgvalue(self, section)
1409 if self.tag_error[section] then
1410 value = self:formvalue(section)
1412 value = self.map:get(section, self.option)
1417 elseif not self.cast or self.cast == type(value) then
1419 elseif self.cast == "string" then
1420 if type(value) == "table" then
1423 elseif self.cast == "table" then
1428 -- Validate the form value
1429 function AbstractValue.validate(self, value)
1430 if self.datatype and value then
1431 if type(value) == "table" then
1433 for _, v in ipairs(value) do
1434 if v and #v > 0 and not verify_datatype(self.datatype, v) then
1440 if not verify_datatype(self.datatype, value) then
1450 AbstractValue.transform = AbstractValue.validate
1454 function AbstractValue.write(self, section, value)
1455 return self.map:set(section, self.option, value)
1459 function AbstractValue.remove(self, section)
1460 return self.map:del(section, self.option)
1467 Value - A one-line value
1468 maxlength: The maximum length
1470 Value = class(AbstractValue)
1472 function Value.__init__(self, ...)
1473 AbstractValue.__init__(self, ...)
1474 self.template = "cbi/value"
1479 function Value.reset_values(self)
1484 function Value.value(self, key, val)
1486 table.insert(self.keylist, tostring(key))
1487 table.insert(self.vallist, tostring(val))
1491 -- DummyValue - This does nothing except being there
1492 DummyValue = class(AbstractValue)
1494 function DummyValue.__init__(self, ...)
1495 AbstractValue.__init__(self, ...)
1496 self.template = "cbi/dvalue"
1500 function DummyValue.cfgvalue(self, section)
1503 if type(self.value) == "function" then
1504 value = self:value(section)
1509 value = AbstractValue.cfgvalue(self, section)
1514 function DummyValue.parse(self)
1520 Flag - A flag being enabled or disabled
1522 Flag = class(AbstractValue)
1524 function Flag.__init__(self, ...)
1525 AbstractValue.__init__(self, ...)
1526 self.template = "cbi/fvalue"
1530 self.default = self.disabled
1533 -- A flag can only have two states: set or unset
1534 function Flag.parse(self, section)
1535 local fexists = self.map:formvalue(
1536 FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
1539 local fvalue = self:formvalue(section) and self.enabled or self.disabled
1540 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
1541 self:write(section, fvalue)
1543 self:remove(section)
1546 self:remove(section)
1550 function Flag.cfgvalue(self, section)
1551 return AbstractValue.cfgvalue(self, section) or self.default
1556 ListValue - A one-line value predefined in a list
1557 widget: The widget that will be used (select, radio)
1559 ListValue = class(AbstractValue)
1561 function ListValue.__init__(self, ...)
1562 AbstractValue.__init__(self, ...)
1563 self.template = "cbi/lvalue"
1568 self.widget = "select"
1571 function ListValue.reset_values(self)
1576 function ListValue.value(self, key, val, ...)
1577 if luci.util.contains(self.keylist, key) then
1582 table.insert(self.keylist, tostring(key))
1583 table.insert(self.vallist, tostring(val))
1585 for i, deps in ipairs({...}) do
1586 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1590 function ListValue.validate(self, val)
1591 if luci.util.contains(self.keylist, val) then
1601 MultiValue - Multiple delimited values
1602 widget: The widget that will be used (select, checkbox)
1603 delimiter: The delimiter that will separate the values (default: " ")
1605 MultiValue = class(AbstractValue)
1607 function MultiValue.__init__(self, ...)
1608 AbstractValue.__init__(self, ...)
1609 self.template = "cbi/mvalue"
1614 self.widget = "checkbox"
1615 self.delimiter = " "
1618 function MultiValue.render(self, ...)
1619 if self.widget == "select" and not self.size then
1620 self.size = #self.vallist
1623 AbstractValue.render(self, ...)
1626 function MultiValue.reset_values(self)
1631 function MultiValue.value(self, key, val)
1632 if luci.util.contains(self.keylist, key) then
1637 table.insert(self.keylist, tostring(key))
1638 table.insert(self.vallist, tostring(val))
1641 function MultiValue.valuelist(self, section)
1642 local val = self:cfgvalue(section)
1644 if not(type(val) == "string") then
1648 return luci.util.split(val, self.delimiter)
1651 function MultiValue.validate(self, val)
1652 val = (type(val) == "table") and val or {val}
1656 for i, value in ipairs(val) do
1657 if luci.util.contains(self.keylist, value) then
1658 result = result and (result .. self.delimiter .. value) or value
1666 StaticList = class(MultiValue)
1668 function StaticList.__init__(self, ...)
1669 MultiValue.__init__(self, ...)
1671 self.valuelist = self.cfgvalue
1673 if not self.override_scheme
1674 and self.map:get_scheme(self.section.sectiontype, self.option) then
1675 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1676 if self.value and vs.values and not self.override_values then
1677 for k, v in pairs(vs.values) do
1684 function StaticList.validate(self, value)
1685 value = (type(value) == "table") and value or {value}
1688 for i, v in ipairs(value) do
1689 if luci.util.contains(self.keylist, v) then
1690 table.insert(valid, v)
1697 DynamicList = class(AbstractValue)
1699 function DynamicList.__init__(self, ...)
1700 AbstractValue.__init__(self, ...)
1701 self.template = "cbi/dynlist"
1707 function DynamicList.reset_values(self)
1712 function DynamicList.value(self, key, val)
1714 table.insert(self.keylist, tostring(key))
1715 table.insert(self.vallist, tostring(val))
1718 function DynamicList.write(self, section, value)
1721 if type(value) == "table" then
1723 for _, x in ipairs(value) do
1724 if x and #x > 0 then
1732 if self.cast == "string" then
1733 value = table.concat(t, " ")
1738 return AbstractValue.write(self, section, value)
1741 function DynamicList.cfgvalue(self, section)
1742 local value = AbstractValue.cfgvalue(self, section)
1744 if type(value) == "string" then
1747 for x in value:gmatch("%S+") do
1758 function DynamicList.formvalue(self, section)
1759 local value = AbstractValue.formvalue(self, section)
1761 if type(value) == "string" then
1762 if self.cast == "string" then
1765 for x in value:gmatch("%S+") do
1779 TextValue - A multi-line value
1782 TextValue = class(AbstractValue)
1784 function TextValue.__init__(self, ...)
1785 AbstractValue.__init__(self, ...)
1786 self.template = "cbi/tvalue"
1792 Button = class(AbstractValue)
1794 function Button.__init__(self, ...)
1795 AbstractValue.__init__(self, ...)
1796 self.template = "cbi/button"
1797 self.inputstyle = nil
1802 FileUpload = class(AbstractValue)
1804 function FileUpload.__init__(self, ...)
1805 AbstractValue.__init__(self, ...)
1806 self.template = "cbi/upload"
1807 if not self.map.upload_fields then
1808 self.map.upload_fields = { self }
1810 self.map.upload_fields[#self.map.upload_fields+1] = self
1814 function FileUpload.formcreated(self, section)
1815 return AbstractValue.formcreated(self, section) or
1816 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1817 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1820 function FileUpload.cfgvalue(self, section)
1821 local val = AbstractValue.cfgvalue(self, section)
1822 if val and fs.access(val) then
1828 function FileUpload.formvalue(self, section)
1829 local val = AbstractValue.formvalue(self, section)
1831 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1832 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1842 function FileUpload.remove(self, section)
1843 local val = AbstractValue.formvalue(self, section)
1844 if val and fs.access(val) then fs.unlink(val) end
1845 return AbstractValue.remove(self, section)
1849 FileBrowser = class(AbstractValue)
1851 function FileBrowser.__init__(self, ...)
1852 AbstractValue.__init__(self, ...)
1853 self.template = "cbi/browser"