2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("base")
80 translate=i18n.translate,
81 translatef=i18n.translatef,
85 setfenv(func, setmetatable(env, {__index =
87 return rawget(tbl, key) or _M[key] or _G[key]
90 local maps = { func() }
92 local has_upload = false
94 for i, map in ipairs(maps) do
95 if not instanceof(map, Node) then
96 error("CBI map returns no valid map object!")
100 if map.upload_fields then
102 for _, field in ipairs(map.upload_fields) do
104 field.config .. '.' ..
105 field.section.sectiontype .. '.' ..
114 local uci = luci.model.uci.cursor()
115 local prm = luci.http.context.request.message.params
118 luci.http.setfilehandler(
119 function( field, chunk, eof )
120 if not field then return end
121 if field.name and not cbid then
122 local c, s, o = field.name:gmatch(
123 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
126 if c and s and o then
127 local t = uci:get( c, s )
128 if t and uploads[c.."."..t.."."..o] then
129 local path = upldir .. field.name
130 fd = io.open(path, "w")
139 if field.name == cbid and fd then
155 local function _uvl_validate_section(node, name)
156 local co = node.map:get()
158 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
159 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161 local function tag_fields(e)
162 if e.option and node.fields[e.option] then
163 if node.fields[e.option].error then
164 node.fields[e.option].error[name] = e
166 node.fields[e.option].error = { [name] = e }
169 for _, c in ipairs(e.childs) do tag_fields(c) end
173 local function tag_section(e)
175 for _, c in ipairs(e.childs or { e }) do
176 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
177 table.insert( s, c.childs[1]:string() )
179 table.insert( s, c:string() )
186 node.error = { [name] = s }
191 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 node.map.save = false
200 local function _uvl_strip_remote_dependencies(deps)
203 for k, v in pairs(deps) do
204 k = k:gsub("%$config%.%$section%.", "")
205 if k:match("^[%w_]+$") and type(v) == "string" then
214 -- Node pseudo abstract class
217 function Node.__init__(self, title, description)
219 self.title = title or ""
220 self.description = description or ""
221 self.template = "cbi/node"
225 function Node._run_hook(self, hook)
226 if type(self[hook]) == "function" then
227 return self[hook](self)
231 function Node._run_hooks(self, ...)
234 for _, f in ipairs(arg) do
235 if type(self[f]) == "function" then
244 function Node.prepare(self, ...)
245 for k, child in ipairs(self.children) do
250 -- Append child nodes
251 function Node.append(self, obj)
252 table.insert(self.children, obj)
255 -- Parse this node and its children
256 function Node.parse(self, ...)
257 for k, child in ipairs(self.children) do
263 function Node.render(self, scope)
267 luci.template.render(self.template, scope)
270 -- Render the children
271 function Node.render_children(self, ...)
272 for k, node in ipairs(self.children) do
279 A simple template element
281 Template = class(Node)
283 function Template.__init__(self, template)
285 self.template = template
288 function Template.render(self)
289 luci.template.render(self.template, {self=self})
292 function Template.parse(self, readinput)
293 self.readinput = (readinput ~= false)
294 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
299 Map - A map describing a configuration file
303 function Map.__init__(self, config, ...)
304 Node.__init__(self, ...)
307 self.parsechain = {self.config}
308 self.template = "cbi/map"
309 self.apply_on_parse = nil
310 self.readinput = true
314 self.uci = uci.cursor()
319 if not self.uci:load(self.config) then
320 error("Unable to read UCI data: " .. self.config)
323 self.validator = luci.uvl.UVL()
324 self.scheme = self.validator:get_scheme(self.config)
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
335 function Map.get_scheme(self, sectiontype, option)
337 return self.scheme and self.scheme.sections[sectiontype]
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
353 function Map.state_handler(self, state)
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
360 self:_run_hooks("on_parse")
362 if self:formvalue("cbi.skip") then
363 self.state = FORM_SKIP
364 return self:state_handler(self.state)
367 Node.parse(self, ...)
370 for i, config in ipairs(self.parsechain) do
371 self.uci:save(config)
373 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
374 self:_run_hooks("on_before_commit")
375 for i, config in ipairs(self.parsechain) do
376 self.uci:commit(config)
378 -- Refresh data because commit changes section names
379 self.uci:load(config)
381 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
382 if self.apply_on_parse then
383 self.uci:apply(self.parsechain)
384 self:_run_hooks("on_apply", "on_after_apply")
386 self._apply = function()
387 local cmd = self.uci:apply(self.parsechain, true)
393 Node.parse(self, true)
396 for i, config in ipairs(self.parsechain) do
397 self.uci:unload(config)
399 if type(self.commit_handler) == "function" then
400 self:commit_handler(self:submitstate())
404 if self:submitstate() then
405 if not self.save then
406 self.state = FORM_INVALID
407 elseif self.proceed then
408 self.state = FORM_PROCEED
410 self.state = self.changed and FORM_CHANGED or FORM_VALID
413 self.state = FORM_NODATA
416 return self:state_handler(self.state)
419 function Map.render(self, ...)
420 self:_run_hooks("on_init")
421 Node.render(self, ...)
423 local fp = self._apply()
426 self:_run_hooks("on_apply")
430 -- Creates a child section
431 function Map.section(self, class, ...)
432 if instanceof(class, AbstractSection) then
433 local obj = class(self, ...)
437 error("class must be a descendent of AbstractSection")
442 function Map.add(self, sectiontype)
443 return self.uci:add(self.config, sectiontype)
447 function Map.set(self, section, option, value)
449 return self.uci:set(self.config, section, option, value)
451 return self.uci:set(self.config, section, value)
456 function Map.del(self, section, option)
458 return self.uci:delete(self.config, section, option)
460 return self.uci:delete(self.config, section)
465 function Map.get(self, section, option)
467 return self.uci:get_all(self.config)
469 return self.uci:get(self.config, section, option)
471 return self.uci:get_all(self.config, section)
478 Compound = class(Node)
480 function Compound.__init__(self, ...)
482 self.template = "cbi/compound"
483 self.children = {...}
486 function Compound.populate_delegator(self, delegator)
487 for _, v in ipairs(self.children) do
488 v.delegator = delegator
492 function Compound.parse(self, ...)
493 local cstate, state = 0
495 for k, child in ipairs(self.children) do
496 cstate = child:parse(...)
497 state = (not state or cstate < state) and cstate or state
505 Delegator - Node controller
507 Delegator = class(Node)
508 function Delegator.__init__(self, ...)
509 Node.__init__(self, ...)
511 self.defaultpath = {}
512 self.pageaction = false
513 self.readinput = true
514 self.allow_reset = false
515 self.allow_cancel = false
516 self.allow_back = false
517 self.allow_finish = false
518 self.template = "cbi/delegator"
521 function Delegator.set(self, name, node)
522 assert(not self.nodes[name], "Duplicate entry")
524 self.nodes[name] = node
527 function Delegator.add(self, name, node)
528 node = self:set(name, node)
529 self.defaultpath[#self.defaultpath+1] = name
532 function Delegator.insert_after(self, name, after)
533 local n = #self.chain + 1
534 for k, v in ipairs(self.chain) do
540 table.insert(self.chain, n, name)
543 function Delegator.set_route(self, ...)
544 local n, chain, route = 0, self.chain, {...}
546 if chain[i] == self.current then
555 for i = n + 1, #chain do
560 function Delegator.get(self, name)
561 local node = self.nodes[name]
563 if type(node) == "string" then
567 if type(node) == "table" and getmetatable(node) == nil then
568 node = Compound(unpack(node))
574 function Delegator.parse(self, ...)
575 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
576 if self:_run_hooks("on_cancel") then
581 if not Map.formvalue(self, "cbi.delg.current") then
582 self:_run_hooks("on_init")
586 self.chain = self.chain or self:get_chain()
587 self.current = self.current or self:get_active()
588 self.active = self.active or self:get(self.current)
589 assert(self.active, "Invalid state")
591 local stat = FORM_DONE
592 if type(self.active) ~= "function" then
593 self.active:populate_delegator(self)
594 stat = self.active:parse()
599 if stat > FORM_PROCEED then
600 if Map.formvalue(self, "cbi.delg.back") then
601 newcurrent = self:get_prev(self.current)
603 newcurrent = self:get_next(self.current)
605 elseif stat < FORM_PROCEED then
610 if not Map.formvalue(self, "cbi.submit") then
612 elseif stat > FORM_PROCEED
613 and (not newcurrent or not self:get(newcurrent)) then
614 return self:_run_hook("on_done") or FORM_DONE
616 self.current = newcurrent or self.current
617 self.active = self:get(self.current)
618 if type(self.active) ~= "function" then
619 self.active:populate_delegator(self)
620 self.active:parse(false)
623 return self:parse(...)
628 function Delegator.get_next(self, state)
629 for k, v in ipairs(self.chain) do
631 return self.chain[k+1]
636 function Delegator.get_prev(self, state)
637 for k, v in ipairs(self.chain) do
639 return self.chain[k-1]
644 function Delegator.get_chain(self)
645 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
646 return type(x) == "table" and x or {x}
649 function Delegator.get_active(self)
650 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
658 Page.__init__ = Node.__init__
659 Page.parse = function() end
663 SimpleForm - A Simple non-UCI form
665 SimpleForm = class(Node)
667 function SimpleForm.__init__(self, config, title, description, data)
668 Node.__init__(self, title, description)
670 self.data = data or {}
671 self.template = "cbi/simpleform"
673 self.pageaction = false
674 self.readinput = true
677 SimpleForm.formvalue = Map.formvalue
678 SimpleForm.formvaluetable = Map.formvaluetable
680 function SimpleForm.parse(self, readinput, ...)
681 self.readinput = (readinput ~= false)
683 if self:formvalue("cbi.skip") then
687 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
691 if self:submitstate() then
692 Node.parse(self, 1, ...)
696 for k, j in ipairs(self.children) do
697 for i, v in ipairs(j.children) do
699 and (not v.tag_missing or not v.tag_missing[1])
700 and (not v.tag_invalid or not v.tag_invalid[1])
706 not self:submitstate() and FORM_NODATA
707 or valid and FORM_VALID
710 self.dorender = not self.handle
712 local nrender, nstate = self:handle(state, self.data)
713 self.dorender = self.dorender or (nrender ~= false)
714 state = nstate or state
719 function SimpleForm.render(self, ...)
720 if self.dorender then
721 Node.render(self, ...)
725 function SimpleForm.submitstate(self)
726 return self:formvalue("cbi.submit")
729 function SimpleForm.section(self, class, ...)
730 if instanceof(class, AbstractSection) then
731 local obj = class(self, ...)
735 error("class must be a descendent of AbstractSection")
739 -- Creates a child field
740 function SimpleForm.field(self, class, ...)
742 for k, v in ipairs(self.children) do
743 if instanceof(v, SimpleSection) then
749 section = self:section(SimpleSection)
752 if instanceof(class, AbstractValue) then
753 local obj = class(self, section, ...)
754 obj.track_missing = true
758 error("class must be a descendent of AbstractValue")
762 function SimpleForm.set(self, section, option, value)
763 self.data[option] = value
767 function SimpleForm.del(self, section, option)
768 self.data[option] = nil
772 function SimpleForm.get(self, section, option)
773 return self.data[option]
777 function SimpleForm.get_scheme()
782 Form = class(SimpleForm)
784 function Form.__init__(self, ...)
785 SimpleForm.__init__(self, ...)
793 AbstractSection = class(Node)
795 function AbstractSection.__init__(self, map, sectiontype, ...)
796 Node.__init__(self, ...)
797 self.sectiontype = sectiontype
799 self.config = map.config
804 self.tag_invalid = {}
805 self.tag_deperror = {}
809 self.addremove = false
813 -- Define a tab for the section
814 function AbstractSection.tab(self, tab, title, desc)
815 self.tabs = self.tabs or { }
816 self.tab_names = self.tab_names or { }
818 self.tab_names[#self.tab_names+1] = tab
826 -- Appends a new option
827 function AbstractSection.option(self, class, option, ...)
828 -- Autodetect from UVL
829 if class == true and self.map:get_scheme(self.sectiontype, option) then
830 local vs = self.map:get_scheme(self.sectiontype, option)
831 if vs.type == "boolean" then
833 elseif vs.type == "list" then
835 elseif vs.type == "enum" or vs.type == "reference" then
842 if instanceof(class, AbstractValue) then
843 local obj = class(self.map, self, option, ...)
845 self.fields[option] = obj
847 elseif class == true then
848 error("No valid class was given and autodetection failed.")
850 error("class must be a descendant of AbstractValue")
854 -- Appends a new tabbed option
855 function AbstractSection.taboption(self, tab, ...)
857 assert(tab and self.tabs and self.tabs[tab],
858 "Cannot assign option to not existing tab %q" % tostring(tab))
860 local l = self.tabs[tab].childs
861 local o = AbstractSection.option(self, ...)
863 if o then l[#l+1] = o end
868 -- Render a single tab
869 function AbstractSection.render_tab(self, tab, ...)
871 assert(tab and self.tabs and self.tabs[tab],
872 "Cannot render not existing tab %q" % tostring(tab))
874 for _, node in ipairs(self.tabs[tab].childs) do
879 -- Parse optional options
880 function AbstractSection.parse_optionals(self, section)
881 if not self.optional then
885 self.optionals[section] = {}
887 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
888 for k,v in ipairs(self.children) do
889 if v.optional and not v:cfgvalue(section) then
890 if field == v.option then
892 self.map.proceed = true
894 table.insert(self.optionals[section], v)
899 if field and #field > 0 and self.dynamic then
900 self:add_dynamic(field)
904 -- Add a dynamic option
905 function AbstractSection.add_dynamic(self, field, optional)
906 local o = self:option(Value, field, field)
907 o.optional = optional
910 -- Parse all dynamic options
911 function AbstractSection.parse_dynamic(self, section)
912 if not self.dynamic then
916 local arr = luci.util.clone(self:cfgvalue(section))
917 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
918 for k, v in pairs(form) do
922 for key,val in pairs(arr) do
925 for i,c in ipairs(self.children) do
926 if c.option == key then
931 if create and key:sub(1, 1) ~= "." then
932 self.map.proceed = true
933 self:add_dynamic(key, true)
938 -- Returns the section's UCI table
939 function AbstractSection.cfgvalue(self, section)
940 return self.map:get(section)
944 function AbstractSection.push_events(self)
945 --luci.util.append(self.map.events, self.events)
946 self.map.changed = true
949 -- Removes the section
950 function AbstractSection.remove(self, section)
951 self.map.proceed = true
952 return self.map:del(section)
955 -- Creates the section
956 function AbstractSection.create(self, section)
960 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
962 section = self.map:add(self.sectiontype)
967 for k,v in pairs(self.children) do
969 self.map:set(section, v.option, v.default)
973 for k,v in pairs(self.defaults) do
974 self.map:set(section, k, v)
978 self.map.proceed = true
984 SimpleSection = class(AbstractSection)
986 function SimpleSection.__init__(self, form, ...)
987 AbstractSection.__init__(self, form, nil, ...)
988 self.template = "cbi/nullsection"
992 Table = class(AbstractSection)
994 function Table.__init__(self, form, data, ...)
995 local datasource = {}
997 datasource.config = "table"
998 self.data = data or {}
1000 datasource.formvalue = Map.formvalue
1001 datasource.formvaluetable = Map.formvaluetable
1002 datasource.readinput = true
1004 function datasource.get(self, section, option)
1005 return tself.data[section] and tself.data[section][option]
1008 function datasource.submitstate(self)
1009 return Map.formvalue(self, "cbi.submit")
1012 function datasource.del(...)
1016 function datasource.get_scheme()
1020 AbstractSection.__init__(self, datasource, "table", ...)
1021 self.template = "cbi/tblsection"
1022 self.rowcolors = true
1023 self.anonymous = true
1026 function Table.parse(self, readinput)
1027 self.map.readinput = (readinput ~= false)
1028 for i, k in ipairs(self:cfgsections()) do
1029 if self.map:submitstate() then
1035 function Table.cfgsections(self)
1038 for i, v in luci.util.kspairs(self.data) do
1039 table.insert(sections, i)
1045 function Table.update(self, data)
1052 NamedSection - A fixed configuration section defined by its name
1054 NamedSection = class(AbstractSection)
1056 function NamedSection.__init__(self, map, section, stype, ...)
1057 AbstractSection.__init__(self, map, stype, ...)
1060 self.addremove = false
1062 -- Use defaults from UVL
1063 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1064 local vs = self.map:get_scheme(self.sectiontype)
1065 self.addremove = not vs.unique and not vs.required
1066 self.dynamic = vs.dynamic
1067 self.title = self.title or vs.title
1068 self.description = self.description or vs.descr
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 if not novld and not self.override_scheme and self.map.scheme then
1100 _uvl_validate_section(self, s)
1103 AbstractSection.parse_optionals(self, s)
1105 if self.changed then
1113 TypedSection - A (set of) configuration section(s) defined by the type
1114 addremove: Defines whether the user can add/remove sections of this type
1115 anonymous: Allow creating anonymous sections
1116 validate: a validation function returning nil if the section is invalid
1118 TypedSection = class(AbstractSection)
1120 function TypedSection.__init__(self, map, type, ...)
1121 AbstractSection.__init__(self, map, type, ...)
1123 self.template = "cbi/tsection"
1125 self.anonymous = false
1127 -- Use defaults from UVL
1128 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1129 local vs = self.map:get_scheme(self.sectiontype)
1130 self.addremove = not vs.unique and not vs.required
1131 self.dynamic = vs.dynamic
1132 self.anonymous = not vs.named
1133 self.title = self.title or vs.title
1134 self.description = self.description or vs.descr
1138 -- Return all matching UCI sections for this TypedSection
1139 function TypedSection.cfgsections(self)
1141 self.map.uci:foreach(self.map.config, self.sectiontype,
1143 if self:checkscope(section[".name"]) then
1144 table.insert(sections, section[".name"])
1151 -- Limits scope to sections that have certain option => value pairs
1152 function TypedSection.depends(self, option, value)
1153 table.insert(self.deps, {option=option, value=value})
1156 function TypedSection.parse(self, novld)
1157 if self.addremove then
1159 local crval = REMOVE_PREFIX .. self.config
1160 local name = self.map:formvaluetable(crval)
1161 for k,v in pairs(name) do
1162 if k:sub(-2) == ".x" then
1163 k = k:sub(1, #k - 2)
1165 if self:cfgvalue(k) and self:checkscope(k) then
1172 for i, k in ipairs(self:cfgsections()) do
1173 AbstractSection.parse_dynamic(self, k)
1174 if self.map:submitstate() then
1175 Node.parse(self, k, novld)
1177 if not novld and not self.override_scheme and self.map.scheme then
1178 _uvl_validate_section(self, k)
1181 AbstractSection.parse_optionals(self, k)
1184 if self.addremove then
1187 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1188 local name = self.map:formvalue(crval)
1189 if self.anonymous then
1191 created = self:create()
1195 -- Ignore if it already exists
1196 if self:cfgvalue(name) then
1200 name = self:checkscope(name)
1203 self.err_invalid = true
1206 if name and #name > 0 then
1207 created = self:create(name) and name
1209 self.invalid_cts = true
1216 AbstractSection.parse_optionals(self, created)
1220 if created or self.changed then
1225 -- Verifies scope of sections
1226 function TypedSection.checkscope(self, section)
1227 -- Check if we are not excluded
1228 if self.filter and not self:filter(section) then
1232 -- Check if at least one dependency is met
1233 if #self.deps > 0 and self:cfgvalue(section) then
1236 for k, v in ipairs(self.deps) do
1237 if self:cfgvalue(section)[v.option] == v.value then
1247 return self:validate(section)
1251 -- Dummy validate function
1252 function TypedSection.validate(self, section)
1258 AbstractValue - An abstract Value Type
1259 null: Value can be empty
1260 valid: A function returning the value if it is valid otherwise nil
1261 depends: A table of option => value pairs of which one must be true
1262 default: The default value
1263 size: The size of the input fields
1264 rmempty: Unset value if empty
1265 optional: This value is optional (see AbstractSection.optionals)
1267 AbstractValue = class(Node)
1269 function AbstractValue.__init__(self, map, section, option, ...)
1270 Node.__init__(self, ...)
1271 self.section = section
1272 self.option = option
1274 self.config = map.config
1275 self.tag_invalid = {}
1276 self.tag_missing = {}
1277 self.tag_reqerror = {}
1281 --self.cast = "string"
1283 self.track_missing = false
1287 self.optional = false
1290 function AbstractValue.prepare(self)
1291 -- Use defaults from UVL
1292 if not self.override_scheme
1293 and self.map:get_scheme(self.section.sectiontype, self.option) then
1294 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1295 if self.cast == nil then
1296 self.cast = (vs.type == "list") and "list" or "string"
1298 self.title = self.title or vs.title
1299 self.description = self.description or vs.descr
1300 if self.default == nil then
1301 self.default = vs.default
1304 if vs.depends and not self.override_dependencies then
1305 for i, deps in ipairs(vs.depends) do
1306 deps = _uvl_strip_remote_dependencies(deps)
1314 self.cast = self.cast or "string"
1317 -- Add a dependencie to another section field
1318 function AbstractValue.depends(self, field, value)
1320 if type(field) == "string" then
1327 table.insert(self.deps, {deps=deps, add=""})
1330 -- Generates the unique CBID
1331 function AbstractValue.cbid(self, section)
1332 return "cbid."..self.map.config.."."..section.."."..self.option
1335 -- Return whether this object should be created
1336 function AbstractValue.formcreated(self, section)
1337 local key = "cbi.opt."..self.config.."."..section
1338 return (self.map:formvalue(key) == self.option)
1341 -- Returns the formvalue for this object
1342 function AbstractValue.formvalue(self, section)
1343 return self.map:formvalue(self:cbid(section))
1346 function AbstractValue.additional(self, value)
1347 self.optional = value
1350 function AbstractValue.mandatory(self, value)
1351 self.rmempty = not value
1354 function AbstractValue.parse(self, section, novld)
1355 local fvalue = self:formvalue(section)
1356 local cvalue = self:cfgvalue(section)
1358 -- If favlue and cvalue are both tables and have the same content
1359 -- make them identical
1360 if type(fvalue) == "table" and type(cvalue) == "table" then
1361 local equal = #fvalue == #cvalue
1364 if cvalue[i] ~= fvalue[i] then
1374 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1375 fvalue = self:transform(self:validate(fvalue, section))
1376 if not fvalue and not novld then
1378 self.error[section] = "invalid"
1380 self.error = { [section] = "invalid" }
1382 if self.section.error then
1383 table.insert(self.section.error[section], "invalid")
1385 self.section.error = {[section] = {"invalid"}}
1387 self.map.save = false
1389 if fvalue and not (fvalue == cvalue) then
1390 if self:write(section, fvalue) then
1392 self.section.changed = true
1393 --luci.util.append(self.map.events, self.events)
1396 else -- Unset the UCI or error
1397 if self.rmempty or self.optional then
1398 if self:remove(section) then
1400 self.section.changed = true
1401 --luci.util.append(self.map.events, self.events)
1403 elseif cvalue ~= fvalue and not novld then
1404 self:write(section, fvalue or "")
1406 self.error[section] = "missing"
1408 self.error = { [section] = "missing" }
1410 self.map.save = false
1415 -- Render if this value exists or if it is mandatory
1416 function AbstractValue.render(self, s, scope)
1417 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1420 scope.cbid = self:cbid(s)
1421 scope.striptags = luci.util.striptags
1422 scope.pcdata = luci.util.pcdata
1424 scope.ifattr = function(cond,key,val)
1426 return string.format(
1427 ' %s="%s"', tostring(key),
1428 luci.util.pcdata(tostring( val
1430 or (type(self[key]) ~= "function" and self[key])
1438 scope.attr = function(...)
1439 return scope.ifattr( true, ... )
1442 Node.render(self, scope)
1446 -- Return the UCI value of this object
1447 function AbstractValue.cfgvalue(self, section)
1448 local value = self.map:get(section, self.option)
1451 elseif not self.cast or self.cast == type(value) then
1453 elseif self.cast == "string" then
1454 if type(value) == "table" then
1457 elseif self.cast == "table" then
1458 return luci.util.split(value, "%s+", nil, true)
1462 -- Validate the form value
1463 function AbstractValue.validate(self, value)
1467 AbstractValue.transform = AbstractValue.validate
1471 function AbstractValue.write(self, section, value)
1472 return self.map:set(section, self.option, value)
1476 function AbstractValue.remove(self, section)
1477 return self.map:del(section, self.option)
1484 Value - A one-line value
1485 maxlength: The maximum length
1487 Value = class(AbstractValue)
1489 function Value.__init__(self, ...)
1490 AbstractValue.__init__(self, ...)
1491 self.template = "cbi/value"
1496 function Value.value(self, key, val)
1498 table.insert(self.keylist, tostring(key))
1499 table.insert(self.vallist, tostring(val))
1503 -- DummyValue - This does nothing except being there
1504 DummyValue = class(AbstractValue)
1506 function DummyValue.__init__(self, ...)
1507 AbstractValue.__init__(self, ...)
1508 self.template = "cbi/dvalue"
1512 function DummyValue.cfgvalue(self, section)
1515 if type(self.value) == "function" then
1516 value = self:value(section)
1521 value = AbstractValue.cfgvalue(self, section)
1526 function DummyValue.parse(self)
1532 Flag - A flag being enabled or disabled
1534 Flag = class(AbstractValue)
1536 function Flag.__init__(self, ...)
1537 AbstractValue.__init__(self, ...)
1538 self.template = "cbi/fvalue"
1544 -- A flag can only have two states: set or unset
1545 function Flag.parse(self, section)
1546 local fvalue = self:formvalue(section)
1549 fvalue = self.enabled
1551 fvalue = self.disabled
1554 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1555 if not(fvalue == self:cfgvalue(section)) then
1556 self:write(section, fvalue)
1559 self:remove(section)
1566 ListValue - A one-line value predefined in a list
1567 widget: The widget that will be used (select, radio)
1569 ListValue = class(AbstractValue)
1571 function ListValue.__init__(self, ...)
1572 AbstractValue.__init__(self, ...)
1573 self.template = "cbi/lvalue"
1578 self.widget = "select"
1581 function ListValue.prepare(self, ...)
1582 AbstractValue.prepare(self, ...)
1583 if not self.override_scheme
1584 and self.map:get_scheme(self.section.sectiontype, self.option) then
1585 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1586 if self.value and vs.valuelist and not self.override_values then
1587 for k, v in ipairs(vs.valuelist) do
1589 if not self.override_dependencies
1590 and vs.enum_depends and vs.enum_depends[v.value] then
1591 for i, dep in ipairs(vs.enum_depends[v.value]) do
1592 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1595 self:value(v.value, v.title or v.value, unpack(deps))
1601 function ListValue.value(self, key, val, ...)
1602 if luci.util.contains(self.keylist, key) then
1607 table.insert(self.keylist, tostring(key))
1608 table.insert(self.vallist, tostring(val))
1610 for i, deps in ipairs({...}) do
1611 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1615 function ListValue.validate(self, val)
1616 if luci.util.contains(self.keylist, val) then
1626 MultiValue - Multiple delimited values
1627 widget: The widget that will be used (select, checkbox)
1628 delimiter: The delimiter that will separate the values (default: " ")
1630 MultiValue = class(AbstractValue)
1632 function MultiValue.__init__(self, ...)
1633 AbstractValue.__init__(self, ...)
1634 self.template = "cbi/mvalue"
1639 self.widget = "checkbox"
1640 self.delimiter = " "
1643 function MultiValue.render(self, ...)
1644 if self.widget == "select" and not self.size then
1645 self.size = #self.vallist
1648 AbstractValue.render(self, ...)
1651 function MultiValue.value(self, key, val)
1652 if luci.util.contains(self.keylist, key) then
1657 table.insert(self.keylist, tostring(key))
1658 table.insert(self.vallist, tostring(val))
1661 function MultiValue.valuelist(self, section)
1662 local val = self:cfgvalue(section)
1664 if not(type(val) == "string") then
1668 return luci.util.split(val, self.delimiter)
1671 function MultiValue.validate(self, val)
1672 val = (type(val) == "table") and val or {val}
1676 for i, value in ipairs(val) do
1677 if luci.util.contains(self.keylist, value) then
1678 result = result and (result .. self.delimiter .. value) or value
1686 StaticList = class(MultiValue)
1688 function StaticList.__init__(self, ...)
1689 MultiValue.__init__(self, ...)
1691 self.valuelist = self.cfgvalue
1693 if not self.override_scheme
1694 and self.map:get_scheme(self.section.sectiontype, self.option) then
1695 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1696 if self.value and vs.values and not self.override_values then
1697 for k, v in pairs(vs.values) do
1704 function StaticList.validate(self, value)
1705 value = (type(value) == "table") and value or {value}
1708 for i, v in ipairs(value) do
1709 if luci.util.contains(self.keylist, v) then
1710 table.insert(valid, v)
1717 DynamicList = class(AbstractValue)
1719 function DynamicList.__init__(self, ...)
1720 AbstractValue.__init__(self, ...)
1721 self.template = "cbi/dynlist"
1727 function DynamicList.value(self, key, val)
1729 table.insert(self.keylist, tostring(key))
1730 table.insert(self.vallist, tostring(val))
1733 function DynamicList.write(self, ...)
1734 self.map.proceed = true
1735 return AbstractValue.write(self, ...)
1738 function DynamicList.formvalue(self, section)
1739 local value = AbstractValue.formvalue(self, section)
1740 value = (type(value) == "table") and value or {value}
1743 for i, v in ipairs(value) do
1745 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1746 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1747 table.insert(valid, v)
1756 TextValue - A multi-line value
1759 TextValue = class(AbstractValue)
1761 function TextValue.__init__(self, ...)
1762 AbstractValue.__init__(self, ...)
1763 self.template = "cbi/tvalue"
1769 Button = class(AbstractValue)
1771 function Button.__init__(self, ...)
1772 AbstractValue.__init__(self, ...)
1773 self.template = "cbi/button"
1774 self.inputstyle = nil
1779 FileUpload = class(AbstractValue)
1781 function FileUpload.__init__(self, ...)
1782 AbstractValue.__init__(self, ...)
1783 self.template = "cbi/upload"
1784 if not self.map.upload_fields then
1785 self.map.upload_fields = { self }
1787 self.map.upload_fields[#self.map.upload_fields+1] = self
1791 function FileUpload.formcreated(self, section)
1792 return AbstractValue.formcreated(self, section) or
1793 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1794 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1797 function FileUpload.cfgvalue(self, section)
1798 local val = AbstractValue.cfgvalue(self, section)
1799 if val and fs.access(val) then
1805 function FileUpload.formvalue(self, section)
1806 local val = AbstractValue.formvalue(self, section)
1808 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1809 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1819 function FileUpload.remove(self, section)
1820 local val = AbstractValue.formvalue(self, section)
1821 if val and fs.access(val) then fs.unlink(val) end
1822 return AbstractValue.remove(self, section)
1826 FileBrowser = class(AbstractValue)
1828 function FileBrowser.__init__(self, ...)
1829 AbstractValue.__init__(self, ...)
1830 self.template = "cbi/browser"