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!"
78 translate=i18n.translate,
79 translatef=i18n.translatef,
83 setfenv(func, setmetatable(env, {__index =
85 return rawget(tbl, key) or _M[key] or _G[key]
88 local maps = { func() }
90 local has_upload = false
92 for i, map in ipairs(maps) do
93 if not instanceof(map, Node) then
94 error("CBI map returns no valid map object!")
98 if map.upload_fields then
100 for _, field in ipairs(map.upload_fields) do
102 field.config .. '.' ..
103 (field.section.sectiontype or '1') .. '.' ..
112 local uci = luci.model.uci.cursor()
113 local prm = luci.http.context.request.message.params
116 luci.http.setfilehandler(
117 function( field, chunk, eof )
118 if not field then return end
119 if field.name and not cbid then
120 local c, s, o = field.name:gmatch(
121 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
124 if c and s and o then
125 local t = uci:get( c, s ) or s
126 if uploads[c.."."..t.."."..o] then
127 local path = upldir .. field.name
128 fd = io.open(path, "w")
137 if field.name == cbid and fd then
154 -- Compile a datatype specification into a parse tree for evaluation later on
156 local cdt_cache = { }
158 function compile_datatype(code)
165 for i = 1, #code+1 do
166 local byte = code:byte(i) or 44
169 elseif byte == 92 then
171 elseif byte == 40 or byte == 44 then
174 local label = code:sub(pos, i-1)
179 if #label > 0 and tonumber(label) then
180 stack[#stack+1] = tonumber(label)
181 elseif label:match("^'.*'$") or label:match('^".*"$') then
182 stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1")
183 elseif type(datatypes[label]) == "function" then
184 stack[#stack+1] = datatypes[label]
185 stack[#stack+1] = { }
187 error("Datatype error, bad token %q" % label)
192 depth = depth + (byte == 40 and 1 or 0)
193 elseif byte == 41 then
196 if type(stack[#stack-1]) ~= "function" then
197 error("Datatype error, argument list follows non-function")
199 stack[#stack] = compile_datatype(code:sub(pos, i-1))
208 function verify_datatype(dt, value)
209 if dt and #dt > 0 then
210 if not cdt_cache[dt] then
211 local c = compile_datatype(dt)
212 if c and type(c[1]) == "function" then
215 error("Datatype error, not a function expression")
218 if cdt_cache[dt] then
219 return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2]))
226 -- Node pseudo abstract class
229 function Node.__init__(self, title, description)
231 self.title = title or ""
232 self.description = description or ""
233 self.template = "cbi/node"
237 function Node._run_hook(self, hook)
238 if type(self[hook]) == "function" then
239 return self[hook](self)
243 function Node._run_hooks(self, ...)
246 for _, f in ipairs(arg) do
247 if type(self[f]) == "function" then
256 function Node.prepare(self, ...)
257 for k, child in ipairs(self.children) do
262 -- Append child nodes
263 function Node.append(self, obj)
264 table.insert(self.children, obj)
267 -- Parse this node and its children
268 function Node.parse(self, ...)
269 for k, child in ipairs(self.children) do
275 function Node.render(self, scope)
279 luci.template.render(self.template, scope)
282 -- Render the children
283 function Node.render_children(self, ...)
285 for k, node in ipairs(self.children) do
286 node.last_child = (k == #self.children)
293 A simple template element
295 Template = class(Node)
297 function Template.__init__(self, template)
299 self.template = template
302 function Template.render(self)
303 luci.template.render(self.template, {self=self})
306 function Template.parse(self, readinput)
307 self.readinput = (readinput ~= false)
308 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
313 Map - A map describing a configuration file
317 function Map.__init__(self, config, ...)
318 Node.__init__(self, ...)
321 self.parsechain = {self.config}
322 self.template = "cbi/map"
323 self.apply_on_parse = nil
324 self.readinput = true
328 self.uci = uci.cursor()
333 if not self.uci:load(self.config) then
334 error("Unable to read UCI data: " .. self.config)
338 function Map.formvalue(self, key)
339 return self.readinput and luci.http.formvalue(key)
342 function Map.formvaluetable(self, key)
343 return self.readinput and luci.http.formvaluetable(key) or {}
346 function Map.get_scheme(self, sectiontype, option)
348 return self.scheme and self.scheme.sections[sectiontype]
350 return self.scheme and self.scheme.variables[sectiontype]
351 and self.scheme.variables[sectiontype][option]
355 function Map.submitstate(self)
356 return self:formvalue("cbi.submit")
359 -- Chain foreign config
360 function Map.chain(self, config)
361 table.insert(self.parsechain, config)
364 function Map.state_handler(self, state)
368 -- Use optimized UCI writing
369 function Map.parse(self, readinput, ...)
370 self.readinput = (readinput ~= false)
371 self:_run_hooks("on_parse")
373 if self:formvalue("cbi.skip") then
374 self.state = FORM_SKIP
375 return self:state_handler(self.state)
378 Node.parse(self, ...)
381 self:_run_hooks("on_save", "on_before_save")
382 for i, config in ipairs(self.parsechain) do
383 self.uci:save(config)
385 self:_run_hooks("on_after_save")
386 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
387 self:_run_hooks("on_before_commit")
388 for i, config in ipairs(self.parsechain) do
389 self.uci:commit(config)
391 -- Refresh data because commit changes section names
392 self.uci:load(config)
394 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
395 if self.apply_on_parse then
396 self.uci:apply(self.parsechain)
397 self:_run_hooks("on_apply", "on_after_apply")
399 -- This is evaluated by the dispatcher and delegated to the
400 -- template which in turn fires XHR to perform the actual
402 self.apply_needed = true
406 Node.parse(self, true)
409 for i, config in ipairs(self.parsechain) do
410 self.uci:unload(config)
412 if type(self.commit_handler) == "function" then
413 self:commit_handler(self:submitstate())
417 if self:submitstate() then
418 if not self.save then
419 self.state = FORM_INVALID
420 elseif self.proceed then
421 self.state = FORM_PROCEED
423 self.state = self.changed and FORM_CHANGED or FORM_VALID
426 self.state = FORM_NODATA
429 return self:state_handler(self.state)
432 function Map.render(self, ...)
433 self:_run_hooks("on_init")
434 Node.render(self, ...)
437 -- Creates a child section
438 function Map.section(self, class, ...)
439 if instanceof(class, AbstractSection) then
440 local obj = class(self, ...)
444 error("class must be a descendent of AbstractSection")
449 function Map.add(self, sectiontype)
450 return self.uci:add(self.config, sectiontype)
454 function Map.set(self, section, option, value)
455 if type(value) ~= "table" or #value > 0 then
457 return self.uci:set(self.config, section, option, value)
459 return self.uci:set(self.config, section, value)
462 return Map.del(self, section, option)
467 function Map.del(self, section, option)
469 return self.uci:delete(self.config, section, option)
471 return self.uci:delete(self.config, section)
476 function Map.get(self, section, option)
478 return self.uci:get_all(self.config)
480 return self.uci:get(self.config, section, option)
482 return self.uci:get_all(self.config, section)
489 Compound = class(Node)
491 function Compound.__init__(self, ...)
493 self.template = "cbi/compound"
494 self.children = {...}
497 function Compound.populate_delegator(self, delegator)
498 for _, v in ipairs(self.children) do
499 v.delegator = delegator
503 function Compound.parse(self, ...)
504 local cstate, state = 0
506 for k, child in ipairs(self.children) do
507 cstate = child:parse(...)
508 state = (not state or cstate < state) and cstate or state
516 Delegator - Node controller
518 Delegator = class(Node)
519 function Delegator.__init__(self, ...)
520 Node.__init__(self, ...)
522 self.defaultpath = {}
523 self.pageaction = false
524 self.readinput = true
525 self.allow_reset = false
526 self.allow_cancel = false
527 self.allow_back = false
528 self.allow_finish = false
529 self.template = "cbi/delegator"
532 function Delegator.set(self, name, node)
533 assert(not self.nodes[name], "Duplicate entry")
535 self.nodes[name] = node
538 function Delegator.add(self, name, node)
539 node = self:set(name, node)
540 self.defaultpath[#self.defaultpath+1] = name
543 function Delegator.insert_after(self, name, after)
544 local n = #self.chain + 1
545 for k, v in ipairs(self.chain) do
551 table.insert(self.chain, n, name)
554 function Delegator.set_route(self, ...)
555 local n, chain, route = 0, self.chain, {...}
557 if chain[i] == self.current then
566 for i = n + 1, #chain do
571 function Delegator.get(self, name)
572 local node = self.nodes[name]
574 if type(node) == "string" then
575 node = load(node, name)
578 if type(node) == "table" and getmetatable(node) == nil then
579 node = Compound(unpack(node))
585 function Delegator.parse(self, ...)
586 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
587 if self:_run_hooks("on_cancel") then
592 if not Map.formvalue(self, "cbi.delg.current") then
593 self:_run_hooks("on_init")
597 self.chain = self.chain or self:get_chain()
598 self.current = self.current or self:get_active()
599 self.active = self.active or self:get(self.current)
600 assert(self.active, "Invalid state")
602 local stat = FORM_DONE
603 if type(self.active) ~= "function" then
604 self.active:populate_delegator(self)
605 stat = self.active:parse()
610 if stat > FORM_PROCEED then
611 if Map.formvalue(self, "cbi.delg.back") then
612 newcurrent = self:get_prev(self.current)
614 newcurrent = self:get_next(self.current)
616 elseif stat < FORM_PROCEED then
621 if not Map.formvalue(self, "cbi.submit") then
623 elseif stat > FORM_PROCEED
624 and (not newcurrent or not self:get(newcurrent)) then
625 return self:_run_hook("on_done") or FORM_DONE
627 self.current = newcurrent or self.current
628 self.active = self:get(self.current)
629 if type(self.active) ~= "function" then
630 self.active:populate_delegator(self)
631 local stat = self.active:parse(false)
632 if stat == FORM_SKIP then
633 return self:parse(...)
638 return self:parse(...)
643 function Delegator.get_next(self, state)
644 for k, v in ipairs(self.chain) do
646 return self.chain[k+1]
651 function Delegator.get_prev(self, state)
652 for k, v in ipairs(self.chain) do
654 return self.chain[k-1]
659 function Delegator.get_chain(self)
660 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
661 return type(x) == "table" and x or {x}
664 function Delegator.get_active(self)
665 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
673 Page.__init__ = Node.__init__
674 Page.parse = function() end
678 SimpleForm - A Simple non-UCI form
680 SimpleForm = class(Node)
682 function SimpleForm.__init__(self, config, title, description, data)
683 Node.__init__(self, title, description)
685 self.data = data or {}
686 self.template = "cbi/simpleform"
688 self.pageaction = false
689 self.readinput = true
692 SimpleForm.formvalue = Map.formvalue
693 SimpleForm.formvaluetable = Map.formvaluetable
695 function SimpleForm.parse(self, readinput, ...)
696 self.readinput = (readinput ~= false)
698 if self:formvalue("cbi.skip") then
702 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
706 if self:submitstate() then
707 Node.parse(self, 1, ...)
711 for k, j in ipairs(self.children) do
712 for i, v in ipairs(j.children) do
714 and (not v.tag_missing or not v.tag_missing[1])
715 and (not v.tag_invalid or not v.tag_invalid[1])
721 not self:submitstate() and FORM_NODATA
722 or valid and FORM_VALID
725 self.dorender = not self.handle
727 local nrender, nstate = self:handle(state, self.data)
728 self.dorender = self.dorender or (nrender ~= false)
729 state = nstate or state
734 function SimpleForm.render(self, ...)
735 if self.dorender then
736 Node.render(self, ...)
740 function SimpleForm.submitstate(self)
741 return self:formvalue("cbi.submit")
744 function SimpleForm.section(self, class, ...)
745 if instanceof(class, AbstractSection) then
746 local obj = class(self, ...)
750 error("class must be a descendent of AbstractSection")
754 -- Creates a child field
755 function SimpleForm.field(self, class, ...)
757 for k, v in ipairs(self.children) do
758 if instanceof(v, SimpleSection) then
764 section = self:section(SimpleSection)
767 if instanceof(class, AbstractValue) then
768 local obj = class(self, section, ...)
769 obj.track_missing = true
773 error("class must be a descendent of AbstractValue")
777 function SimpleForm.set(self, section, option, value)
778 self.data[option] = value
782 function SimpleForm.del(self, section, option)
783 self.data[option] = nil
787 function SimpleForm.get(self, section, option)
788 return self.data[option]
792 function SimpleForm.get_scheme()
797 Form = class(SimpleForm)
799 function Form.__init__(self, ...)
800 SimpleForm.__init__(self, ...)
808 AbstractSection = class(Node)
810 function AbstractSection.__init__(self, map, sectiontype, ...)
811 Node.__init__(self, ...)
812 self.sectiontype = sectiontype
814 self.config = map.config
819 self.tag_invalid = {}
820 self.tag_deperror = {}
824 self.addremove = false
828 -- Define a tab for the section
829 function AbstractSection.tab(self, tab, title, desc)
830 self.tabs = self.tabs or { }
831 self.tab_names = self.tab_names or { }
833 self.tab_names[#self.tab_names+1] = tab
841 -- Check whether the section has tabs
842 function AbstractSection.has_tabs(self)
843 return (self.tabs ~= nil) and (next(self.tabs) ~= nil)
846 -- Appends a new option
847 function AbstractSection.option(self, class, option, ...)
848 if instanceof(class, AbstractValue) then
849 local obj = class(self.map, self, option, ...)
851 self.fields[option] = obj
853 elseif class == true then
854 error("No valid class was given and autodetection failed.")
856 error("class must be a descendant of AbstractValue")
860 -- Appends a new tabbed option
861 function AbstractSection.taboption(self, tab, ...)
863 assert(tab and self.tabs and self.tabs[tab],
864 "Cannot assign option to not existing tab %q" % tostring(tab))
866 local l = self.tabs[tab].childs
867 local o = AbstractSection.option(self, ...)
869 if o then l[#l+1] = o end
874 -- Render a single tab
875 function AbstractSection.render_tab(self, tab, ...)
877 assert(tab and self.tabs and self.tabs[tab],
878 "Cannot render not existing tab %q" % tostring(tab))
881 for k, node in ipairs(self.tabs[tab].childs) do
882 node.last_child = (k == #self.tabs[tab].childs)
887 -- Parse optional options
888 function AbstractSection.parse_optionals(self, section)
889 if not self.optional then
893 self.optionals[section] = {}
895 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
896 for k,v in ipairs(self.children) do
897 if v.optional and not v:cfgvalue(section) and not self:has_tabs() then
898 if field == v.option then
900 self.map.proceed = true
902 table.insert(self.optionals[section], v)
907 if field and #field > 0 and self.dynamic then
908 self:add_dynamic(field)
912 -- Add a dynamic option
913 function AbstractSection.add_dynamic(self, field, optional)
914 local o = self:option(Value, field, field)
915 o.optional = optional
918 -- Parse all dynamic options
919 function AbstractSection.parse_dynamic(self, section)
920 if not self.dynamic then
924 local arr = luci.util.clone(self:cfgvalue(section))
925 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
926 for k, v in pairs(form) do
930 for key,val in pairs(arr) do
933 for i,c in ipairs(self.children) do
934 if c.option == key then
939 if create and key:sub(1, 1) ~= "." then
940 self.map.proceed = true
941 self:add_dynamic(key, true)
946 -- Returns the section's UCI table
947 function AbstractSection.cfgvalue(self, section)
948 return self.map:get(section)
952 function AbstractSection.push_events(self)
953 --luci.util.append(self.map.events, self.events)
954 self.map.changed = true
957 -- Removes the section
958 function AbstractSection.remove(self, section)
959 self.map.proceed = true
960 return self.map:del(section)
963 -- Creates the section
964 function AbstractSection.create(self, section)
968 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
970 section = self.map:add(self.sectiontype)
975 for k,v in pairs(self.children) do
977 self.map:set(section, v.option, v.default)
981 for k,v in pairs(self.defaults) do
982 self.map:set(section, k, v)
986 self.map.proceed = true
992 SimpleSection = class(AbstractSection)
994 function SimpleSection.__init__(self, form, ...)
995 AbstractSection.__init__(self, form, nil, ...)
996 self.template = "cbi/nullsection"
1000 Table = class(AbstractSection)
1002 function Table.__init__(self, form, data, ...)
1003 local datasource = {}
1005 datasource.config = "table"
1006 self.data = data or {}
1008 datasource.formvalue = Map.formvalue
1009 datasource.formvaluetable = Map.formvaluetable
1010 datasource.readinput = true
1012 function datasource.get(self, section, option)
1013 return tself.data[section] and tself.data[section][option]
1016 function datasource.submitstate(self)
1017 return Map.formvalue(self, "cbi.submit")
1020 function datasource.del(...)
1024 function datasource.get_scheme()
1028 AbstractSection.__init__(self, datasource, "table", ...)
1029 self.template = "cbi/tblsection"
1030 self.rowcolors = true
1031 self.anonymous = true
1034 function Table.parse(self, readinput)
1035 self.map.readinput = (readinput ~= false)
1036 for i, k in ipairs(self:cfgsections()) do
1037 if self.map:submitstate() then
1043 function Table.cfgsections(self)
1046 for i, v in luci.util.kspairs(self.data) do
1047 table.insert(sections, i)
1053 function Table.update(self, data)
1060 NamedSection - A fixed configuration section defined by its name
1062 NamedSection = class(AbstractSection)
1064 function NamedSection.__init__(self, map, section, stype, ...)
1065 AbstractSection.__init__(self, map, stype, ...)
1068 self.addremove = false
1069 self.template = "cbi/nsection"
1070 self.section = section
1073 function NamedSection.parse(self, novld)
1074 local s = self.section
1075 local active = self:cfgvalue(s)
1077 if self.addremove then
1078 local path = self.config.."."..s
1079 if active then -- Remove the section
1080 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1084 else -- Create and apply default values
1085 if self.map:formvalue("cbi.cns."..path) then
1093 AbstractSection.parse_dynamic(self, s)
1094 if self.map:submitstate() then
1097 AbstractSection.parse_optionals(self, s)
1099 if self.changed then
1107 TypedSection - A (set of) configuration section(s) defined by the type
1108 addremove: Defines whether the user can add/remove sections of this type
1109 anonymous: Allow creating anonymous sections
1110 validate: a validation function returning nil if the section is invalid
1112 TypedSection = class(AbstractSection)
1114 function TypedSection.__init__(self, map, type, ...)
1115 AbstractSection.__init__(self, map, type, ...)
1117 self.template = "cbi/tsection"
1119 self.anonymous = false
1122 -- Return all matching UCI sections for this TypedSection
1123 function TypedSection.cfgsections(self)
1125 self.map.uci:foreach(self.map.config, self.sectiontype,
1127 if self:checkscope(section[".name"]) then
1128 table.insert(sections, section[".name"])
1135 -- Limits scope to sections that have certain option => value pairs
1136 function TypedSection.depends(self, option, value)
1137 table.insert(self.deps, {option=option, value=value})
1140 function TypedSection.parse(self, novld)
1141 if self.addremove then
1143 local crval = REMOVE_PREFIX .. self.config
1144 local name = self.map:formvaluetable(crval)
1145 for k,v in pairs(name) do
1146 if k:sub(-2) == ".x" then
1147 k = k:sub(1, #k - 2)
1149 if self:cfgvalue(k) and self:checkscope(k) then
1156 for i, k in ipairs(self:cfgsections()) do
1157 AbstractSection.parse_dynamic(self, k)
1158 if self.map:submitstate() then
1159 Node.parse(self, k, novld)
1161 AbstractSection.parse_optionals(self, k)
1164 if self.addremove then
1167 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1168 local origin, name = next(self.map:formvaluetable(crval))
1169 if self.anonymous then
1171 created = self:create(nil, origin)
1175 -- Ignore if it already exists
1176 if self:cfgvalue(name) then
1180 name = self:checkscope(name)
1183 self.err_invalid = true
1186 if name and #name > 0 then
1187 created = self:create(name, origin) and name
1189 self.invalid_cts = true
1196 AbstractSection.parse_optionals(self, created)
1200 if self.sortable then
1201 local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype
1202 local order = self.map:formvalue(stval)
1203 if order and #order > 0 then
1206 for sid in util.imatch(order) do
1207 self.map.uci:reorder(self.config, sid, num)
1210 self.changed = (num > 0)
1214 if created or self.changed then
1219 -- Verifies scope of sections
1220 function TypedSection.checkscope(self, section)
1221 -- Check if we are not excluded
1222 if self.filter and not self:filter(section) then
1226 -- Check if at least one dependency is met
1227 if #self.deps > 0 and self:cfgvalue(section) then
1230 for k, v in ipairs(self.deps) do
1231 if self:cfgvalue(section)[v.option] == v.value then
1241 return self:validate(section)
1245 -- Dummy validate function
1246 function TypedSection.validate(self, section)
1252 AbstractValue - An abstract Value Type
1253 null: Value can be empty
1254 valid: A function returning the value if it is valid otherwise nil
1255 depends: A table of option => value pairs of which one must be true
1256 default: The default value
1257 size: The size of the input fields
1258 rmempty: Unset value if empty
1259 optional: This value is optional (see AbstractSection.optionals)
1261 AbstractValue = class(Node)
1263 function AbstractValue.__init__(self, map, section, option, ...)
1264 Node.__init__(self, ...)
1265 self.section = section
1266 self.option = option
1268 self.config = map.config
1269 self.tag_invalid = {}
1270 self.tag_missing = {}
1271 self.tag_reqerror = {}
1275 --self.cast = "string"
1277 self.track_missing = false
1281 self.optional = false
1284 function AbstractValue.prepare(self)
1285 self.cast = self.cast or "string"
1288 -- Add a dependencie to another section field
1289 function AbstractValue.depends(self, field, value)
1291 if type(field) == "string" then
1298 table.insert(self.deps, {deps=deps, add=""})
1301 -- Generates the unique CBID
1302 function AbstractValue.cbid(self, section)
1303 return "cbid."..self.map.config.."."..section.."."..self.option
1306 -- Return whether this object should be created
1307 function AbstractValue.formcreated(self, section)
1308 local key = "cbi.opt."..self.config.."."..section
1309 return (self.map:formvalue(key) == self.option)
1312 -- Returns the formvalue for this object
1313 function AbstractValue.formvalue(self, section)
1314 return self.map:formvalue(self:cbid(section))
1317 function AbstractValue.additional(self, value)
1318 self.optional = value
1321 function AbstractValue.mandatory(self, value)
1322 self.rmempty = not value
1325 function AbstractValue.add_error(self, section, type, msg)
1326 self.error = self.error or { }
1327 self.error[section] = msg or type
1329 self.section.error = self.section.error or { }
1330 self.section.error[section] = self.section.error[section] or { }
1331 table.insert(self.section.error[section], msg or type)
1333 if type == "invalid" then
1334 self.tag_invalid[section] = true
1335 elseif type == "missing" then
1336 self.tag_missing[section] = true
1339 self.tag_error[section] = true
1340 self.map.save = false
1343 function AbstractValue.parse(self, section, novld)
1344 local fvalue = self:formvalue(section)
1345 local cvalue = self:cfgvalue(section)
1347 -- If favlue and cvalue are both tables and have the same content
1348 -- make them identical
1349 if type(fvalue) == "table" and type(cvalue) == "table" then
1350 local equal = #fvalue == #cvalue
1353 if cvalue[i] ~= fvalue[i] then
1363 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1365 fvalue, val_err = self:validate(fvalue, section)
1366 fvalue = self:transform(fvalue)
1368 if not fvalue and not novld then
1369 self:add_error(section, "invalid", val_err)
1372 if fvalue and (self.forcewrite or not (fvalue == cvalue)) then
1373 if self:write(section, fvalue) then
1375 self.section.changed = true
1376 --luci.util.append(self.map.events, self.events)
1379 else -- Unset the UCI or error
1380 if self.rmempty or self.optional then
1381 if self:remove(section) then
1383 self.section.changed = true
1384 --luci.util.append(self.map.events, self.events)
1386 elseif cvalue ~= fvalue and not novld then
1387 -- trigger validator with nil value to get custom user error msg.
1388 local _, val_err = self:validate(nil, section)
1389 self:add_error(section, "missing", val_err)
1394 -- Render if this value exists or if it is mandatory
1395 function AbstractValue.render(self, s, scope)
1396 if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then
1399 scope.cbid = self:cbid(s)
1400 Node.render(self, scope)
1404 -- Return the UCI value of this object
1405 function AbstractValue.cfgvalue(self, section)
1407 if self.tag_error[section] then
1408 value = self:formvalue(section)
1410 value = self.map:get(section, self.option)
1415 elseif not self.cast or self.cast == type(value) then
1417 elseif self.cast == "string" then
1418 if type(value) == "table" then
1421 elseif self.cast == "table" then
1426 -- Validate the form value
1427 function AbstractValue.validate(self, value)
1428 if self.datatype and value then
1429 if type(value) == "table" then
1431 for _, v in ipairs(value) do
1432 if v and #v > 0 and not verify_datatype(self.datatype, v) then
1437 if not verify_datatype(self.datatype, value) then
1446 AbstractValue.transform = AbstractValue.validate
1450 function AbstractValue.write(self, section, value)
1451 return self.map:set(section, self.option, value)
1455 function AbstractValue.remove(self, section)
1456 return self.map:del(section, self.option)
1463 Value - A one-line value
1464 maxlength: The maximum length
1466 Value = class(AbstractValue)
1468 function Value.__init__(self, ...)
1469 AbstractValue.__init__(self, ...)
1470 self.template = "cbi/value"
1475 function Value.reset_values(self)
1480 function Value.value(self, key, val)
1482 table.insert(self.keylist, tostring(key))
1483 table.insert(self.vallist, tostring(val))
1487 -- DummyValue - This does nothing except being there
1488 DummyValue = class(AbstractValue)
1490 function DummyValue.__init__(self, ...)
1491 AbstractValue.__init__(self, ...)
1492 self.template = "cbi/dvalue"
1496 function DummyValue.cfgvalue(self, section)
1499 if type(self.value) == "function" then
1500 value = self:value(section)
1505 value = AbstractValue.cfgvalue(self, section)
1510 function DummyValue.parse(self)
1516 Flag - A flag being enabled or disabled
1518 Flag = class(AbstractValue)
1520 function Flag.__init__(self, ...)
1521 AbstractValue.__init__(self, ...)
1522 self.template = "cbi/fvalue"
1526 self.default = self.disabled
1529 -- A flag can only have two states: set or unset
1530 function Flag.parse(self, section)
1531 local fexists = self.map:formvalue(
1532 FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
1535 local fvalue = self:formvalue(section) and self.enabled or self.disabled
1536 if fvalue ~= self.default or (not self.optional and not self.rmempty) then
1537 self:write(section, fvalue)
1539 self:remove(section)
1542 self:remove(section)
1546 function Flag.cfgvalue(self, section)
1547 return AbstractValue.cfgvalue(self, section) or self.default
1552 ListValue - A one-line value predefined in a list
1553 widget: The widget that will be used (select, radio)
1555 ListValue = class(AbstractValue)
1557 function ListValue.__init__(self, ...)
1558 AbstractValue.__init__(self, ...)
1559 self.template = "cbi/lvalue"
1564 self.widget = "select"
1567 function ListValue.reset_values(self)
1572 function ListValue.value(self, key, val, ...)
1573 if luci.util.contains(self.keylist, key) then
1578 table.insert(self.keylist, tostring(key))
1579 table.insert(self.vallist, tostring(val))
1581 for i, deps in ipairs({...}) do
1582 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1586 function ListValue.validate(self, val)
1587 if luci.util.contains(self.keylist, val) then
1597 MultiValue - Multiple delimited values
1598 widget: The widget that will be used (select, checkbox)
1599 delimiter: The delimiter that will separate the values (default: " ")
1601 MultiValue = class(AbstractValue)
1603 function MultiValue.__init__(self, ...)
1604 AbstractValue.__init__(self, ...)
1605 self.template = "cbi/mvalue"
1610 self.widget = "checkbox"
1611 self.delimiter = " "
1614 function MultiValue.render(self, ...)
1615 if self.widget == "select" and not self.size then
1616 self.size = #self.vallist
1619 AbstractValue.render(self, ...)
1622 function MultiValue.reset_values(self)
1627 function MultiValue.value(self, key, val)
1628 if luci.util.contains(self.keylist, key) then
1633 table.insert(self.keylist, tostring(key))
1634 table.insert(self.vallist, tostring(val))
1637 function MultiValue.valuelist(self, section)
1638 local val = self:cfgvalue(section)
1640 if not(type(val) == "string") then
1644 return luci.util.split(val, self.delimiter)
1647 function MultiValue.validate(self, val)
1648 val = (type(val) == "table") and val or {val}
1652 for i, value in ipairs(val) do
1653 if luci.util.contains(self.keylist, value) then
1654 result = result and (result .. self.delimiter .. value) or value
1662 StaticList = class(MultiValue)
1664 function StaticList.__init__(self, ...)
1665 MultiValue.__init__(self, ...)
1667 self.valuelist = self.cfgvalue
1669 if not self.override_scheme
1670 and self.map:get_scheme(self.section.sectiontype, self.option) then
1671 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1672 if self.value and vs.values and not self.override_values then
1673 for k, v in pairs(vs.values) do
1680 function StaticList.validate(self, value)
1681 value = (type(value) == "table") and value or {value}
1684 for i, v in ipairs(value) do
1685 if luci.util.contains(self.keylist, v) then
1686 table.insert(valid, v)
1693 DynamicList = class(AbstractValue)
1695 function DynamicList.__init__(self, ...)
1696 AbstractValue.__init__(self, ...)
1697 self.template = "cbi/dynlist"
1703 function DynamicList.reset_values(self)
1708 function DynamicList.value(self, key, val)
1710 table.insert(self.keylist, tostring(key))
1711 table.insert(self.vallist, tostring(val))
1714 function DynamicList.write(self, section, value)
1717 if type(value) == "table" then
1719 for _, x in ipairs(value) do
1720 if x and #x > 0 then
1728 if self.cast == "string" then
1729 value = table.concat(t, " ")
1734 return AbstractValue.write(self, section, value)
1737 function DynamicList.cfgvalue(self, section)
1738 local value = AbstractValue.cfgvalue(self, section)
1740 if type(value) == "string" then
1743 for x in value:gmatch("%S+") do
1754 function DynamicList.formvalue(self, section)
1755 local value = AbstractValue.formvalue(self, section)
1757 if type(value) == "string" then
1758 if self.cast == "string" then
1761 for x in value:gmatch("%S+") do
1775 TextValue - A multi-line value
1778 TextValue = class(AbstractValue)
1780 function TextValue.__init__(self, ...)
1781 AbstractValue.__init__(self, ...)
1782 self.template = "cbi/tvalue"
1788 Button = class(AbstractValue)
1790 function Button.__init__(self, ...)
1791 AbstractValue.__init__(self, ...)
1792 self.template = "cbi/button"
1793 self.inputstyle = nil
1798 FileUpload = class(AbstractValue)
1800 function FileUpload.__init__(self, ...)
1801 AbstractValue.__init__(self, ...)
1802 self.template = "cbi/upload"
1803 if not self.map.upload_fields then
1804 self.map.upload_fields = { self }
1806 self.map.upload_fields[#self.map.upload_fields+1] = self
1810 function FileUpload.formcreated(self, section)
1811 return AbstractValue.formcreated(self, section) or
1812 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1813 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1816 function FileUpload.cfgvalue(self, section)
1817 local val = AbstractValue.cfgvalue(self, section)
1818 if val and fs.access(val) then
1824 function FileUpload.formvalue(self, section)
1825 local val = AbstractValue.formvalue(self, section)
1827 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1828 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1838 function FileUpload.remove(self, section)
1839 local val = AbstractValue.formvalue(self, section)
1840 if val and fs.access(val) then fs.unlink(val) end
1841 return AbstractValue.remove(self, section)
1845 FileBrowser = class(AbstractValue)
1847 function FileBrowser.__init__(self, ...)
1848 AbstractValue.__init__(self, ...)
1849 self.template = "cbi/browser"