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('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_hook(self, hook)
243 if type(self[hook]) == "function" then
244 return self[hook](self)
248 function Node._run_hooks(self, ...)
251 for _, f in ipairs(arg) do
252 if type(self[f]) == "function" then
261 function Node.prepare(self, ...)
262 for k, child in ipairs(self.children) do
267 -- Append child nodes
268 function Node.append(self, obj)
269 table.insert(self.children, obj)
272 -- Parse this node and its children
273 function Node.parse(self, ...)
274 for k, child in ipairs(self.children) do
280 function Node.render(self, scope)
284 luci.template.render(self.template, scope)
287 -- Render the children
288 function Node.render_children(self, ...)
289 for k, node in ipairs(self.children) do
296 A simple template element
298 Template = class(Node)
300 function Template.__init__(self, template)
302 self.template = template
305 function Template.render(self)
306 luci.template.render(self.template, {self=self})
309 function Template.parse(self, readinput)
310 self.readinput = (readinput ~= false)
311 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
316 Map - A map describing a configuration file
320 function Map.__init__(self, config, ...)
321 Node.__init__(self, ...)
322 Node._i18n(self, config, nil, nil, ...)
325 self.parsechain = {self.config}
326 self.template = "cbi/map"
327 self.apply_on_parse = nil
328 self.readinput = true
332 self.uci = uci.cursor()
337 if not self.uci:load(self.config) then
338 error("Unable to read UCI data: " .. self.config)
341 self.validator = luci.uvl.UVL()
342 self.scheme = self.validator:get_scheme(self.config)
346 function Map.formvalue(self, key)
347 return self.readinput and luci.http.formvalue(key)
350 function Map.formvaluetable(self, key)
351 return self.readinput and luci.http.formvaluetable(key) or {}
354 function Map.get_scheme(self, sectiontype, option)
356 return self.scheme and self.scheme.sections[sectiontype]
358 return self.scheme and self.scheme.variables[sectiontype]
359 and self.scheme.variables[sectiontype][option]
363 function Map.submitstate(self)
364 return self:formvalue("cbi.submit")
367 -- Chain foreign config
368 function Map.chain(self, config)
369 table.insert(self.parsechain, config)
372 function Map.state_handler(self, state)
376 -- Use optimized UCI writing
377 function Map.parse(self, readinput, ...)
378 self.readinput = (readinput ~= false)
379 self:_run_hooks("on_parse")
381 if self:formvalue("cbi.skip") then
382 self.state = FORM_SKIP
383 return self:state_handler(self.state)
386 Node.parse(self, ...)
389 for i, config in ipairs(self.parsechain) do
390 self.uci:save(config)
392 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
393 self:_run_hooks("on_before_commit")
394 for i, config in ipairs(self.parsechain) do
395 self.uci:commit(config)
397 -- Refresh data because commit changes section names
398 self.uci:load(config)
400 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
401 if self.apply_on_parse then
402 self.uci:apply(self.parsechain)
403 self:_run_hooks("on_apply", "on_after_apply")
405 self._apply = function()
406 local cmd = self.uci:apply(self.parsechain, true)
412 Node.parse(self, true)
415 for i, config in ipairs(self.parsechain) do
416 self.uci:unload(config)
418 if type(self.commit_handler) == "function" then
419 self:commit_handler(self:submitstate())
423 if self:submitstate() then
424 if not self.save then
425 self.state = FORM_INVALID
426 elseif self.proceed then
427 self.state = FORM_PROCEED
429 self.state = self.changed and FORM_CHANGED or FORM_VALID
432 self.state = FORM_NODATA
435 return self:state_handler(self.state)
438 function Map.render(self, ...)
439 self:_run_hooks("on_init")
440 Node.render(self, ...)
442 local fp = self._apply()
445 self:_run_hooks("on_apply")
449 -- Creates a child section
450 function Map.section(self, class, ...)
451 if instanceof(class, AbstractSection) then
452 local obj = class(self, ...)
456 error("class must be a descendent of AbstractSection")
461 function Map.add(self, sectiontype)
462 return self.uci:add(self.config, sectiontype)
466 function Map.set(self, section, option, value)
468 return self.uci:set(self.config, section, option, value)
470 return self.uci:set(self.config, section, value)
475 function Map.del(self, section, option)
477 return self.uci:delete(self.config, section, option)
479 return self.uci:delete(self.config, section)
484 function Map.get(self, section, option)
486 return self.uci:get_all(self.config)
488 return self.uci:get(self.config, section, option)
490 return self.uci:get_all(self.config, section)
497 Compound = class(Node)
499 function Compound.__init__(self, ...)
501 self.template = "cbi/compound"
502 self.children = {...}
505 function Compound.populate_delegator(self, delegator)
506 for _, v in ipairs(self.children) do
507 v.delegator = delegator
511 function Compound.parse(self, ...)
512 local cstate, state = 0
514 for k, child in ipairs(self.children) do
515 cstate = child:parse(...)
516 state = (not state or cstate < state) and cstate or state
524 Delegator - Node controller
526 Delegator = class(Node)
527 function Delegator.__init__(self, ...)
528 Node.__init__(self, ...)
530 self.defaultpath = {}
531 self.pageaction = false
532 self.readinput = true
533 self.allow_reset = false
534 self.allow_cancel = false
535 self.allow_back = false
536 self.allow_finish = false
537 self.template = "cbi/delegator"
540 function Delegator.set(self, name, node)
541 assert(not self.nodes[name], "Duplicate entry")
543 self.nodes[name] = node
546 function Delegator.add(self, name, node)
547 node = self:set(name, node)
548 self.defaultpath[#self.defaultpath+1] = name
551 function Delegator.insert_after(self, name, after)
552 local n = #self.chain + 1
553 for k, v in ipairs(self.chain) do
559 table.insert(self.chain, n, name)
562 function Delegator.set_route(self, ...)
563 local n, chain, route = 0, self.chain, {...}
565 if chain[i] == self.current then
574 for i = n + 1, #chain do
579 function Delegator.get(self, name)
580 local node = self.nodes[name]
582 if type(node) == "string" then
583 node = load(node, name)
586 if type(node) == "table" and getmetatable(node) == nil then
587 node = Compound(unpack(node))
593 function Delegator.parse(self, ...)
594 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
595 if self:_run_hooks("on_cancel") then
600 if not Map.formvalue(self, "cbi.delg.current") then
601 self:_run_hooks("on_init")
605 self.chain = self.chain or self:get_chain()
606 self.current = self.current or self:get_active()
607 self.active = self.active or self:get(self.current)
608 assert(self.active, "Invalid state")
610 local stat = FORM_DONE
611 if type(self.active) ~= "function" then
612 self.active:populate_delegator(self)
613 stat = self.active:parse()
618 if stat > FORM_PROCEED then
619 if Map.formvalue(self, "cbi.delg.back") then
620 newcurrent = self:get_prev(self.current)
622 newcurrent = self:get_next(self.current)
624 elseif stat < FORM_PROCEED then
629 if not Map.formvalue(self, "cbi.submit") then
631 elseif stat > FORM_PROCEED
632 and (not newcurrent or not self:get(newcurrent)) then
633 return self:_run_hook("on_done") or FORM_DONE
635 self.current = newcurrent or self.current
636 self.active = self:get(self.current)
637 if type(self.active) ~= "function" then
638 self.active:populate_delegator(self)
639 local stat = self.active:parse(false)
640 if stat == FORM_SKIP then
641 return self:parse(...)
646 return self:parse(...)
651 function Delegator.get_next(self, state)
652 for k, v in ipairs(self.chain) do
654 return self.chain[k+1]
659 function Delegator.get_prev(self, state)
660 for k, v in ipairs(self.chain) do
662 return self.chain[k-1]
667 function Delegator.get_chain(self)
668 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
669 return type(x) == "table" and x or {x}
672 function Delegator.get_active(self)
673 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
681 Page.__init__ = Node.__init__
682 Page.parse = function() end
686 SimpleForm - A Simple non-UCI form
688 SimpleForm = class(Node)
690 function SimpleForm.__init__(self, config, title, description, data)
691 Node.__init__(self, title, description)
693 self.data = data or {}
694 self.template = "cbi/simpleform"
696 self.pageaction = false
697 self.readinput = true
700 SimpleForm.formvalue = Map.formvalue
701 SimpleForm.formvaluetable = Map.formvaluetable
703 function SimpleForm.parse(self, readinput, ...)
704 self.readinput = (readinput ~= false)
706 if self:formvalue("cbi.skip") then
710 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
714 if self:submitstate() then
715 Node.parse(self, 1, ...)
719 for k, j in ipairs(self.children) do
720 for i, v in ipairs(j.children) do
722 and (not v.tag_missing or not v.tag_missing[1])
723 and (not v.tag_invalid or not v.tag_invalid[1])
729 not self:submitstate() and FORM_NODATA
730 or valid and FORM_VALID
733 self.dorender = not self.handle
735 local nrender, nstate = self:handle(state, self.data)
736 self.dorender = self.dorender or (nrender ~= false)
737 state = nstate or state
742 function SimpleForm.render(self, ...)
743 if self.dorender then
744 Node.render(self, ...)
748 function SimpleForm.submitstate(self)
749 return self:formvalue("cbi.submit")
752 function SimpleForm.section(self, class, ...)
753 if instanceof(class, AbstractSection) then
754 local obj = class(self, ...)
758 error("class must be a descendent of AbstractSection")
762 -- Creates a child field
763 function SimpleForm.field(self, class, ...)
765 for k, v in ipairs(self.children) do
766 if instanceof(v, SimpleSection) then
772 section = self:section(SimpleSection)
775 if instanceof(class, AbstractValue) then
776 local obj = class(self, section, ...)
777 obj.track_missing = true
781 error("class must be a descendent of AbstractValue")
785 function SimpleForm.set(self, section, option, value)
786 self.data[option] = value
790 function SimpleForm.del(self, section, option)
791 self.data[option] = nil
795 function SimpleForm.get(self, section, option)
796 return self.data[option]
800 function SimpleForm.get_scheme()
805 Form = class(SimpleForm)
807 function Form.__init__(self, ...)
808 SimpleForm.__init__(self, ...)
816 AbstractSection = class(Node)
818 function AbstractSection.__init__(self, map, sectiontype, ...)
819 Node.__init__(self, ...)
820 self.sectiontype = sectiontype
822 self.config = map.config
827 self.tag_invalid = {}
828 self.tag_deperror = {}
832 self.addremove = false
836 -- Define a tab for the section
837 function AbstractSection.tab(self, tab, title, desc)
838 self.tabs = self.tabs or { }
839 self.tab_names = self.tab_names or { }
841 self.tab_names[#self.tab_names+1] = tab
849 -- Appends a new option
850 function AbstractSection.option(self, class, option, ...)
851 -- Autodetect from UVL
852 if class == true and self.map:get_scheme(self.sectiontype, option) then
853 local vs = self.map:get_scheme(self.sectiontype, option)
854 if vs.type == "boolean" then
856 elseif vs.type == "list" then
858 elseif vs.type == "enum" or vs.type == "reference" then
865 if instanceof(class, AbstractValue) then
866 local obj = class(self.map, self, option, ...)
868 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
871 self.fields[option] = obj
873 elseif class == true then
874 error("No valid class was given and autodetection failed.")
876 error("class must be a descendant of AbstractValue")
880 -- Appends a new tabbed option
881 function AbstractSection.taboption(self, tab, ...)
883 assert(tab and self.tabs and self.tabs[tab],
884 "Cannot assign option to not existing tab %q" % tostring(tab))
886 local l = self.tabs[tab].childs
887 local o = AbstractSection.option(self, ...)
889 if o then l[#l+1] = o end
894 -- Render a single tab
895 function AbstractSection.render_tab(self, tab, ...)
897 assert(tab and self.tabs and self.tabs[tab],
898 "Cannot render not existing tab %q" % tostring(tab))
900 for _, node in ipairs(self.tabs[tab].childs) do
905 -- Parse optional options
906 function AbstractSection.parse_optionals(self, section)
907 if not self.optional then
911 self.optionals[section] = {}
913 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
914 for k,v in ipairs(self.children) do
915 if v.optional and not v:cfgvalue(section) then
916 if field == v.option then
918 self.map.proceed = true
920 table.insert(self.optionals[section], v)
925 if field and #field > 0 and self.dynamic then
926 self:add_dynamic(field)
930 -- Add a dynamic option
931 function AbstractSection.add_dynamic(self, field, optional)
932 local o = self:option(Value, field, field)
933 o.optional = optional
936 -- Parse all dynamic options
937 function AbstractSection.parse_dynamic(self, section)
938 if not self.dynamic then
942 local arr = luci.util.clone(self:cfgvalue(section))
943 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
944 for k, v in pairs(form) do
948 for key,val in pairs(arr) do
951 for i,c in ipairs(self.children) do
952 if c.option == key then
957 if create and key:sub(1, 1) ~= "." then
958 self.map.proceed = true
959 self:add_dynamic(key, true)
964 -- Returns the section's UCI table
965 function AbstractSection.cfgvalue(self, section)
966 return self.map:get(section)
970 function AbstractSection.push_events(self)
971 --luci.util.append(self.map.events, self.events)
972 self.map.changed = true
975 -- Removes the section
976 function AbstractSection.remove(self, section)
977 self.map.proceed = true
978 return self.map:del(section)
981 -- Creates the section
982 function AbstractSection.create(self, section)
986 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
988 section = self.map:add(self.sectiontype)
993 for k,v in pairs(self.children) do
995 self.map:set(section, v.option, v.default)
999 for k,v in pairs(self.defaults) do
1000 self.map:set(section, k, v)
1004 self.map.proceed = true
1010 SimpleSection = class(AbstractSection)
1012 function SimpleSection.__init__(self, form, ...)
1013 AbstractSection.__init__(self, form, nil, ...)
1014 self.template = "cbi/nullsection"
1018 Table = class(AbstractSection)
1020 function Table.__init__(self, form, data, ...)
1021 local datasource = {}
1023 datasource.config = "table"
1024 self.data = data or {}
1026 datasource.formvalue = Map.formvalue
1027 datasource.formvaluetable = Map.formvaluetable
1028 datasource.readinput = true
1030 function datasource.get(self, section, option)
1031 return tself.data[section] and tself.data[section][option]
1034 function datasource.submitstate(self)
1035 return Map.formvalue(self, "cbi.submit")
1038 function datasource.del(...)
1042 function datasource.get_scheme()
1046 AbstractSection.__init__(self, datasource, "table", ...)
1047 self.template = "cbi/tblsection"
1048 self.rowcolors = true
1049 self.anonymous = true
1052 function Table.parse(self, readinput)
1053 self.map.readinput = (readinput ~= false)
1054 for i, k in ipairs(self:cfgsections()) do
1055 if self.map:submitstate() then
1061 function Table.cfgsections(self)
1064 for i, v in luci.util.kspairs(self.data) do
1065 table.insert(sections, i)
1071 function Table.update(self, data)
1078 NamedSection - A fixed configuration section defined by its name
1080 NamedSection = class(AbstractSection)
1082 function NamedSection.__init__(self, map, section, stype, ...)
1083 AbstractSection.__init__(self, map, stype, ...)
1084 Node._i18n(self, map.config, section, nil, ...)
1087 self.addremove = false
1089 -- Use defaults from UVL
1090 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1091 local vs = self.map:get_scheme(self.sectiontype)
1092 self.addremove = not vs.unique and not vs.required
1093 self.dynamic = vs.dynamic
1094 self.title = self.title or vs.title
1095 self.description = self.description or vs.descr
1098 self.template = "cbi/nsection"
1099 self.section = section
1102 function NamedSection.parse(self, novld)
1103 local s = self.section
1104 local active = self:cfgvalue(s)
1106 if self.addremove then
1107 local path = self.config.."."..s
1108 if active then -- Remove the section
1109 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1113 else -- Create and apply default values
1114 if self.map:formvalue("cbi.cns."..path) then
1122 AbstractSection.parse_dynamic(self, s)
1123 if self.map:submitstate() then
1126 if not novld and not self.override_scheme and self.map.scheme then
1127 _uvl_validate_section(self, s)
1130 AbstractSection.parse_optionals(self, s)
1132 if self.changed then
1140 TypedSection - A (set of) configuration section(s) defined by the type
1141 addremove: Defines whether the user can add/remove sections of this type
1142 anonymous: Allow creating anonymous sections
1143 validate: a validation function returning nil if the section is invalid
1145 TypedSection = class(AbstractSection)
1147 function TypedSection.__init__(self, map, type, ...)
1148 AbstractSection.__init__(self, map, type, ...)
1149 Node._i18n(self, map.config, type, nil, ...)
1151 self.template = "cbi/tsection"
1153 self.anonymous = false
1155 -- Use defaults from UVL
1156 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1157 local vs = self.map:get_scheme(self.sectiontype)
1158 self.addremove = not vs.unique and not vs.required
1159 self.dynamic = vs.dynamic
1160 self.anonymous = not vs.named
1161 self.title = self.title or vs.title
1162 self.description = self.description or vs.descr
1166 -- Return all matching UCI sections for this TypedSection
1167 function TypedSection.cfgsections(self)
1169 self.map.uci:foreach(self.map.config, self.sectiontype,
1171 if self:checkscope(section[".name"]) then
1172 table.insert(sections, section[".name"])
1179 -- Limits scope to sections that have certain option => value pairs
1180 function TypedSection.depends(self, option, value)
1181 table.insert(self.deps, {option=option, value=value})
1184 function TypedSection.parse(self, novld)
1185 if self.addremove then
1187 local crval = REMOVE_PREFIX .. self.config
1188 local name = self.map:formvaluetable(crval)
1189 for k,v in pairs(name) do
1190 if k:sub(-2) == ".x" then
1191 k = k:sub(1, #k - 2)
1193 if self:cfgvalue(k) and self:checkscope(k) then
1200 for i, k in ipairs(self:cfgsections()) do
1201 AbstractSection.parse_dynamic(self, k)
1202 if self.map:submitstate() then
1203 Node.parse(self, k, novld)
1205 if not novld and not self.override_scheme and self.map.scheme then
1206 _uvl_validate_section(self, k)
1209 AbstractSection.parse_optionals(self, k)
1212 if self.addremove then
1215 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1216 local name = self.map:formvalue(crval)
1217 if self.anonymous then
1219 created = self:create()
1223 -- Ignore if it already exists
1224 if self:cfgvalue(name) then
1228 name = self:checkscope(name)
1231 self.err_invalid = true
1234 if name and #name > 0 then
1235 created = self:create(name) and name
1237 self.invalid_cts = true
1244 AbstractSection.parse_optionals(self, created)
1248 if created or self.changed then
1253 -- Verifies scope of sections
1254 function TypedSection.checkscope(self, section)
1255 -- Check if we are not excluded
1256 if self.filter and not self:filter(section) then
1260 -- Check if at least one dependency is met
1261 if #self.deps > 0 and self:cfgvalue(section) then
1264 for k, v in ipairs(self.deps) do
1265 if self:cfgvalue(section)[v.option] == v.value then
1275 return self:validate(section)
1279 -- Dummy validate function
1280 function TypedSection.validate(self, section)
1286 AbstractValue - An abstract Value Type
1287 null: Value can be empty
1288 valid: A function returning the value if it is valid otherwise nil
1289 depends: A table of option => value pairs of which one must be true
1290 default: The default value
1291 size: The size of the input fields
1292 rmempty: Unset value if empty
1293 optional: This value is optional (see AbstractSection.optionals)
1295 AbstractValue = class(Node)
1297 function AbstractValue.__init__(self, map, section, option, ...)
1298 Node.__init__(self, ...)
1299 self.section = section
1300 self.option = option
1302 self.config = map.config
1303 self.tag_invalid = {}
1304 self.tag_missing = {}
1305 self.tag_reqerror = {}
1309 --self.cast = "string"
1311 self.track_missing = false
1315 self.optional = false
1318 function AbstractValue.prepare(self)
1319 -- Use defaults from UVL
1320 if not self.override_scheme
1321 and self.map:get_scheme(self.section.sectiontype, self.option) then
1322 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1323 if self.cast == nil then
1324 self.cast = (vs.type == "list") and "list" or "string"
1326 self.title = self.title or vs.title
1327 self.description = self.description or vs.descr
1328 if self.default == nil then
1329 self.default = vs.default
1332 if vs.depends and not self.override_dependencies then
1333 for i, deps in ipairs(vs.depends) do
1334 deps = _uvl_strip_remote_dependencies(deps)
1342 self.cast = self.cast or "string"
1345 -- Add a dependencie to another section field
1346 function AbstractValue.depends(self, field, value)
1348 if type(field) == "string" then
1355 table.insert(self.deps, {deps=deps, add=""})
1358 -- Generates the unique CBID
1359 function AbstractValue.cbid(self, section)
1360 return "cbid."..self.map.config.."."..section.."."..self.option
1363 -- Return whether this object should be created
1364 function AbstractValue.formcreated(self, section)
1365 local key = "cbi.opt."..self.config.."."..section
1366 return (self.map:formvalue(key) == self.option)
1369 -- Returns the formvalue for this object
1370 function AbstractValue.formvalue(self, section)
1371 return self.map:formvalue(self:cbid(section))
1374 function AbstractValue.additional(self, value)
1375 self.optional = value
1378 function AbstractValue.mandatory(self, value)
1379 self.rmempty = not value
1382 function AbstractValue.parse(self, section, novld)
1383 local fvalue = self:formvalue(section)
1384 local cvalue = self:cfgvalue(section)
1386 -- If favlue and cvalue are both tables and have the same content
1387 -- make them identical
1388 if type(fvalue) == "table" and type(cvalue) == "table" then
1389 local equal = #fvalue == #cvalue
1392 if cvalue[i] ~= fvalue[i] then
1402 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1403 fvalue = self:transform(self:validate(fvalue, section))
1404 if not fvalue and not novld then
1406 self.error[section] = "invalid"
1408 self.error = { [section] = "invalid" }
1410 if self.section.error then
1411 table.insert(self.section.error[section], "invalid")
1413 self.section.error = {[section] = {"invalid"}}
1415 self.map.save = false
1417 if fvalue and not (fvalue == cvalue) then
1418 if self:write(section, fvalue) then
1420 self.section.changed = true
1421 --luci.util.append(self.map.events, self.events)
1424 else -- Unset the UCI or error
1425 if self.rmempty or self.optional then
1426 if self:remove(section) then
1428 self.section.changed = true
1429 --luci.util.append(self.map.events, self.events)
1431 elseif cvalue ~= fvalue and not novld then
1432 self:write(section, fvalue or "")
1434 self.error[section] = "missing"
1436 self.error = { [section] = "missing" }
1438 self.map.save = false
1443 -- Render if this value exists or if it is mandatory
1444 function AbstractValue.render(self, s, scope)
1445 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1448 scope.cbid = self:cbid(s)
1449 scope.striptags = luci.util.striptags
1450 scope.pcdata = luci.util.pcdata
1452 scope.ifattr = function(cond,key,val)
1454 return string.format(
1455 ' %s="%s"', tostring(key),
1456 luci.util.pcdata(tostring( val
1458 or (type(self[key]) ~= "function" and self[key])
1466 scope.attr = function(...)
1467 return scope.ifattr( true, ... )
1470 Node.render(self, scope)
1474 -- Return the UCI value of this object
1475 function AbstractValue.cfgvalue(self, section)
1476 local value = self.map:get(section, self.option)
1479 elseif not self.cast or self.cast == type(value) then
1481 elseif self.cast == "string" then
1482 if type(value) == "table" then
1485 elseif self.cast == "table" then
1486 return luci.util.split(value, "%s+", nil, true)
1490 -- Validate the form value
1491 function AbstractValue.validate(self, value)
1495 AbstractValue.transform = AbstractValue.validate
1499 function AbstractValue.write(self, section, value)
1500 return self.map:set(section, self.option, value)
1504 function AbstractValue.remove(self, section)
1505 return self.map:del(section, self.option)
1512 Value - A one-line value
1513 maxlength: The maximum length
1515 Value = class(AbstractValue)
1517 function Value.__init__(self, ...)
1518 AbstractValue.__init__(self, ...)
1519 self.template = "cbi/value"
1524 function Value.value(self, key, val)
1526 table.insert(self.keylist, tostring(key))
1527 table.insert(self.vallist, tostring(val))
1531 -- DummyValue - This does nothing except being there
1532 DummyValue = class(AbstractValue)
1534 function DummyValue.__init__(self, ...)
1535 AbstractValue.__init__(self, ...)
1536 self.template = "cbi/dvalue"
1540 function DummyValue.cfgvalue(self, section)
1543 if type(self.value) == "function" then
1544 value = self:value(section)
1549 value = AbstractValue.cfgvalue(self, section)
1554 function DummyValue.parse(self)
1560 Flag - A flag being enabled or disabled
1562 Flag = class(AbstractValue)
1564 function Flag.__init__(self, ...)
1565 AbstractValue.__init__(self, ...)
1566 self.template = "cbi/fvalue"
1572 -- A flag can only have two states: set or unset
1573 function Flag.parse(self, section)
1574 local fvalue = self:formvalue(section)
1577 fvalue = self.enabled
1579 fvalue = self.disabled
1582 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1583 if not(fvalue == self:cfgvalue(section)) then
1584 self:write(section, fvalue)
1587 self:remove(section)
1594 ListValue - A one-line value predefined in a list
1595 widget: The widget that will be used (select, radio)
1597 ListValue = class(AbstractValue)
1599 function ListValue.__init__(self, ...)
1600 AbstractValue.__init__(self, ...)
1601 self.template = "cbi/lvalue"
1606 self.widget = "select"
1609 function ListValue.prepare(self, ...)
1610 AbstractValue.prepare(self, ...)
1611 if not self.override_scheme
1612 and self.map:get_scheme(self.section.sectiontype, self.option) then
1613 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1614 if self.value and vs.valuelist and not self.override_values then
1615 for k, v in ipairs(vs.valuelist) do
1617 if not self.override_dependencies
1618 and vs.enum_depends and vs.enum_depends[v.value] then
1619 for i, dep in ipairs(vs.enum_depends[v.value]) do
1620 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1623 self:value(v.value, v.title or v.value, unpack(deps))
1629 function ListValue.value(self, key, val, ...)
1630 if luci.util.contains(self.keylist, key) then
1635 table.insert(self.keylist, tostring(key))
1636 table.insert(self.vallist, tostring(val))
1638 for i, deps in ipairs({...}) do
1639 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1643 function ListValue.validate(self, val)
1644 if luci.util.contains(self.keylist, val) then
1654 MultiValue - Multiple delimited values
1655 widget: The widget that will be used (select, checkbox)
1656 delimiter: The delimiter that will separate the values (default: " ")
1658 MultiValue = class(AbstractValue)
1660 function MultiValue.__init__(self, ...)
1661 AbstractValue.__init__(self, ...)
1662 self.template = "cbi/mvalue"
1667 self.widget = "checkbox"
1668 self.delimiter = " "
1671 function MultiValue.render(self, ...)
1672 if self.widget == "select" and not self.size then
1673 self.size = #self.vallist
1676 AbstractValue.render(self, ...)
1679 function MultiValue.value(self, key, val)
1680 if luci.util.contains(self.keylist, key) then
1685 table.insert(self.keylist, tostring(key))
1686 table.insert(self.vallist, tostring(val))
1689 function MultiValue.valuelist(self, section)
1690 local val = self:cfgvalue(section)
1692 if not(type(val) == "string") then
1696 return luci.util.split(val, self.delimiter)
1699 function MultiValue.validate(self, val)
1700 val = (type(val) == "table") and val or {val}
1704 for i, value in ipairs(val) do
1705 if luci.util.contains(self.keylist, value) then
1706 result = result and (result .. self.delimiter .. value) or value
1714 StaticList = class(MultiValue)
1716 function StaticList.__init__(self, ...)
1717 MultiValue.__init__(self, ...)
1719 self.valuelist = self.cfgvalue
1721 if not self.override_scheme
1722 and self.map:get_scheme(self.section.sectiontype, self.option) then
1723 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1724 if self.value and vs.values and not self.override_values then
1725 for k, v in pairs(vs.values) do
1732 function StaticList.validate(self, value)
1733 value = (type(value) == "table") and value or {value}
1736 for i, v in ipairs(value) do
1737 if luci.util.contains(self.keylist, v) then
1738 table.insert(valid, v)
1745 DynamicList = class(AbstractValue)
1747 function DynamicList.__init__(self, ...)
1748 AbstractValue.__init__(self, ...)
1749 self.template = "cbi/dynlist"
1755 function DynamicList.value(self, key, val)
1757 table.insert(self.keylist, tostring(key))
1758 table.insert(self.vallist, tostring(val))
1761 function DynamicList.write(self, ...)
1762 self.map.proceed = true
1763 return AbstractValue.write(self, ...)
1766 function DynamicList.formvalue(self, section)
1767 local value = AbstractValue.formvalue(self, section)
1768 value = (type(value) == "table") and value or {value}
1771 for i, v in ipairs(value) do
1773 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1774 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1775 table.insert(valid, v)
1784 TextValue - A multi-line value
1787 TextValue = class(AbstractValue)
1789 function TextValue.__init__(self, ...)
1790 AbstractValue.__init__(self, ...)
1791 self.template = "cbi/tvalue"
1797 Button = class(AbstractValue)
1799 function Button.__init__(self, ...)
1800 AbstractValue.__init__(self, ...)
1801 self.template = "cbi/button"
1802 self.inputstyle = nil
1807 FileUpload = class(AbstractValue)
1809 function FileUpload.__init__(self, ...)
1810 AbstractValue.__init__(self, ...)
1811 self.template = "cbi/upload"
1812 if not self.map.upload_fields then
1813 self.map.upload_fields = { self }
1815 self.map.upload_fields[#self.map.upload_fields+1] = self
1819 function FileUpload.formcreated(self, section)
1820 return AbstractValue.formcreated(self, section) or
1821 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1822 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1825 function FileUpload.cfgvalue(self, section)
1826 local val = AbstractValue.cfgvalue(self, section)
1827 if val and fs.access(val) then
1833 function FileUpload.formvalue(self, section)
1834 local val = AbstractValue.formvalue(self, section)
1836 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1837 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1847 function FileUpload.remove(self, section)
1848 local val = AbstractValue.formvalue(self, section)
1849 if val and fs.access(val) then fs.unlink(val) end
1850 return AbstractValue.remove(self, section)
1854 FileBrowser = class(AbstractValue)
1856 function FileBrowser.__init__(self, ...)
1857 AbstractValue.__init__(self, ...)
1858 self.template = "cbi/browser"