1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
4 module("luci.cbi", package.seeall)
6 require("luci.template")
7 local util = require("luci.util")
11 --local event = require "luci.sys.event"
12 local fs = require("nixio.fs")
13 local uci = require("luci.model.uci")
14 local datatypes = require("luci.cbi.datatypes")
15 local class = util.class
16 local instanceof = util.instanceof
28 CREATE_PREFIX = "cbi.cts."
29 REMOVE_PREFIX = "cbi.rts."
30 RESORT_PREFIX = "cbi.sts."
31 FEXIST_PREFIX = "cbi.cbe."
33 -- Loads a CBI map from given file, creating an environment and returns it
34 function load(cbimap, ...)
35 local fs = require "nixio.fs"
36 local i18n = require "luci.i18n"
37 require("luci.config")
40 local upldir = "/lib/uci/upload/"
41 local cbidir = luci.util.libpath() .. "/model/cbi/"
44 if fs.access(cbidir..cbimap..".lua") then
45 func, err = loadfile(cbidir..cbimap..".lua")
46 elseif fs.access(cbimap) then
47 func, err = loadfile(cbimap)
49 func, err = nil, "Model '" .. cbimap .. "' not found!"
55 translate=i18n.translate,
56 translatef=i18n.translatef,
60 setfenv(func, setmetatable(env, {__index =
62 return rawget(tbl, key) or _M[key] or _G[key]
65 local maps = { func() }
67 local has_upload = false
69 for i, map in ipairs(maps) do
70 if not instanceof(map, Node) then
71 error("CBI map returns no valid map object!")
75 if map.upload_fields then
77 for _, field in ipairs(map.upload_fields) do
79 field.config .. '.' ..
80 (field.section.sectiontype or '1') .. '.' ..
89 local uci = luci.model.uci.cursor()
90 local prm = luci.http.context.request.message.params
93 luci.http.setfilehandler(
94 function( field, chunk, eof )
95 if not field then return end
96 if field.name and not cbid then
97 local c, s, o = field.name:gmatch(
98 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
101 if c and s and o then
102 local t = uci:get( c, s ) or s
103 if uploads[c.."."..t.."."..o] then
104 local path = upldir .. field.name
105 fd = io.open(path, "w")
114 if field.name == cbid and fd then
131 -- Compile a datatype specification into a parse tree for evaluation later on
133 local cdt_cache = { }
135 function compile_datatype(code)
142 for i = 1, #code+1 do
143 local byte = code:byte(i) or 44
146 elseif byte == 92 then
148 elseif byte == 40 or byte == 44 then
151 local label = code:sub(pos, i-1)
156 if #label > 0 and tonumber(label) then
157 stack[#stack+1] = tonumber(label)
158 elseif label:match("^'.*'$") or label:match('^".*"$') then
159 stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1")
160 elseif type(datatypes[label]) == "function" then
161 stack[#stack+1] = datatypes[label]
162 stack[#stack+1] = { }
164 error("Datatype error, bad token %q" % label)
169 depth = depth + (byte == 40 and 1 or 0)
170 elseif byte == 41 then
173 if type(stack[#stack-1]) ~= "function" then
174 error("Datatype error, argument list follows non-function")
176 stack[#stack] = compile_datatype(code:sub(pos, i-1))
185 function verify_datatype(dt, value)
186 if dt and #dt > 0 then
187 if not cdt_cache[dt] then
188 local c = compile_datatype(dt)
189 if c and type(c[1]) == "function" then
192 error("Datatype error, not a function expression")
195 if cdt_cache[dt] then
196 return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2]))
203 -- Node pseudo abstract class
206 function Node.__init__(self, title, description)
208 self.title = title or ""
209 self.description = description or ""
210 self.template = "cbi/node"
214 function Node._run_hook(self, hook)
215 if type(self[hook]) == "function" then
216 return self[hook](self)
220 function Node._run_hooks(self, ...)
223 for _, f in ipairs(arg) do
224 if type(self[f]) == "function" then
233 function Node.prepare(self, ...)
234 for k, child in ipairs(self.children) do
239 -- Append child nodes
240 function Node.append(self, obj)
241 table.insert(self.children, obj)
244 -- Parse this node and its children
245 function Node.parse(self, ...)
246 for k, child in ipairs(self.children) do
252 function Node.render(self, scope)
256 luci.template.render(self.template, scope)
259 -- Render the children
260 function Node.render_children(self, ...)
262 for k, node in ipairs(self.children) do
263 node.last_child = (k == #self.children)
270 A simple template element
272 Template = class(Node)
274 function Template.__init__(self, template)
276 self.template = template
279 function Template.render(self)
280 luci.template.render(self.template, {self=self})
283 function Template.parse(self, readinput)
284 self.readinput = (readinput ~= false)
285 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
290 Map - A map describing a configuration file
294 function Map.__init__(self, config, ...)
295 Node.__init__(self, ...)
298 self.parsechain = {self.config}
299 self.template = "cbi/map"
300 self.apply_on_parse = nil
301 self.readinput = true
305 self.uci = uci.cursor()
310 if not self.uci:load(self.config) then
311 error("Unable to read UCI data: " .. self.config)
315 function Map.formvalue(self, key)
316 return self.readinput and luci.http.formvalue(key)
319 function Map.formvaluetable(self, key)
320 return self.readinput and luci.http.formvaluetable(key) or {}
323 function Map.get_scheme(self, sectiontype, option)
325 return self.scheme and self.scheme.sections[sectiontype]
327 return self.scheme and self.scheme.variables[sectiontype]
328 and self.scheme.variables[sectiontype][option]
332 function Map.submitstate(self)
333 return self:formvalue("cbi.submit")
336 -- Chain foreign config
337 function Map.chain(self, config)
338 table.insert(self.parsechain, config)
341 function Map.state_handler(self, state)
345 -- Use optimized UCI writing
346 function Map.parse(self, readinput, ...)
347 self.readinput = (readinput ~= false)
348 self:_run_hooks("on_parse")
350 if self:formvalue("cbi.skip") then
351 self.state = FORM_SKIP
352 return self:state_handler(self.state)
355 Node.parse(self, ...)
358 self:_run_hooks("on_save", "on_before_save")
359 for i, config in ipairs(self.parsechain) do
360 self.uci:save(config)
362 self:_run_hooks("on_after_save")
363 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
364 self:_run_hooks("on_before_commit")
365 for i, config in ipairs(self.parsechain) do
366 self.uci:commit(config)
368 -- Refresh data because commit changes section names
369 self.uci:load(config)
371 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
372 if self.apply_on_parse then
373 self.uci:apply(self.parsechain)
374 self:_run_hooks("on_apply", "on_after_apply")
376 -- This is evaluated by the dispatcher and delegated to the
377 -- template which in turn fires XHR to perform the actual
379 self.apply_needed = true
383 Node.parse(self, true)
386 for i, config in ipairs(self.parsechain) do
387 self.uci:unload(config)
389 if type(self.commit_handler) == "function" then
390 self:commit_handler(self:submitstate())
394 if self:submitstate() then
395 if not self.save then
396 self.state = FORM_INVALID
397 elseif self.proceed then
398 self.state = FORM_PROCEED
400 self.state = self.changed and FORM_CHANGED or FORM_VALID
403 self.state = FORM_NODATA
406 return self:state_handler(self.state)
409 function Map.render(self, ...)
410 self:_run_hooks("on_init")
411 Node.render(self, ...)
414 -- Creates a child section
415 function Map.section(self, class, ...)
416 if instanceof(class, AbstractSection) then
417 local obj = class(self, ...)
421 error("class must be a descendent of AbstractSection")
426 function Map.add(self, sectiontype)
427 return self.uci:add(self.config, sectiontype)
431 function Map.set(self, section, option, value)
432 if type(value) ~= "table" or #value > 0 then
434 return self.uci:set(self.config, section, option, value)
436 return self.uci:set(self.config, section, value)
439 return Map.del(self, section, option)
444 function Map.del(self, section, option)
446 return self.uci:delete(self.config, section, option)
448 return self.uci:delete(self.config, section)
453 function Map.get(self, section, option)
455 return self.uci:get_all(self.config)
457 return self.uci:get(self.config, section, option)
459 return self.uci:get_all(self.config, section)
466 Compound = class(Node)
468 function Compound.__init__(self, ...)
470 self.template = "cbi/compound"
471 self.children = {...}
474 function Compound.populate_delegator(self, delegator)
475 for _, v in ipairs(self.children) do
476 v.delegator = delegator
480 function Compound.parse(self, ...)
481 local cstate, state = 0
483 for k, child in ipairs(self.children) do
484 cstate = child:parse(...)
485 state = (not state or cstate < state) and cstate or state
493 Delegator - Node controller
495 Delegator = class(Node)
496 function Delegator.__init__(self, ...)
497 Node.__init__(self, ...)
499 self.defaultpath = {}
500 self.pageaction = false
501 self.readinput = true
502 self.allow_reset = false
503 self.allow_cancel = false
504 self.allow_back = false
505 self.allow_finish = false
506 self.template = "cbi/delegator"
509 function Delegator.set(self, name, node)
510 assert(not self.nodes[name], "Duplicate entry")
512 self.nodes[name] = node
515 function Delegator.add(self, name, node)
516 node = self:set(name, node)
517 self.defaultpath[#self.defaultpath+1] = name
520 function Delegator.insert_after(self, name, after)
521 local n = #self.chain + 1
522 for k, v in ipairs(self.chain) do
528 table.insert(self.chain, n, name)
531 function Delegator.set_route(self, ...)
532 local n, chain, route = 0, self.chain, {...}
534 if chain[i] == self.current then
543 for i = n + 1, #chain do
548 function Delegator.get(self, name)
549 local node = self.nodes[name]
551 if type(node) == "string" then
552 node = load(node, name)
555 if type(node) == "table" and getmetatable(node) == nil then
556 node = Compound(unpack(node))
562 function Delegator.parse(self, ...)
563 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
564 if self:_run_hooks("on_cancel") then
569 if not Map.formvalue(self, "cbi.delg.current") then
570 self:_run_hooks("on_init")
574 self.chain = self.chain or self:get_chain()
575 self.current = self.current or self:get_active()
576 self.active = self.active or self:get(self.current)
577 assert(self.active, "Invalid state")
579 local stat = FORM_DONE
580 if type(self.active) ~= "function" then
581 self.active:populate_delegator(self)
582 stat = self.active:parse()
587 if stat > FORM_PROCEED then
588 if Map.formvalue(self, "cbi.delg.back") then
589 newcurrent = self:get_prev(self.current)
591 newcurrent = self:get_next(self.current)
593 elseif stat < FORM_PROCEED then
598 if not Map.formvalue(self, "cbi.submit") then
600 elseif stat > FORM_PROCEED
601 and (not newcurrent or not self:get(newcurrent)) then
602 return self:_run_hook("on_done") or FORM_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:populate_delegator(self)
608 local stat = self.active:parse(false)
609 if stat == FORM_SKIP then
610 return self:parse(...)
615 return self:parse(...)
620 function Delegator.get_next(self, state)
621 for k, v in ipairs(self.chain) do
623 return self.chain[k+1]
628 function Delegator.get_prev(self, state)
629 for k, v in ipairs(self.chain) do
631 return self.chain[k-1]
636 function Delegator.get_chain(self)
637 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
638 return type(x) == "table" and x or {x}
641 function Delegator.get_active(self)
642 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
650 Page.__init__ = Node.__init__
651 Page.parse = function() end
655 SimpleForm - A Simple non-UCI form
657 SimpleForm = class(Node)
659 function SimpleForm.__init__(self, config, title, description, data)
660 Node.__init__(self, title, description)
662 self.data = data or {}
663 self.template = "cbi/simpleform"
665 self.pageaction = false
666 self.readinput = true
669 SimpleForm.formvalue = Map.formvalue
670 SimpleForm.formvaluetable = Map.formvaluetable
672 function SimpleForm.parse(self, readinput, ...)
673 self.readinput = (readinput ~= false)
675 if self:formvalue("cbi.skip") then
679 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
683 if self:submitstate() then
684 Node.parse(self, 1, ...)
688 for k, j in ipairs(self.children) do
689 for i, v in ipairs(j.children) do
691 and (not v.tag_missing or not v.tag_missing[1])
692 and (not v.tag_invalid or not v.tag_invalid[1])
698 not self:submitstate() and FORM_NODATA
699 or valid and FORM_VALID
702 self.dorender = not self.handle
704 local nrender, nstate = self:handle(state, self.data)
705 self.dorender = self.dorender or (nrender ~= false)
706 state = nstate or state
711 function SimpleForm.render(self, ...)
712 if self.dorender then
713 Node.render(self, ...)
717 function SimpleForm.submitstate(self)
718 return self:formvalue("cbi.submit")
721 function SimpleForm.section(self, class, ...)
722 if instanceof(class, AbstractSection) then
723 local obj = class(self, ...)
727 error("class must be a descendent of AbstractSection")
731 -- Creates a child field
732 function SimpleForm.field(self, class, ...)
734 for k, v in ipairs(self.children) do
735 if instanceof(v, SimpleSection) then
741 section = self:section(SimpleSection)
744 if instanceof(class, AbstractValue) then
745 local obj = class(self, section, ...)
746 obj.track_missing = true
750 error("class must be a descendent of AbstractValue")
754 function SimpleForm.set(self, section, option, value)
755 self.data[option] = value
759 function SimpleForm.del(self, section, option)
760 self.data[option] = nil
764 function SimpleForm.get(self, section, option)
765 return self.data[option]
769 function SimpleForm.get_scheme()
774 Form = class(SimpleForm)
776 function Form.__init__(self, ...)
777 SimpleForm.__init__(self, ...)
785 AbstractSection = class(Node)
787 function AbstractSection.__init__(self, map, sectiontype, ...)
788 Node.__init__(self, ...)
789 self.sectiontype = sectiontype
791 self.config = map.config
796 self.tag_invalid = {}
797 self.tag_deperror = {}
801 self.addremove = false
805 -- Define a tab for the section
806 function AbstractSection.tab(self, tab, title, desc)
807 self.tabs = self.tabs or { }
808 self.tab_names = self.tab_names or { }
810 self.tab_names[#self.tab_names+1] = tab
818 -- Check whether the section has tabs
819 function AbstractSection.has_tabs(self)
820 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
823 -- Appends a new option
824 function AbstractSection.option(self, class, option, ...)
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))
858 for k, node in ipairs(self.tabs[tab].childs) do
859 node.last_child = (k == #self.tabs[tab].childs)
864 -- Parse optional options
865 function AbstractSection.parse_optionals(self, section)
866 if not self.optional then
870 self.optionals[section] = {}
872 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
873 for k,v in ipairs(self.children) do
874 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
875 if field == v.option then
877 self.map.proceed = true
879 table.insert(self.optionals[section], v)
884 if field and #field > 0 and self.dynamic then
885 self:add_dynamic(field)
889 -- Add a dynamic option
890 function AbstractSection.add_dynamic(self, field, optional)
891 local o = self:option(Value, field, field)
892 o.optional = optional
895 -- Parse all dynamic options
896 function AbstractSection.parse_dynamic(self, section)
897 if not self.dynamic then
901 local arr = luci.util.clone(self:cfgvalue(section))
902 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
903 for k, v in pairs(form) do
907 for key,val in pairs(arr) do
910 for i,c in ipairs(self.children) do
911 if c.option == key then
916 if create and key:sub(1, 1) ~= "." then
917 self.map.proceed = true
918 self:add_dynamic(key, true)
923 -- Returns the section's UCI table
924 function AbstractSection.cfgvalue(self, section)
925 return self.map:get(section)
929 function AbstractSection.push_events(self)
930 --luci.util.append(self.map.events, self.events)
931 self.map.changed = true
934 -- Removes the section
935 function AbstractSection.remove(self, section)
936 self.map.proceed = true
937 return self.map:del(section)
940 -- Creates the section
941 function AbstractSection.create(self, section)
945 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
947 section = self.map:add(self.sectiontype)
952 for k,v in pairs(self.children) do
954 self.map:set(section, v.option, v.default)
958 for k,v in pairs(self.defaults) do
959 self.map:set(section, k, v)
963 self.map.proceed = true
969 SimpleSection = class(AbstractSection)
971 function SimpleSection.__init__(self, form, ...)
972 AbstractSection.__init__(self, form, nil, ...)
973 self.template = "cbi/nullsection"
977 Table = class(AbstractSection)
979 function Table.__init__(self, form, data, ...)
980 local datasource = {}
982 datasource.config = "table"
983 self.data = data or {}
985 datasource.formvalue = Map.formvalue
986 datasource.formvaluetable = Map.formvaluetable
987 datasource.readinput = true
989 function datasource.get(self, section, option)
990 return tself.data[section] and tself.data[section][option]
993 function datasource.submitstate(self)
994 return Map.formvalue(self, "cbi.submit")
997 function datasource.del(...)
1001 function datasource.get_scheme()
1005 AbstractSection.__init__(self, datasource, "table", ...)
1006 self.template = "cbi/tblsection"
1007 self.rowcolors = true
1008 self.anonymous = true
1011 function Table.parse(self, readinput)
1012 self.map.readinput = (readinput ~= false)
1013 for i, k in ipairs(self:cfgsections()) do
1014 if self.map:submitstate() then
1020 function Table.cfgsections(self)
1023 for i, v in luci.util.kspairs(self.data) do
1024 table.insert(sections, i)
1030 function Table.update(self, data)
1037 NamedSection - A fixed configuration section defined by its name
1039 NamedSection = class(AbstractSection)
1041 function NamedSection.__init__(self, map, section, stype, ...)
1042 AbstractSection.__init__(self, map, stype, ...)
1045 self.addremove = false
1046 self.template = "cbi/nsection"
1047 self.section = section
1050 function NamedSection.parse(self, novld)
1051 local s = self.section
1052 local active = self:cfgvalue(s)
1054 if self.addremove then
1055 local path = self.config.."."..s
1056 if active then -- Remove the section
1057 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1061 else -- Create and apply default values
1062 if self.map:formvalue("cbi.cns."..path) then
1070 AbstractSection.parse_dynamic(self, s)
1071 if self.map:submitstate() then
1074 AbstractSection.parse_optionals(self, s)
1076 if self.changed then
1084 TypedSection - A (set of) configuration section(s) defined by the type
1085 addremove: Defines whether the user can add/remove sections of this type
1086 anonymous: Allow creating anonymous sections
1087 validate: a validation function returning nil if the section is invalid
1089 TypedSection = class(AbstractSection)
1091 function TypedSection.__init__(self, map, type, ...)
1092 AbstractSection.__init__(self, map, type, ...)
1094 self.template = "cbi/tsection"
1096 self.anonymous = false
1099 -- Return all matching UCI sections for this TypedSection
1100 function TypedSection.cfgsections(self)
1102 self.map.uci:foreach(self.map.config, self.sectiontype,
1104 if self:checkscope(section[".name"]) then
1105 table.insert(sections, section[".name"])
1112 -- Limits scope to sections that have certain option => value pairs
1113 function TypedSection.depends(self, option, value)
1114 table.insert(self.deps, {option=option, value=value})
1117 function TypedSection.parse(self, novld)
1118 if self.addremove then
1120 local crval = REMOVE_PREFIX .. self.config
1121 local name = self.map:formvaluetable(crval)
1122 for k,v in pairs(name) do
1123 if k:sub(-2) == ".x" then
1124 k = k:sub(1, #k - 2)
1126 if self:cfgvalue(k) and self:checkscope(k) then
1133 for i, k in ipairs(self:cfgsections()) do
1134 AbstractSection.parse_dynamic(self, k)
1135 if self.map:submitstate() then
1136 Node.parse(self, k, novld)
1138 AbstractSection.parse_optionals(self, k)
1141 if self.addremove then
1144 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1145 local origin, name = next(self.map:formvaluetable(crval))
1146 if self.anonymous then
1148 created = self:create(nil, origin)
1152 -- Ignore if it already exists
1153 if self:cfgvalue(name) then
1157 name = self:checkscope(name)
1160 self.err_invalid = true
1163 if name and #name > 0 then
1164 created = self:create(name, origin) and name
1166 self.invalid_cts = true
1173 AbstractSection.parse_optionals(self, created)
1177 if self.sortable then
1178 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1179 local order = self.map:formvalue(stval)
1180 if order and #order > 0 then
1183 for sid in util.imatch(order) do
1184 self.map.uci:reorder(self.config, sid, num)
1187 self.changed = (num > 0)
1191 if created or self.changed then
1196 -- Verifies scope of sections
1197 function TypedSection.checkscope(self, section)
1198 -- Check if we are not excluded
1199 if self.filter and not self:filter(section) then
1203 -- Check if at least one dependency is met
1204 if #self.deps > 0 and self:cfgvalue(section) then
1207 for k, v in ipairs(self.deps) do
1208 if self:cfgvalue(section)[v.option] == v.value then
1218 return self:validate(section)
1222 -- Dummy validate function
1223 function TypedSection.validate(self, section)
1229 AbstractValue - An abstract Value Type
1230 null: Value can be empty
1231 valid: A function returning the value if it is valid otherwise nil
1232 depends: A table of option => value pairs of which one must be true
1233 default: The default value
1234 size: The size of the input fields
1235 rmempty: Unset value if empty
1236 optional: This value is optional (see AbstractSection.optionals)
1238 AbstractValue = class(Node)
1240 function AbstractValue.__init__(self, map, section, option, ...)
1241 Node.__init__(self, ...)
1242 self.section = section
1243 self.option = option
1245 self.config = map.config
1246 self.tag_invalid = {}
1247 self.tag_missing = {}
1248 self.tag_reqerror = {}
1252 --self.cast = "string"
1254 self.track_missing = false
1258 self.optional = false
1261 function AbstractValue.prepare(self)
1262 self.cast = self.cast or "string"
1265 -- Add a dependencie to another section field
1266 function AbstractValue.depends(self, field, value)
1268 if type(field) == "string" then
1275 table.insert(self.deps, {deps=deps, add=""})
1278 -- Generates the unique CBID
1279 function AbstractValue.cbid(self, section)
1280 return "cbid."..self.map.config.."."..section.."."..self.option
1283 -- Return whether this object should be created
1284 function AbstractValue.formcreated(self, section)
1285 local key = "cbi.opt."..self.config.."."..section
1286 return (self.map:formvalue(key) == self.option)
1289 -- Returns the formvalue for this object
1290 function AbstractValue.formvalue(self, section)
1291 return self.map:formvalue(self:cbid(section))
1294 function AbstractValue.additional(self, value)
1295 self.optional = value
1298 function AbstractValue.mandatory(self, value)
1299 self.rmempty = not value
1302 function AbstractValue.add_error(self, section, type, msg)
1303 self.error = self.error or { }
1304 self.error[section] = msg or type
1306 self.section.error = self.section.error or { }
1307 self.section.error[section] = self.section.error[section] or { }
1308 table.insert(self.section.error[section], msg or type)
1310 if type == "invalid" then
1311 self.tag_invalid[section] = true
1312 elseif type == "missing" then
1313 self.tag_missing[section] = true
1316 self.tag_error[section] = true
1317 self.map.save = false
1320 function AbstractValue.parse(self, section, novld)
1321 local fvalue = self:formvalue(section)
1322 local cvalue = self:cfgvalue(section)
1324 -- If favlue and cvalue are both tables and have the same content
1325 -- make them identical
1326 if type(fvalue) == "table" and type(cvalue) == "table" then
1327 local equal = #fvalue == #cvalue
1330 if cvalue[i] ~= fvalue[i] then
1340 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1342 fvalue, val_err = self:validate(fvalue, section)
1343 fvalue = self:transform(fvalue)
1345 if not fvalue and not novld then
1346 self:add_error(section, "invalid", val_err)
1349 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1350 if self:write(section, fvalue) then
1352 self.section.changed = true
1353 --luci.util.append(self.map.events, self.events)
1356 else -- Unset the UCI or error
1357 if self.rmempty or self.optional then
1358 if self:remove(section) then
1360 self.section.changed = true
1361 --luci.util.append(self.map.events, self.events)
1363 elseif cvalue ~= fvalue and not novld then
1364 -- trigger validator with nil value to get custom user error msg.
1365 local _, val_err = self:validate(nil, section)
1366 self:add_error(section, "missing", val_err)
1371 -- Render if this value exists or if it is mandatory
1372 function AbstractValue.render(self, s, scope)
1373 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1376 scope.cbid = self:cbid(s)
1377 Node.render(self, scope)
1381 -- Return the UCI value of this object
1382 function AbstractValue.cfgvalue(self, section)
1384 if self.tag_error[section] then
1385 value = self:formvalue(section)
1387 value = self.map:get(section, self.option)
1392 elseif not self.cast or self.cast == type(value) then
1394 elseif self.cast == "string" then
1395 if type(value) == "table" then
1398 elseif self.cast == "table" then
1403 -- Validate the form value
1404 function AbstractValue.validate(self, value)
1405 if self.datatype and value then
1406 if type(value) == "table" then
1408 for _, v in ipairs(value) do
1409 if v and #v > 0 and not verify_datatype(self.datatype, v) then
1414 if not verify_datatype(self.datatype, value) then
1423 AbstractValue.transform = AbstractValue.validate
1427 function AbstractValue.write(self, section, value)
1428 return self.map:set(section, self.option, value)
1432 function AbstractValue.remove(self, section)
1433 return self.map:del(section, self.option)
1440 Value - A one-line value
1441 maxlength: The maximum length
1443 Value = class(AbstractValue)
1445 function Value.__init__(self, ...)
1446 AbstractValue.__init__(self, ...)
1447 self.template = "cbi/value"
1452 function Value.reset_values(self)
1457 function Value.value(self, key, val)
1459 table.insert(self.keylist, tostring(key))
1460 table.insert(self.vallist, tostring(val))
1464 -- DummyValue - This does nothing except being there
1465 DummyValue = class(AbstractValue)
1467 function DummyValue.__init__(self, ...)
1468 AbstractValue.__init__(self, ...)
1469 self.template = "cbi/dvalue"
1473 function DummyValue.cfgvalue(self, section)
1476 if type(self.value) == "function" then
1477 value = self:value(section)
1482 value = AbstractValue.cfgvalue(self, section)
1487 function DummyValue.parse(self)
1493 Flag - A flag being enabled or disabled
1495 Flag = class(AbstractValue)
1497 function Flag.__init__(self, ...)
1498 AbstractValue.__init__(self, ...)
1499 self.template = "cbi/fvalue"
1503 self.default = self.disabled
1506 -- A flag can only have two states: set or unset
1507 function Flag.parse(self, section)
1508 local fexists = self.map:formvalue(
1509 FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
1512 local fvalue = self:formvalue(section) and self.enabled or self.disabled
1513 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
1514 self:write(section, fvalue)
1516 self:remove(section)
1519 self:remove(section)
1523 function Flag.cfgvalue(self, section)
1524 return AbstractValue.cfgvalue(self, section) or self.default
1529 ListValue - A one-line value predefined in a list
1530 widget: The widget that will be used (select, radio)
1532 ListValue = class(AbstractValue)
1534 function ListValue.__init__(self, ...)
1535 AbstractValue.__init__(self, ...)
1536 self.template = "cbi/lvalue"
1541 self.widget = "select"
1544 function ListValue.reset_values(self)
1549 function ListValue.value(self, key, val, ...)
1550 if luci.util.contains(self.keylist, key) then
1555 table.insert(self.keylist, tostring(key))
1556 table.insert(self.vallist, tostring(val))
1558 for i, deps in ipairs({...}) do
1559 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1563 function ListValue.validate(self, val)
1564 if luci.util.contains(self.keylist, val) then
1574 MultiValue - Multiple delimited values
1575 widget: The widget that will be used (select, checkbox)
1576 delimiter: The delimiter that will separate the values (default: " ")
1578 MultiValue = class(AbstractValue)
1580 function MultiValue.__init__(self, ...)
1581 AbstractValue.__init__(self, ...)
1582 self.template = "cbi/mvalue"
1587 self.widget = "checkbox"
1588 self.delimiter = " "
1591 function MultiValue.render(self, ...)
1592 if self.widget == "select" and not self.size then
1593 self.size = #self.vallist
1596 AbstractValue.render(self, ...)
1599 function MultiValue.reset_values(self)
1604 function MultiValue.value(self, key, val)
1605 if luci.util.contains(self.keylist, key) then
1610 table.insert(self.keylist, tostring(key))
1611 table.insert(self.vallist, tostring(val))
1614 function MultiValue.valuelist(self, section)
1615 local val = self:cfgvalue(section)
1617 if not(type(val) == "string") then
1621 return luci.util.split(val, self.delimiter)
1624 function MultiValue.validate(self, val)
1625 val = (type(val) == "table") and val or {val}
1629 for i, value in ipairs(val) do
1630 if luci.util.contains(self.keylist, value) then
1631 result = result and (result .. self.delimiter .. value) or value
1639 StaticList = class(MultiValue)
1641 function StaticList.__init__(self, ...)
1642 MultiValue.__init__(self, ...)
1644 self.valuelist = self.cfgvalue
1646 if not self.override_scheme
1647 and self.map:get_scheme(self.section.sectiontype, self.option) then
1648 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1649 if self.value and vs.values and not self.override_values then
1650 for k, v in pairs(vs.values) do
1657 function StaticList.validate(self, value)
1658 value = (type(value) == "table") and value or {value}
1661 for i, v in ipairs(value) do
1662 if luci.util.contains(self.keylist, v) then
1663 table.insert(valid, v)
1670 DynamicList = class(AbstractValue)
1672 function DynamicList.__init__(self, ...)
1673 AbstractValue.__init__(self, ...)
1674 self.template = "cbi/dynlist"
1680 function DynamicList.reset_values(self)
1685 function DynamicList.value(self, key, val)
1687 table.insert(self.keylist, tostring(key))
1688 table.insert(self.vallist, tostring(val))
1691 function DynamicList.write(self, section, value)
1694 if type(value) == "table" then
1696 for _, x in ipairs(value) do
1697 if x and #x > 0 then
1705 if self.cast == "string" then
1706 value = table.concat(t, " ")
1711 return AbstractValue.write(self, section, value)
1714 function DynamicList.cfgvalue(self, section)
1715 local value = AbstractValue.cfgvalue(self, section)
1717 if type(value) == "string" then
1720 for x in value:gmatch("%S+") do
1731 function DynamicList.formvalue(self, section)
1732 local value = AbstractValue.formvalue(self, section)
1734 if type(value) == "string" then
1735 if self.cast == "string" then
1738 for x in value:gmatch("%S+") do
1752 TextValue - A multi-line value
1755 TextValue = class(AbstractValue)
1757 function TextValue.__init__(self, ...)
1758 AbstractValue.__init__(self, ...)
1759 self.template = "cbi/tvalue"
1765 Button = class(AbstractValue)
1767 function Button.__init__(self, ...)
1768 AbstractValue.__init__(self, ...)
1769 self.template = "cbi/button"
1770 self.inputstyle = nil
1775 FileUpload = class(AbstractValue)
1777 function FileUpload.__init__(self, ...)
1778 AbstractValue.__init__(self, ...)
1779 self.template = "cbi/upload"
1780 if not self.map.upload_fields then
1781 self.map.upload_fields = { self }
1783 self.map.upload_fields[#self.map.upload_fields+1] = self
1787 function FileUpload.formcreated(self, section)
1788 return AbstractValue.formcreated(self, section) or
1789 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1790 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1793 function FileUpload.cfgvalue(self, section)
1794 local val = AbstractValue.cfgvalue(self, section)
1795 if val and fs.access(val) then
1801 function FileUpload.formvalue(self, section)
1802 local val = AbstractValue.formvalue(self, section)
1804 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1805 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1815 function FileUpload.remove(self, section)
1816 local val = AbstractValue.formvalue(self, section)
1817 if val and fs.access(val) then fs.unlink(val) end
1818 return AbstractValue.remove(self, section)
1822 FileBrowser = class(AbstractValue)
1824 function FileBrowser.__init__(self, ...)
1825 AbstractValue.__init__(self, ...)
1826 self.template = "cbi/browser"