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 if type(node) == "table" and getmetatable(node) == nil then
523 node = Compound(unpack(node))
525 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
526 assert(not self.nodes[name], "Duplicate entry")
528 self.nodes[name] = node
531 function Delegator.add(self, name, node)
532 node = self:set(name, node)
533 self.defaultpath[#self.defaultpath+1] = name
536 function Delegator.insert_after(self, name, after)
537 local n = #self.chain + 1
538 for k, v in ipairs(self.chain) do
544 table.insert(self.chain, n, name)
547 function Delegator.set_route(self, ...)
548 local n, chain, route = 0, self.chain, {...}
550 if chain[i] == self.current then
559 for i = n + 1, #chain do
564 function Delegator.get(self, name)
565 return self.nodes[name]
568 function Delegator.parse(self, ...)
569 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
570 if self:_run_hooks("on_cancel") then
575 if not Map.formvalue(self, "cbi.delg.current") then
576 self:_run_hooks("on_init")
580 self.chain = self.chain or self:get_chain()
581 self.current = self.current or self:get_active()
582 self.active = self.active or self:get(self.current)
583 assert(self.active, "Invalid state")
585 local stat = FORM_DONE
586 if type(self.active) ~= "function" then
587 self.active:populate_delegator(self)
588 stat = self.active:parse()
593 if stat > FORM_PROCEED then
594 if Map.formvalue(self, "cbi.delg.back") then
595 newcurrent = self:get_prev(self.current)
597 newcurrent = self:get_next(self.current)
599 elseif stat < FORM_PROCEED then
604 if not Map.formvalue(self, "cbi.submit") then
606 elseif stat > FORM_PROCEED
607 and (not newcurrent or not self:get(newcurrent)) then
608 return self:_run_hook("on_done") or FORM_DONE
610 self.current = newcurrent or self.current
611 self.active = self:get(self.current)
612 if type(self.active) ~= "function" then
613 self.active:parse(false)
616 return self:parse(...)
621 function Delegator.get_next(self, state)
622 for k, v in ipairs(self.chain) do
624 return self.chain[k+1]
629 function Delegator.get_prev(self, state)
630 for k, v in ipairs(self.chain) do
632 return self.chain[k-1]
637 function Delegator.get_chain(self)
638 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
639 return type(x) == "table" and x or {x}
642 function Delegator.get_active(self)
643 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
651 Page.__init__ = Node.__init__
652 Page.parse = function() end
656 SimpleForm - A Simple non-UCI form
658 SimpleForm = class(Node)
660 function SimpleForm.__init__(self, config, title, description, data)
661 Node.__init__(self, title, description)
663 self.data = data or {}
664 self.template = "cbi/simpleform"
666 self.pageaction = false
667 self.readinput = true
670 SimpleForm.formvalue = Map.formvalue
671 SimpleForm.formvaluetable = Map.formvaluetable
673 function SimpleForm.parse(self, readinput, ...)
674 self.readinput = (readinput ~= false)
676 if self:formvalue("cbi.skip") then
680 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
684 if self:submitstate() then
685 Node.parse(self, 1, ...)
689 for k, j in ipairs(self.children) do
690 for i, v in ipairs(j.children) do
692 and (not v.tag_missing or not v.tag_missing[1])
693 and (not v.tag_invalid or not v.tag_invalid[1])
699 not self:submitstate() and FORM_NODATA
700 or valid and FORM_VALID
703 self.dorender = not self.handle
705 local nrender, nstate = self:handle(state, self.data)
706 self.dorender = self.dorender or (nrender ~= false)
707 state = nstate or state
712 function SimpleForm.render(self, ...)
713 if self.dorender then
714 Node.render(self, ...)
718 function SimpleForm.submitstate(self)
719 return self:formvalue("cbi.submit")
722 function SimpleForm.section(self, class, ...)
723 if instanceof(class, AbstractSection) then
724 local obj = class(self, ...)
728 error("class must be a descendent of AbstractSection")
732 -- Creates a child field
733 function SimpleForm.field(self, class, ...)
735 for k, v in ipairs(self.children) do
736 if instanceof(v, SimpleSection) then
742 section = self:section(SimpleSection)
745 if instanceof(class, AbstractValue) then
746 local obj = class(self, section, ...)
747 obj.track_missing = true
751 error("class must be a descendent of AbstractValue")
755 function SimpleForm.set(self, section, option, value)
756 self.data[option] = value
760 function SimpleForm.del(self, section, option)
761 self.data[option] = nil
765 function SimpleForm.get(self, section, option)
766 return self.data[option]
770 function SimpleForm.get_scheme()
775 Form = class(SimpleForm)
777 function Form.__init__(self, ...)
778 SimpleForm.__init__(self, ...)
786 AbstractSection = class(Node)
788 function AbstractSection.__init__(self, map, sectiontype, ...)
789 Node.__init__(self, ...)
790 self.sectiontype = sectiontype
792 self.config = map.config
797 self.tag_invalid = {}
798 self.tag_deperror = {}
802 self.addremove = false
806 -- Define a tab for the section
807 function AbstractSection.tab(self, tab, title, desc)
808 self.tabs = self.tabs or { }
809 self.tab_names = self.tab_names or { }
811 self.tab_names[#self.tab_names+1] = tab
819 -- Appends a new option
820 function AbstractSection.option(self, class, option, ...)
821 -- Autodetect from UVL
822 if class == true and self.map:get_scheme(self.sectiontype, option) then
823 local vs = self.map:get_scheme(self.sectiontype, option)
824 if vs.type == "boolean" then
826 elseif vs.type == "list" then
828 elseif vs.type == "enum" or vs.type == "reference" then
835 if instanceof(class, AbstractValue) then
836 local obj = class(self.map, self, option, ...)
838 self.fields[option] = obj
840 elseif class == true then
841 error("No valid class was given and autodetection failed.")
843 error("class must be a descendant of AbstractValue")
847 -- Appends a new tabbed option
848 function AbstractSection.taboption(self, tab, ...)
850 assert(tab and self.tabs and self.tabs[tab],
851 "Cannot assign option to not existing tab %q" % tostring(tab))
853 local l = self.tabs[tab].childs
854 local o = AbstractSection.option(self, ...)
856 if o then l[#l+1] = o end
861 -- Render a single tab
862 function AbstractSection.render_tab(self, tab, ...)
864 assert(tab and self.tabs and self.tabs[tab],
865 "Cannot render not existing tab %q" % tostring(tab))
867 for _, node in ipairs(self.tabs[tab].childs) do
872 -- Parse optional options
873 function AbstractSection.parse_optionals(self, section)
874 if not self.optional then
878 self.optionals[section] = {}
880 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
881 for k,v in ipairs(self.children) do
882 if v.optional and not v:cfgvalue(section) then
883 if field == v.option then
885 self.map.proceed = true
887 table.insert(self.optionals[section], v)
892 if field and #field > 0 and self.dynamic then
893 self:add_dynamic(field)
897 -- Add a dynamic option
898 function AbstractSection.add_dynamic(self, field, optional)
899 local o = self:option(Value, field, field)
900 o.optional = optional
903 -- Parse all dynamic options
904 function AbstractSection.parse_dynamic(self, section)
905 if not self.dynamic then
909 local arr = luci.util.clone(self:cfgvalue(section))
910 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
911 for k, v in pairs(form) do
915 for key,val in pairs(arr) do
918 for i,c in ipairs(self.children) do
919 if c.option == key then
924 if create and key:sub(1, 1) ~= "." then
925 self.map.proceed = true
926 self:add_dynamic(key, true)
931 -- Returns the section's UCI table
932 function AbstractSection.cfgvalue(self, section)
933 return self.map:get(section)
937 function AbstractSection.push_events(self)
938 --luci.util.append(self.map.events, self.events)
939 self.map.changed = true
942 -- Removes the section
943 function AbstractSection.remove(self, section)
944 self.map.proceed = true
945 return self.map:del(section)
948 -- Creates the section
949 function AbstractSection.create(self, section)
953 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
955 section = self.map:add(self.sectiontype)
960 for k,v in pairs(self.children) do
962 self.map:set(section, v.option, v.default)
966 for k,v in pairs(self.defaults) do
967 self.map:set(section, k, v)
971 self.map.proceed = true
977 SimpleSection = class(AbstractSection)
979 function SimpleSection.__init__(self, form, ...)
980 AbstractSection.__init__(self, form, nil, ...)
981 self.template = "cbi/nullsection"
985 Table = class(AbstractSection)
987 function Table.__init__(self, form, data, ...)
988 local datasource = {}
990 datasource.config = "table"
991 self.data = data or {}
993 datasource.formvalue = Map.formvalue
994 datasource.formvaluetable = Map.formvaluetable
995 datasource.readinput = true
997 function datasource.get(self, section, option)
998 return tself.data[section] and tself.data[section][option]
1001 function datasource.submitstate(self)
1002 return Map.formvalue(self, "cbi.submit")
1005 function datasource.del(...)
1009 function datasource.get_scheme()
1013 AbstractSection.__init__(self, datasource, "table", ...)
1014 self.template = "cbi/tblsection"
1015 self.rowcolors = true
1016 self.anonymous = true
1019 function Table.parse(self, readinput)
1020 self.map.readinput = (readinput ~= false)
1021 for i, k in ipairs(self:cfgsections()) do
1022 if self.map:submitstate() then
1028 function Table.cfgsections(self)
1031 for i, v in luci.util.kspairs(self.data) do
1032 table.insert(sections, i)
1038 function Table.update(self, data)
1045 NamedSection - A fixed configuration section defined by its name
1047 NamedSection = class(AbstractSection)
1049 function NamedSection.__init__(self, map, section, stype, ...)
1050 AbstractSection.__init__(self, map, stype, ...)
1053 self.addremove = false
1055 -- Use defaults from UVL
1056 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1057 local vs = self.map:get_scheme(self.sectiontype)
1058 self.addremove = not vs.unique and not vs.required
1059 self.dynamic = vs.dynamic
1060 self.title = self.title or vs.title
1061 self.description = self.description or vs.descr
1064 self.template = "cbi/nsection"
1065 self.section = section
1068 function NamedSection.parse(self, novld)
1069 local s = self.section
1070 local active = self:cfgvalue(s)
1072 if self.addremove then
1073 local path = self.config.."."..s
1074 if active then -- Remove the section
1075 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1079 else -- Create and apply default values
1080 if self.map:formvalue("cbi.cns."..path) then
1088 AbstractSection.parse_dynamic(self, s)
1089 if self.map:submitstate() then
1092 if not novld and not self.override_scheme and self.map.scheme then
1093 _uvl_validate_section(self, s)
1096 AbstractSection.parse_optionals(self, s)
1098 if self.changed then
1106 TypedSection - A (set of) configuration section(s) defined by the type
1107 addremove: Defines whether the user can add/remove sections of this type
1108 anonymous: Allow creating anonymous sections
1109 validate: a validation function returning nil if the section is invalid
1111 TypedSection = class(AbstractSection)
1113 function TypedSection.__init__(self, map, type, ...)
1114 AbstractSection.__init__(self, map, type, ...)
1116 self.template = "cbi/tsection"
1118 self.anonymous = false
1120 -- Use defaults from UVL
1121 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1122 local vs = self.map:get_scheme(self.sectiontype)
1123 self.addremove = not vs.unique and not vs.required
1124 self.dynamic = vs.dynamic
1125 self.anonymous = not vs.named
1126 self.title = self.title or vs.title
1127 self.description = self.description or vs.descr
1131 -- Return all matching UCI sections for this TypedSection
1132 function TypedSection.cfgsections(self)
1134 self.map.uci:foreach(self.map.config, self.sectiontype,
1136 if self:checkscope(section[".name"]) then
1137 table.insert(sections, section[".name"])
1144 -- Limits scope to sections that have certain option => value pairs
1145 function TypedSection.depends(self, option, value)
1146 table.insert(self.deps, {option=option, value=value})
1149 function TypedSection.parse(self, novld)
1150 if self.addremove then
1152 local crval = REMOVE_PREFIX .. self.config
1153 local name = self.map:formvaluetable(crval)
1154 for k,v in pairs(name) do
1155 if k:sub(-2) == ".x" then
1156 k = k:sub(1, #k - 2)
1158 if self:cfgvalue(k) and self:checkscope(k) then
1165 for i, k in ipairs(self:cfgsections()) do
1166 AbstractSection.parse_dynamic(self, k)
1167 if self.map:submitstate() then
1168 Node.parse(self, k, novld)
1170 if not novld and not self.override_scheme and self.map.scheme then
1171 _uvl_validate_section(self, k)
1174 AbstractSection.parse_optionals(self, k)
1177 if self.addremove then
1180 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1181 local name = self.map:formvalue(crval)
1182 if self.anonymous then
1184 created = self:create()
1188 -- Ignore if it already exists
1189 if self:cfgvalue(name) then
1193 name = self:checkscope(name)
1196 self.err_invalid = true
1199 if name and #name > 0 then
1200 created = self:create(name) and name
1202 self.invalid_cts = true
1209 AbstractSection.parse_optionals(self, created)
1213 if created or self.changed then
1218 -- Verifies scope of sections
1219 function TypedSection.checkscope(self, section)
1220 -- Check if we are not excluded
1221 if self.filter and not self:filter(section) then
1225 -- Check if at least one dependency is met
1226 if #self.deps > 0 and self:cfgvalue(section) then
1229 for k, v in ipairs(self.deps) do
1230 if self:cfgvalue(section)[v.option] == v.value then
1240 return self:validate(section)
1244 -- Dummy validate function
1245 function TypedSection.validate(self, section)
1251 AbstractValue - An abstract Value Type
1252 null: Value can be empty
1253 valid: A function returning the value if it is valid otherwise nil
1254 depends: A table of option => value pairs of which one must be true
1255 default: The default value
1256 size: The size of the input fields
1257 rmempty: Unset value if empty
1258 optional: This value is optional (see AbstractSection.optionals)
1260 AbstractValue = class(Node)
1262 function AbstractValue.__init__(self, map, section, option, ...)
1263 Node.__init__(self, ...)
1264 self.section = section
1265 self.option = option
1267 self.config = map.config
1268 self.tag_invalid = {}
1269 self.tag_missing = {}
1270 self.tag_reqerror = {}
1274 --self.cast = "string"
1276 self.track_missing = false
1280 self.optional = false
1283 function AbstractValue.prepare(self)
1284 -- Use defaults from UVL
1285 if not self.override_scheme
1286 and self.map:get_scheme(self.section.sectiontype, self.option) then
1287 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1288 if self.cast == nil then
1289 self.cast = (vs.type == "list") and "list" or "string"
1291 self.title = self.title or vs.title
1292 self.description = self.description or vs.descr
1293 if self.default == nil then
1294 self.default = vs.default
1297 if vs.depends and not self.override_dependencies then
1298 for i, deps in ipairs(vs.depends) do
1299 deps = _uvl_strip_remote_dependencies(deps)
1307 self.cast = self.cast or "string"
1310 -- Add a dependencie to another section field
1311 function AbstractValue.depends(self, field, value)
1313 if type(field) == "string" then
1320 table.insert(self.deps, {deps=deps, add=""})
1323 -- Generates the unique CBID
1324 function AbstractValue.cbid(self, section)
1325 return "cbid."..self.map.config.."."..section.."."..self.option
1328 -- Return whether this object should be created
1329 function AbstractValue.formcreated(self, section)
1330 local key = "cbi.opt."..self.config.."."..section
1331 return (self.map:formvalue(key) == self.option)
1334 -- Returns the formvalue for this object
1335 function AbstractValue.formvalue(self, section)
1336 return self.map:formvalue(self:cbid(section))
1339 function AbstractValue.additional(self, value)
1340 self.optional = value
1343 function AbstractValue.mandatory(self, value)
1344 self.rmempty = not value
1347 function AbstractValue.parse(self, section, novld)
1348 local fvalue = self:formvalue(section)
1349 local cvalue = self:cfgvalue(section)
1351 -- If favlue and cvalue are both tables and have the same content
1352 -- make them identical
1353 if type(fvalue) == "table" and type(cvalue) == "table" then
1354 local equal = #fvalue == #cvalue
1357 if cvalue[i] ~= fvalue[i] then
1367 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1368 fvalue = self:transform(self:validate(fvalue, section))
1369 if not fvalue and not novld then
1371 self.error[section] = "invalid"
1373 self.error = { [section] = "invalid" }
1375 if self.section.error then
1376 table.insert(self.section.error[section], "invalid")
1378 self.section.error = {[section] = {"invalid"}}
1380 self.map.save = false
1382 if fvalue and not (fvalue == cvalue) then
1383 if self:write(section, fvalue) then
1385 self.section.changed = true
1386 --luci.util.append(self.map.events, self.events)
1389 else -- Unset the UCI or error
1390 if self.rmempty or self.optional then
1391 if self:remove(section) then
1393 self.section.changed = true
1394 --luci.util.append(self.map.events, self.events)
1396 elseif cvalue ~= fvalue and not novld then
1397 self:write(section, fvalue or "")
1399 self.error[section] = "missing"
1401 self.error = { [section] = "missing" }
1403 self.map.save = false
1408 -- Render if this value exists or if it is mandatory
1409 function AbstractValue.render(self, s, scope)
1410 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1413 scope.cbid = self:cbid(s)
1414 scope.striptags = luci.util.striptags
1415 scope.pcdata = luci.util.pcdata
1417 scope.ifattr = function(cond,key,val)
1419 return string.format(
1420 ' %s="%s"', tostring(key),
1421 luci.util.pcdata(tostring( val
1423 or (type(self[key]) ~= "function" and self[key])
1431 scope.attr = function(...)
1432 return scope.ifattr( true, ... )
1435 Node.render(self, scope)
1439 -- Return the UCI value of this object
1440 function AbstractValue.cfgvalue(self, section)
1441 local value = self.map:get(section, self.option)
1444 elseif not self.cast or self.cast == type(value) then
1446 elseif self.cast == "string" then
1447 if type(value) == "table" then
1450 elseif self.cast == "table" then
1451 return luci.util.split(value, "%s+", nil, true)
1455 -- Validate the form value
1456 function AbstractValue.validate(self, value)
1460 AbstractValue.transform = AbstractValue.validate
1464 function AbstractValue.write(self, section, value)
1465 return self.map:set(section, self.option, value)
1469 function AbstractValue.remove(self, section)
1470 return self.map:del(section, self.option)
1477 Value - A one-line value
1478 maxlength: The maximum length
1480 Value = class(AbstractValue)
1482 function Value.__init__(self, ...)
1483 AbstractValue.__init__(self, ...)
1484 self.template = "cbi/value"
1489 function Value.value(self, key, val)
1491 table.insert(self.keylist, tostring(key))
1492 table.insert(self.vallist, tostring(val))
1496 -- DummyValue - This does nothing except being there
1497 DummyValue = class(AbstractValue)
1499 function DummyValue.__init__(self, ...)
1500 AbstractValue.__init__(self, ...)
1501 self.template = "cbi/dvalue"
1505 function DummyValue.cfgvalue(self, section)
1508 if type(self.value) == "function" then
1509 value = self:value(section)
1514 value = AbstractValue.cfgvalue(self, section)
1519 function DummyValue.parse(self)
1525 Flag - A flag being enabled or disabled
1527 Flag = class(AbstractValue)
1529 function Flag.__init__(self, ...)
1530 AbstractValue.__init__(self, ...)
1531 self.template = "cbi/fvalue"
1537 -- A flag can only have two states: set or unset
1538 function Flag.parse(self, section)
1539 local fvalue = self:formvalue(section)
1542 fvalue = self.enabled
1544 fvalue = self.disabled
1547 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1548 if not(fvalue == self:cfgvalue(section)) then
1549 self:write(section, fvalue)
1552 self:remove(section)
1559 ListValue - A one-line value predefined in a list
1560 widget: The widget that will be used (select, radio)
1562 ListValue = class(AbstractValue)
1564 function ListValue.__init__(self, ...)
1565 AbstractValue.__init__(self, ...)
1566 self.template = "cbi/lvalue"
1571 self.widget = "select"
1574 function ListValue.prepare(self, ...)
1575 AbstractValue.prepare(self, ...)
1576 if not self.override_scheme
1577 and self.map:get_scheme(self.section.sectiontype, self.option) then
1578 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1579 if self.value and vs.valuelist and not self.override_values then
1580 for k, v in ipairs(vs.valuelist) do
1582 if not self.override_dependencies
1583 and vs.enum_depends and vs.enum_depends[v.value] then
1584 for i, dep in ipairs(vs.enum_depends[v.value]) do
1585 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1588 self:value(v.value, v.title or v.value, unpack(deps))
1594 function ListValue.value(self, key, val, ...)
1595 if luci.util.contains(self.keylist, key) then
1600 table.insert(self.keylist, tostring(key))
1601 table.insert(self.vallist, tostring(val))
1603 for i, deps in ipairs({...}) do
1604 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1608 function ListValue.validate(self, val)
1609 if luci.util.contains(self.keylist, val) then
1619 MultiValue - Multiple delimited values
1620 widget: The widget that will be used (select, checkbox)
1621 delimiter: The delimiter that will separate the values (default: " ")
1623 MultiValue = class(AbstractValue)
1625 function MultiValue.__init__(self, ...)
1626 AbstractValue.__init__(self, ...)
1627 self.template = "cbi/mvalue"
1632 self.widget = "checkbox"
1633 self.delimiter = " "
1636 function MultiValue.render(self, ...)
1637 if self.widget == "select" and not self.size then
1638 self.size = #self.vallist
1641 AbstractValue.render(self, ...)
1644 function MultiValue.value(self, key, val)
1645 if luci.util.contains(self.keylist, key) then
1650 table.insert(self.keylist, tostring(key))
1651 table.insert(self.vallist, tostring(val))
1654 function MultiValue.valuelist(self, section)
1655 local val = self:cfgvalue(section)
1657 if not(type(val) == "string") then
1661 return luci.util.split(val, self.delimiter)
1664 function MultiValue.validate(self, val)
1665 val = (type(val) == "table") and val or {val}
1669 for i, value in ipairs(val) do
1670 if luci.util.contains(self.keylist, value) then
1671 result = result and (result .. self.delimiter .. value) or value
1679 StaticList = class(MultiValue)
1681 function StaticList.__init__(self, ...)
1682 MultiValue.__init__(self, ...)
1684 self.valuelist = self.cfgvalue
1686 if not self.override_scheme
1687 and self.map:get_scheme(self.section.sectiontype, self.option) then
1688 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1689 if self.value and vs.values and not self.override_values then
1690 for k, v in pairs(vs.values) do
1697 function StaticList.validate(self, value)
1698 value = (type(value) == "table") and value or {value}
1701 for i, v in ipairs(value) do
1702 if luci.util.contains(self.keylist, v) then
1703 table.insert(valid, v)
1710 DynamicList = class(AbstractValue)
1712 function DynamicList.__init__(self, ...)
1713 AbstractValue.__init__(self, ...)
1714 self.template = "cbi/dynlist"
1720 function DynamicList.value(self, key, val)
1722 table.insert(self.keylist, tostring(key))
1723 table.insert(self.vallist, tostring(val))
1726 function DynamicList.write(self, ...)
1727 self.map.proceed = true
1728 return AbstractValue.write(self, ...)
1731 function DynamicList.formvalue(self, section)
1732 local value = AbstractValue.formvalue(self, section)
1733 value = (type(value) == "table") and value or {value}
1736 for i, v in ipairs(value) do
1738 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1739 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1740 table.insert(valid, v)
1749 TextValue - A multi-line value
1752 TextValue = class(AbstractValue)
1754 function TextValue.__init__(self, ...)
1755 AbstractValue.__init__(self, ...)
1756 self.template = "cbi/tvalue"
1762 Button = class(AbstractValue)
1764 function Button.__init__(self, ...)
1765 AbstractValue.__init__(self, ...)
1766 self.template = "cbi/button"
1767 self.inputstyle = nil
1772 FileUpload = class(AbstractValue)
1774 function FileUpload.__init__(self, ...)
1775 AbstractValue.__init__(self, ...)
1776 self.template = "cbi/upload"
1777 if not self.map.upload_fields then
1778 self.map.upload_fields = { self }
1780 self.map.upload_fields[#self.map.upload_fields+1] = self
1784 function FileUpload.formcreated(self, section)
1785 return AbstractValue.formcreated(self, section) or
1786 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1787 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1790 function FileUpload.cfgvalue(self, section)
1791 local val = AbstractValue.cfgvalue(self, section)
1792 if val and fs.access(val) then
1798 function FileUpload.formvalue(self, section)
1799 local val = AbstractValue.formvalue(self, section)
1801 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1802 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1812 function FileUpload.remove(self, section)
1813 local val = AbstractValue.formvalue(self, section)
1814 if val and fs.access(val) then fs.unlink(val) end
1815 return AbstractValue.remove(self, section)
1819 FileBrowser = class(AbstractValue)
1821 function FileBrowser.__init__(self, ...)
1822 AbstractValue.__init__(self, ...)
1823 self.template = "cbi/browser"