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("cbi")
78 luci.i18n.loadc("uvl")
81 translate=i18n.translate,
82 translatef=i18n.translatef,
86 setfenv(func, setmetatable(env, {__index =
88 return rawget(tbl, key) or _M[key] or _G[key]
91 local maps = { func() }
93 local has_upload = false
95 for i, map in ipairs(maps) do
96 if not instanceof(map, Node) then
97 error("CBI map returns no valid map object!")
101 if map.upload_fields then
103 for _, field in ipairs(map.upload_fields) do
105 field.config .. '.' ..
106 field.section.sectiontype .. '.' ..
115 local uci = luci.model.uci.cursor()
116 local prm = luci.http.context.request.message.params
119 luci.http.setfilehandler(
120 function( field, chunk, eof )
121 if not field then return end
122 if field.name and not cbid then
123 local c, s, o = field.name:gmatch(
124 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
127 if c and s and o then
128 local t = uci:get( c, s )
129 if t and uploads[c.."."..t.."."..o] then
130 local path = upldir .. field.name
131 fd = io.open(path, "w")
140 if field.name == cbid and fd then
156 local function _uvl_validate_section(node, name)
157 local co = node.map:get()
159 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
160 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
162 local function tag_fields(e)
163 if e.option and node.fields[e.option] then
164 if node.fields[e.option].error then
165 node.fields[e.option].error[name] = e
167 node.fields[e.option].error = { [name] = e }
170 for _, c in ipairs(e.childs) do tag_fields(c) end
174 local function tag_section(e)
176 for _, c in ipairs(e.childs or { e }) do
177 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
178 table.insert( s, c.childs[1]:string() )
180 table.insert( s, c:string() )
187 node.error = { [name] = s }
192 local stat, err = node.map.validator:validate_section(node.config, name, co)
194 node.map.save = false
201 local function _uvl_strip_remote_dependencies(deps)
204 for k, v in pairs(deps) do
205 k = k:gsub("%$config%.%$section%.", "")
206 if k:match("^[%w_]+$") and type(v) == "string" then
215 -- Node pseudo abstract class
218 function Node.__init__(self, title, description)
220 self.title = title or ""
221 self.description = description or ""
222 self.template = "cbi/node"
226 function Node._i18n(self, config, section, option, title, description)
229 if type(luci.i18n) == "table" then
231 local key = config and config:gsub("[^%w]+", "") or ""
233 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
234 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
236 self.title = title or luci.i18n.translate( key, option or section or config )
237 self.description = description or luci.i18n.translate( key .. "_desc", "" )
242 function Node._run_hooks(self, ...)
245 for _, f in ipairs(arg) do
246 if type(self[f]) == "function" then
255 function Node.prepare(self, ...)
256 for k, child in ipairs(self.children) do
261 -- Append child nodes
262 function Node.append(self, obj)
263 table.insert(self.children, obj)
266 -- Parse this node and its children
267 function Node.parse(self, ...)
268 for k, child in ipairs(self.children) do
274 function Node.render(self, scope)
278 luci.template.render(self.template, scope)
281 -- Render the children
282 function Node.render_children(self, ...)
283 for k, node in ipairs(self.children) do
290 A simple template element
292 Template = class(Node)
294 function Template.__init__(self, template)
296 self.template = template
299 function Template.render(self)
300 luci.template.render(self.template, {self=self})
303 function Template.parse(self, readinput)
304 self.readinput = (readinput ~= false)
305 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
310 Map - A map describing a configuration file
314 function Map.__init__(self, config, ...)
315 Node.__init__(self, ...)
316 Node._i18n(self, config, nil, nil, ...)
319 self.parsechain = {self.config}
320 self.template = "cbi/map"
321 self.apply_on_parse = nil
322 self.readinput = true
326 self.uci = uci.cursor()
331 if not self.uci:load(self.config) then
332 error("Unable to read UCI data: " .. self.config)
335 self.validator = luci.uvl.UVL()
336 self.scheme = self.validator:get_scheme(self.config)
339 function Map.formvalue(self, key)
340 return self.readinput and luci.http.formvalue(key)
343 function Map.formvaluetable(self, key)
344 return self.readinput and luci.http.formvaluetable(key) or {}
347 function Map.get_scheme(self, sectiontype, option)
349 return self.scheme and self.scheme.sections[sectiontype]
351 return self.scheme and self.scheme.variables[sectiontype]
352 and self.scheme.variables[sectiontype][option]
356 function Map.submitstate(self)
357 return self:formvalue("cbi.submit")
360 -- Chain foreign config
361 function Map.chain(self, config)
362 table.insert(self.parsechain, config)
365 function Map.state_handler(self, state)
369 -- Use optimized UCI writing
370 function Map.parse(self, readinput, ...)
371 self.readinput = (readinput ~= false)
373 if self:formvalue("cbi.skip") then
374 self.state = FORM_SKIP
375 return self:state_handler(self.state)
378 Node.parse(self, ...)
381 for i, config in ipairs(self.parsechain) do
382 self.uci:save(config)
384 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
385 self:_run_hooks("on_before_commit")
386 for i, config in ipairs(self.parsechain) do
387 self.uci:commit(config)
389 -- Refresh data because commit changes section names
390 self.uci:load(config)
392 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
393 if self.apply_on_parse then
394 self.uci:apply(self.parsechain)
395 self:_run_hooks("on_apply", "on_after_apply")
397 self._apply = function()
398 local cmd = self.uci:apply(self.parsechain, true)
404 Node.parse(self, true)
407 for i, config in ipairs(self.parsechain) do
408 self.uci:unload(config)
410 if type(self.commit_handler) == "function" then
411 self:commit_handler(self:submitstate())
415 if self:submitstate() then
416 if not self.save then
417 self.state = FORM_INVALID
418 elseif self.proceed then
419 self.state = FORM_PROCEED
421 self.state = self.changed and FORM_CHANGED or FORM_VALID
424 self.state = FORM_NODATA
427 return self:state_handler(self.state)
430 function Map.render(self, ...)
431 self:_run_hooks("on_init")
432 Node.render(self, ...)
434 local fp = self._apply()
437 self:_run_hooks("on_apply")
441 -- Creates a child section
442 function Map.section(self, class, ...)
443 if instanceof(class, AbstractSection) then
444 local obj = class(self, ...)
448 error("class must be a descendent of AbstractSection")
453 function Map.add(self, sectiontype)
454 return self.uci:add(self.config, sectiontype)
458 function Map.set(self, section, option, value)
460 return self.uci:set(self.config, section, option, value)
462 return self.uci:set(self.config, section, value)
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 if type(node) == "table" and getmetatable(node) == nil then
534 node = Compound(unpack(node))
536 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
537 assert(not self.nodes[name], "Duplicate entry")
539 self.nodes[name] = node
542 function Delegator.add(self, name, node)
543 node = self:set(name, node)
544 self.defaultpath[#self.defaultpath+1] = name
547 function Delegator.insert_after(self, name, after)
548 local n = #self.chain
549 for k, v in ipairs(self.chain) do
555 table.insert(self.chain, n, name)
558 function Delegator.set_route(self, ...)
559 local n, chain, route = 0, self.chain, {...}
561 if chain[i] == self.current then
570 for i = n + 1, #chain do
575 function Delegator.get(self, name)
576 return self.nodes[name]
579 function Delegator.parse(self, ...)
580 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
581 if self:_run_hooks("on_cancel") then
586 if not Map.formvalue(self, "cbi.delg.current") then
587 self:_run_hooks("on_init")
591 self.chain = self.chain or self:get_chain()
592 self.current = self.current or self:get_active()
593 self.active = self.active or self:get(self.current)
594 assert(self.active, "Invalid state")
596 local stat = FORM_DONE
597 if type(self.active) ~= "function" then
598 self.active:populate_delegator(self)
599 stat = self.active:parse()
604 if stat > FORM_PROCEED then
605 if Map.formvalue(self, "cbi.delg.back") then
606 newcurrent = self:get_prev(self.current)
608 newcurrent = self:get_next(self.current)
610 elseif stat < FORM_PROCEED then
615 if not Map.formvalue(self, "cbi.submit") then
617 elseif stat > FORM_PROCEED
618 and (not newcurrent or not self:get(newcurrent)) then
619 self:_run_hooks("on_done")
622 self.current = newcurrent or self.current
623 self.active = self:get(self.current)
624 if type(self.active) ~= "function" then
625 self.active:parse(false)
628 return self:parse(...)
633 function Delegator.get_next(self, state)
634 for k, v in ipairs(self.chain) do
636 return self.chain[k+1]
641 function Delegator.get_prev(self, state)
642 for k, v in ipairs(self.chain) do
644 return self.chain[k-1]
649 function Delegator.get_chain(self)
650 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
651 return type(x) == "table" and x or {x}
654 function Delegator.get_active(self)
655 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
663 Page.__init__ = Node.__init__
664 Page.parse = function() end
668 SimpleForm - A Simple non-UCI form
670 SimpleForm = class(Node)
672 function SimpleForm.__init__(self, config, title, description, data)
673 Node.__init__(self, title, description)
675 self.data = data or {}
676 self.template = "cbi/simpleform"
678 self.pageaction = false
679 self.readinput = true
682 SimpleForm.formvalue = Map.formvalue
683 SimpleForm.formvaluetable = Map.formvaluetable
685 function SimpleForm.parse(self, readinput, ...)
686 self.readinput = (readinput ~= false)
688 if self:formvalue("cbi.skip") then
692 if self:submitstate() then
693 Node.parse(self, 1, ...)
697 for k, j in ipairs(self.children) do
698 for i, v in ipairs(j.children) do
700 and (not v.tag_missing or not v.tag_missing[1])
701 and (not v.tag_invalid or not v.tag_invalid[1])
707 not self:submitstate() and FORM_NODATA
708 or valid and FORM_VALID
711 self.dorender = not self.handle
713 local nrender, nstate = self:handle(state, self.data)
714 self.dorender = self.dorender or (nrender ~= false)
715 state = nstate or state
720 function SimpleForm.render(self, ...)
721 if self.dorender then
722 Node.render(self, ...)
726 function SimpleForm.submitstate(self)
727 return self:formvalue("cbi.submit")
730 function SimpleForm.section(self, class, ...)
731 if instanceof(class, AbstractSection) then
732 local obj = class(self, ...)
736 error("class must be a descendent of AbstractSection")
740 -- Creates a child field
741 function SimpleForm.field(self, class, ...)
743 for k, v in ipairs(self.children) do
744 if instanceof(v, SimpleSection) then
750 section = self:section(SimpleSection)
753 if instanceof(class, AbstractValue) then
754 local obj = class(self, section, ...)
755 obj.track_missing = true
759 error("class must be a descendent of AbstractValue")
763 function SimpleForm.set(self, section, option, value)
764 self.data[option] = value
768 function SimpleForm.del(self, section, option)
769 self.data[option] = nil
773 function SimpleForm.get(self, section, option)
774 return self.data[option]
778 function SimpleForm.get_scheme()
783 Form = class(SimpleForm)
785 function Form.__init__(self, ...)
786 SimpleForm.__init__(self, ...)
794 AbstractSection = class(Node)
796 function AbstractSection.__init__(self, map, sectiontype, ...)
797 Node.__init__(self, ...)
798 self.sectiontype = sectiontype
800 self.config = map.config
805 self.tag_invalid = {}
806 self.tag_deperror = {}
810 self.addremove = false
814 -- Define a tab for the section
815 function AbstractSection.tab(self, tab, title, desc)
816 self.tabs = self.tabs or { }
817 self.tab_names = self.tab_names or { }
819 self.tab_names[#self.tab_names+1] = tab
827 -- Appends a new option
828 function AbstractSection.option(self, class, option, ...)
829 -- Autodetect from UVL
830 if class == true and self.map:get_scheme(self.sectiontype, option) then
831 local vs = self.map:get_scheme(self.sectiontype, option)
832 if vs.type == "boolean" then
834 elseif vs.type == "list" then
836 elseif vs.type == "enum" or vs.type == "reference" then
843 if instanceof(class, AbstractValue) then
844 local obj = class(self.map, self, option, ...)
846 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
849 self.fields[option] = obj
851 elseif class == true then
852 error("No valid class was given and autodetection failed.")
854 error("class must be a descendant of AbstractValue")
858 -- Appends a new tabbed option
859 function AbstractSection.taboption(self, tab, ...)
861 assert(tab and self.tabs and self.tabs[tab],
862 "Cannot assign option to not existing tab %q" % tostring(tab))
864 local l = self.tabs[tab].childs
865 local o = AbstractSection.option(self, ...)
867 if o then l[#l+1] = o end
872 -- Render a single tab
873 function AbstractSection.render_tab(self, tab, ...)
875 assert(tab and self.tabs and self.tabs[tab],
876 "Cannot render not existing tab %q" % tostring(tab))
878 for _, node in ipairs(self.tabs[tab].childs) do
883 -- Parse optional options
884 function AbstractSection.parse_optionals(self, section)
885 if not self.optional then
889 self.optionals[section] = {}
891 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
892 for k,v in ipairs(self.children) do
893 if v.optional and not v:cfgvalue(section) then
894 if field == v.option then
896 self.map.proceed = true
898 table.insert(self.optionals[section], v)
903 if field and #field > 0 and self.dynamic then
904 self:add_dynamic(field)
908 -- Add a dynamic option
909 function AbstractSection.add_dynamic(self, field, optional)
910 local o = self:option(Value, field, field)
911 o.optional = optional
914 -- Parse all dynamic options
915 function AbstractSection.parse_dynamic(self, section)
916 if not self.dynamic then
920 local arr = luci.util.clone(self:cfgvalue(section))
921 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
922 for k, v in pairs(form) do
926 for key,val in pairs(arr) do
929 for i,c in ipairs(self.children) do
930 if c.option == key then
935 if create and key:sub(1, 1) ~= "." then
936 self.map.proceed = true
937 self:add_dynamic(key, true)
942 -- Returns the section's UCI table
943 function AbstractSection.cfgvalue(self, section)
944 return self.map:get(section)
948 function AbstractSection.push_events(self)
949 --luci.util.append(self.map.events, self.events)
950 self.map.changed = true
953 -- Removes the section
954 function AbstractSection.remove(self, section)
955 self.map.proceed = true
956 return self.map:del(section)
959 -- Creates the section
960 function AbstractSection.create(self, section)
964 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
966 section = self.map:add(self.sectiontype)
971 for k,v in pairs(self.children) do
973 self.map:set(section, v.option, v.default)
977 for k,v in pairs(self.defaults) do
978 self.map:set(section, k, v)
982 self.map.proceed = true
988 SimpleSection = class(AbstractSection)
990 function SimpleSection.__init__(self, form, ...)
991 AbstractSection.__init__(self, form, nil, ...)
992 self.template = "cbi/nullsection"
996 Table = class(AbstractSection)
998 function Table.__init__(self, form, data, ...)
999 local datasource = {}
1001 datasource.config = "table"
1002 self.data = data or {}
1004 datasource.formvalue = Map.formvalue
1005 datasource.formvaluetable = Map.formvaluetable
1006 datasource.readinput = true
1008 function datasource.get(self, section, option)
1009 return tself.data[section] and tself.data[section][option]
1012 function datasource.submitstate(self)
1013 return Map.formvalue(self, "cbi.submit")
1016 function datasource.del(...)
1020 function datasource.get_scheme()
1024 AbstractSection.__init__(self, datasource, "table", ...)
1025 self.template = "cbi/tblsection"
1026 self.rowcolors = true
1027 self.anonymous = true
1030 function Table.parse(self, readinput)
1031 self.map.readinput = (readinput ~= false)
1032 for i, k in ipairs(self:cfgsections()) do
1033 if self.map:submitstate() then
1039 function Table.cfgsections(self)
1042 for i, v in luci.util.kspairs(self.data) do
1043 table.insert(sections, i)
1049 function Table.update(self, data)
1056 NamedSection - A fixed configuration section defined by its name
1058 NamedSection = class(AbstractSection)
1060 function NamedSection.__init__(self, map, section, stype, ...)
1061 AbstractSection.__init__(self, map, stype, ...)
1062 Node._i18n(self, map.config, section, nil, ...)
1065 self.addremove = false
1067 -- Use defaults from UVL
1068 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1069 local vs = self.map:get_scheme(self.sectiontype)
1070 self.addremove = not vs.unique and not vs.required
1071 self.dynamic = vs.dynamic
1072 self.title = self.title or vs.title
1073 self.description = self.description or vs.descr
1076 self.template = "cbi/nsection"
1077 self.section = section
1080 function NamedSection.parse(self, novld)
1081 local s = self.section
1082 local active = self:cfgvalue(s)
1084 if self.addremove then
1085 local path = self.config.."."..s
1086 if active then -- Remove the section
1087 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1091 else -- Create and apply default values
1092 if self.map:formvalue("cbi.cns."..path) then
1100 AbstractSection.parse_dynamic(self, s)
1101 if self.map:submitstate() then
1104 if not novld and not self.override_scheme and self.map.scheme then
1105 _uvl_validate_section(self, s)
1108 AbstractSection.parse_optionals(self, s)
1110 if self.changed then
1118 TypedSection - A (set of) configuration section(s) defined by the type
1119 addremove: Defines whether the user can add/remove sections of this type
1120 anonymous: Allow creating anonymous sections
1121 validate: a validation function returning nil if the section is invalid
1123 TypedSection = class(AbstractSection)
1125 function TypedSection.__init__(self, map, type, ...)
1126 AbstractSection.__init__(self, map, type, ...)
1127 Node._i18n(self, map.config, type, nil, ...)
1129 self.template = "cbi/tsection"
1131 self.anonymous = false
1133 -- Use defaults from UVL
1134 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1135 local vs = self.map:get_scheme(self.sectiontype)
1136 self.addremove = not vs.unique and not vs.required
1137 self.dynamic = vs.dynamic
1138 self.anonymous = not vs.named
1139 self.title = self.title or vs.title
1140 self.description = self.description or vs.descr
1144 -- Return all matching UCI sections for this TypedSection
1145 function TypedSection.cfgsections(self)
1147 self.map.uci:foreach(self.map.config, self.sectiontype,
1149 if self:checkscope(section[".name"]) then
1150 table.insert(sections, section[".name"])
1157 -- Limits scope to sections that have certain option => value pairs
1158 function TypedSection.depends(self, option, value)
1159 table.insert(self.deps, {option=option, value=value})
1162 function TypedSection.parse(self, novld)
1163 if self.addremove then
1165 local crval = REMOVE_PREFIX .. self.config
1166 local name = self.map:formvaluetable(crval)
1167 for k,v in pairs(name) do
1168 if k:sub(-2) == ".x" then
1169 k = k:sub(1, #k - 2)
1171 if self:cfgvalue(k) and self:checkscope(k) then
1178 for i, k in ipairs(self:cfgsections()) do
1179 AbstractSection.parse_dynamic(self, k)
1180 if self.map:submitstate() then
1181 Node.parse(self, k, novld)
1183 if not novld and not self.override_scheme and self.map.scheme then
1184 _uvl_validate_section(self, k)
1187 AbstractSection.parse_optionals(self, k)
1190 if self.addremove then
1193 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1194 local name = self.map:formvalue(crval)
1195 if self.anonymous then
1197 created = self:create()
1201 -- Ignore if it already exists
1202 if self:cfgvalue(name) then
1206 name = self:checkscope(name)
1209 self.err_invalid = true
1212 if name and #name > 0 then
1213 created = self:create(name) and name
1215 self.invalid_cts = true
1222 AbstractSection.parse_optionals(self, created)
1226 if created or self.changed then
1231 -- Verifies scope of sections
1232 function TypedSection.checkscope(self, section)
1233 -- Check if we are not excluded
1234 if self.filter and not self:filter(section) then
1238 -- Check if at least one dependency is met
1239 if #self.deps > 0 and self:cfgvalue(section) then
1242 for k, v in ipairs(self.deps) do
1243 if self:cfgvalue(section)[v.option] == v.value then
1253 return self:validate(section)
1257 -- Dummy validate function
1258 function TypedSection.validate(self, section)
1264 AbstractValue - An abstract Value Type
1265 null: Value can be empty
1266 valid: A function returning the value if it is valid otherwise nil
1267 depends: A table of option => value pairs of which one must be true
1268 default: The default value
1269 size: The size of the input fields
1270 rmempty: Unset value if empty
1271 optional: This value is optional (see AbstractSection.optionals)
1273 AbstractValue = class(Node)
1275 function AbstractValue.__init__(self, map, section, option, ...)
1276 Node.__init__(self, ...)
1277 self.section = section
1278 self.option = option
1280 self.config = map.config
1281 self.tag_invalid = {}
1282 self.tag_missing = {}
1283 self.tag_reqerror = {}
1286 --self.cast = "string"
1288 self.track_missing = false
1292 self.optional = false
1295 function AbstractValue.prepare(self)
1296 -- Use defaults from UVL
1297 if not self.override_scheme
1298 and self.map:get_scheme(self.section.sectiontype, self.option) then
1299 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1300 if self.cast == nil then
1301 self.cast = (vs.type == "list") and "list" or "string"
1303 self.title = self.title or vs.title
1304 self.description = self.description or vs.descr
1305 if self.default == nil then
1306 self.default = vs.default
1309 if vs.depends and not self.override_dependencies then
1310 for i, deps in ipairs(vs.depends) do
1311 deps = _uvl_strip_remote_dependencies(deps)
1319 self.cast = self.cast or "string"
1322 -- Add a dependencie to another section field
1323 function AbstractValue.depends(self, field, value)
1325 if type(field) == "string" then
1332 table.insert(self.deps, {deps=deps, add=""})
1335 -- Generates the unique CBID
1336 function AbstractValue.cbid(self, section)
1337 return "cbid."..self.map.config.."."..section.."."..self.option
1340 -- Return whether this object should be created
1341 function AbstractValue.formcreated(self, section)
1342 local key = "cbi.opt."..self.config.."."..section
1343 return (self.map:formvalue(key) == self.option)
1346 -- Returns the formvalue for this object
1347 function AbstractValue.formvalue(self, section)
1348 return self.map:formvalue(self:cbid(section))
1351 function AbstractValue.additional(self, value)
1352 self.optional = value
1355 function AbstractValue.mandatory(self, value)
1356 self.rmempty = not value
1359 function AbstractValue.parse(self, section, novld)
1360 local fvalue = self:formvalue(section)
1361 local cvalue = self:cfgvalue(section)
1363 -- If favlue and cvalue are both tables and have the same content
1364 -- make them identical
1365 if type(fvalue) == "table" and type(cvalue) == "table" then
1366 local equal = #fvalue == #cvalue
1369 if cvalue[i] ~= fvalue[i] then
1379 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1380 fvalue = self:transform(self:validate(fvalue, section))
1381 if not fvalue and not novld then
1383 self.error[section] = "invalid"
1385 self.error = { [section] = "invalid" }
1387 if self.section.error then
1388 table.insert(self.section.error[section], "invalid")
1390 self.section.error = {[section] = {"invalid"}}
1392 self.map.save = false
1394 if fvalue and not (fvalue == cvalue) then
1395 if self:write(section, fvalue) then
1397 self.section.changed = true
1398 --luci.util.append(self.map.events, self.events)
1401 else -- Unset the UCI or error
1402 if self.rmempty or self.optional then
1403 if self:remove(section) then
1405 self.section.changed = true
1406 --luci.util.append(self.map.events, self.events)
1408 elseif cvalue ~= fvalue and not novld then
1409 self:write(section, fvalue or "")
1411 self.error[section] = "missing"
1413 self.error = { [section] = "missing" }
1415 self.map.save = false
1420 -- Render if this value exists or if it is mandatory
1421 function AbstractValue.render(self, s, scope)
1422 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1425 scope.cbid = self:cbid(s)
1426 scope.striptags = luci.util.striptags
1427 scope.pcdata = luci.util.pcdata
1429 scope.ifattr = function(cond,key,val)
1431 return string.format(
1432 ' %s="%s"', tostring(key),
1433 luci.util.pcdata(tostring( val
1435 or (type(self[key]) ~= "function" and self[key])
1443 scope.attr = function(...)
1444 return scope.ifattr( true, ... )
1447 Node.render(self, scope)
1451 -- Return the UCI value of this object
1452 function AbstractValue.cfgvalue(self, section)
1453 local value = self.map:get(section, self.option)
1456 elseif not self.cast or self.cast == type(value) then
1458 elseif self.cast == "string" then
1459 if type(value) == "table" then
1462 elseif self.cast == "table" then
1463 return luci.util.split(value, "%s+", nil, true)
1467 -- Validate the form value
1468 function AbstractValue.validate(self, value)
1472 AbstractValue.transform = AbstractValue.validate
1476 function AbstractValue.write(self, section, value)
1477 return self.map:set(section, self.option, value)
1481 function AbstractValue.remove(self, section)
1482 return self.map:del(section, self.option)
1489 Value - A one-line value
1490 maxlength: The maximum length
1492 Value = class(AbstractValue)
1494 function Value.__init__(self, ...)
1495 AbstractValue.__init__(self, ...)
1496 self.template = "cbi/value"
1501 function Value.value(self, key, val)
1503 table.insert(self.keylist, tostring(key))
1504 table.insert(self.vallist, tostring(val))
1508 -- DummyValue - This does nothing except being there
1509 DummyValue = class(AbstractValue)
1511 function DummyValue.__init__(self, ...)
1512 AbstractValue.__init__(self, ...)
1513 self.template = "cbi/dvalue"
1517 function DummyValue.cfgvalue(self, section)
1520 if type(self.value) == "function" then
1521 value = self:value(section)
1526 value = AbstractValue.cfgvalue(self, section)
1531 function DummyValue.parse(self)
1537 Flag - A flag being enabled or disabled
1539 Flag = class(AbstractValue)
1541 function Flag.__init__(self, ...)
1542 AbstractValue.__init__(self, ...)
1543 self.template = "cbi/fvalue"
1549 -- A flag can only have two states: set or unset
1550 function Flag.parse(self, section)
1551 local fvalue = self:formvalue(section)
1554 fvalue = self.enabled
1556 fvalue = self.disabled
1559 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1560 if not(fvalue == self:cfgvalue(section)) then
1561 self:write(section, fvalue)
1564 self:remove(section)
1571 ListValue - A one-line value predefined in a list
1572 widget: The widget that will be used (select, radio)
1574 ListValue = class(AbstractValue)
1576 function ListValue.__init__(self, ...)
1577 AbstractValue.__init__(self, ...)
1578 self.template = "cbi/lvalue"
1583 self.widget = "select"
1586 function ListValue.prepare(self, ...)
1587 AbstractValue.prepare(self, ...)
1588 if not self.override_scheme
1589 and self.map:get_scheme(self.section.sectiontype, self.option) then
1590 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1591 if self.value and vs.valuelist and not self.override_values then
1592 for k, v in ipairs(vs.valuelist) do
1594 if not self.override_dependencies
1595 and vs.enum_depends and vs.enum_depends[v.value] then
1596 for i, dep in ipairs(vs.enum_depends[v.value]) do
1597 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1600 self:value(v.value, v.title or v.value, unpack(deps))
1606 function ListValue.value(self, key, val, ...)
1607 if luci.util.contains(self.keylist, key) then
1612 table.insert(self.keylist, tostring(key))
1613 table.insert(self.vallist, tostring(val))
1615 for i, deps in ipairs({...}) do
1616 table.insert(self.deps, {add = "-"..key, deps=deps})
1620 function ListValue.validate(self, val)
1621 if luci.util.contains(self.keylist, val) then
1631 MultiValue - Multiple delimited values
1632 widget: The widget that will be used (select, checkbox)
1633 delimiter: The delimiter that will separate the values (default: " ")
1635 MultiValue = class(AbstractValue)
1637 function MultiValue.__init__(self, ...)
1638 AbstractValue.__init__(self, ...)
1639 self.template = "cbi/mvalue"
1644 self.widget = "checkbox"
1645 self.delimiter = " "
1648 function MultiValue.render(self, ...)
1649 if self.widget == "select" and not self.size then
1650 self.size = #self.vallist
1653 AbstractValue.render(self, ...)
1656 function MultiValue.value(self, key, val)
1657 if luci.util.contains(self.keylist, key) then
1662 table.insert(self.keylist, tostring(key))
1663 table.insert(self.vallist, tostring(val))
1666 function MultiValue.valuelist(self, section)
1667 local val = self:cfgvalue(section)
1669 if not(type(val) == "string") then
1673 return luci.util.split(val, self.delimiter)
1676 function MultiValue.validate(self, val)
1677 val = (type(val) == "table") and val or {val}
1681 for i, value in ipairs(val) do
1682 if luci.util.contains(self.keylist, value) then
1683 result = result and (result .. self.delimiter .. value) or value
1691 StaticList = class(MultiValue)
1693 function StaticList.__init__(self, ...)
1694 MultiValue.__init__(self, ...)
1696 self.valuelist = self.cfgvalue
1698 if not self.override_scheme
1699 and self.map:get_scheme(self.section.sectiontype, self.option) then
1700 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1701 if self.value and vs.values and not self.override_values then
1702 for k, v in pairs(vs.values) do
1709 function StaticList.validate(self, value)
1710 value = (type(value) == "table") and value or {value}
1713 for i, v in ipairs(value) do
1714 if luci.util.contains(self.keylist, v) then
1715 table.insert(valid, v)
1722 DynamicList = class(AbstractValue)
1724 function DynamicList.__init__(self, ...)
1725 AbstractValue.__init__(self, ...)
1726 self.template = "cbi/dynlist"
1732 function DynamicList.value(self, key, val)
1734 table.insert(self.keylist, tostring(key))
1735 table.insert(self.vallist, tostring(val))
1738 function DynamicList.write(self, ...)
1739 self.map.proceed = true
1740 return AbstractValue.write(self, ...)
1743 function DynamicList.formvalue(self, section)
1744 local value = AbstractValue.formvalue(self, section)
1745 value = (type(value) == "table") and value or {value}
1748 for i, v in ipairs(value) do
1750 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1751 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1752 table.insert(valid, v)
1761 TextValue - A multi-line value
1764 TextValue = class(AbstractValue)
1766 function TextValue.__init__(self, ...)
1767 AbstractValue.__init__(self, ...)
1768 self.template = "cbi/tvalue"
1774 Button = class(AbstractValue)
1776 function Button.__init__(self, ...)
1777 AbstractValue.__init__(self, ...)
1778 self.template = "cbi/button"
1779 self.inputstyle = nil
1784 FileUpload = class(AbstractValue)
1786 function FileUpload.__init__(self, ...)
1787 AbstractValue.__init__(self, ...)
1788 self.template = "cbi/upload"
1789 if not self.map.upload_fields then
1790 self.map.upload_fields = { self }
1792 self.map.upload_fields[#self.map.upload_fields+1] = self
1796 function FileUpload.formcreated(self, section)
1797 return AbstractValue.formcreated(self, section) or
1798 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1799 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1802 function FileUpload.cfgvalue(self, section)
1803 local val = AbstractValue.cfgvalue(self, section)
1804 if val and fs.access(val) then
1810 function FileUpload.formvalue(self, section)
1811 local val = AbstractValue.formvalue(self, section)
1813 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1814 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1824 function FileUpload.remove(self, section)
1825 local val = AbstractValue.formvalue(self, section)
1826 if val and fs.access(val) then fs.unlink(val) end
1827 return AbstractValue.remove(self, section)
1831 FileBrowser = class(AbstractValue)
1833 function FileBrowser.__init__(self, ...)
1834 AbstractValue.__init__(self, ...)
1835 self.template = "cbi/browser"