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 not newcurrent or not self:get(newcurrent) then
618 self:_run_hooks("on_done")
621 self.current = newcurrent
622 self.active = self:get(self.current)
623 if type(self.active) ~= "function" then
624 self.active:parse(false)
627 return self:parse(...)
632 function Delegator.get_next(self, state)
633 for k, v in ipairs(self.chain) do
635 return self.chain[k+1]
640 function Delegator.get_prev(self, state)
641 for k, v in ipairs(self.chain) do
643 return self.chain[k-1]
648 function Delegator.get_chain(self)
649 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
650 return type(x) == "table" and x or {x}
653 function Delegator.get_active(self)
654 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
662 Page.__init__ = Node.__init__
663 Page.parse = function() end
667 SimpleForm - A Simple non-UCI form
669 SimpleForm = class(Node)
671 function SimpleForm.__init__(self, config, title, description, data)
672 Node.__init__(self, title, description)
674 self.data = data or {}
675 self.template = "cbi/simpleform"
677 self.pageaction = false
678 self.readinput = true
681 SimpleForm.formvalue = Map.formvalue
682 SimpleForm.formvaluetable = Map.formvaluetable
684 function SimpleForm.parse(self, readinput, ...)
685 self.readinput = (readinput ~= false)
687 if self:formvalue("cbi.skip") then
691 if self:submitstate() then
692 Node.parse(self, 1, ...)
696 for k, j in ipairs(self.children) do
697 for i, v in ipairs(j.children) do
699 and (not v.tag_missing or not v.tag_missing[1])
700 and (not v.tag_invalid or not v.tag_invalid[1])
706 not self:submitstate() and FORM_NODATA
707 or valid and FORM_VALID
710 self.dorender = not self.handle
712 local nrender, nstate = self:handle(state, self.data)
713 self.dorender = self.dorender or (nrender ~= false)
714 state = nstate or state
719 function SimpleForm.render(self, ...)
720 if self.dorender then
721 Node.render(self, ...)
725 function SimpleForm.submitstate(self)
726 return self:formvalue("cbi.submit")
729 function SimpleForm.section(self, class, ...)
730 if instanceof(class, AbstractSection) then
731 local obj = class(self, ...)
735 error("class must be a descendent of AbstractSection")
739 -- Creates a child field
740 function SimpleForm.field(self, class, ...)
742 for k, v in ipairs(self.children) do
743 if instanceof(v, SimpleSection) then
749 section = self:section(SimpleSection)
752 if instanceof(class, AbstractValue) then
753 local obj = class(self, section, ...)
754 obj.track_missing = true
758 error("class must be a descendent of AbstractValue")
762 function SimpleForm.set(self, section, option, value)
763 self.data[option] = value
767 function SimpleForm.del(self, section, option)
768 self.data[option] = nil
772 function SimpleForm.get(self, section, option)
773 return self.data[option]
777 function SimpleForm.get_scheme()
782 Form = class(SimpleForm)
784 function Form.__init__(self, ...)
785 SimpleForm.__init__(self, ...)
793 AbstractSection = class(Node)
795 function AbstractSection.__init__(self, map, sectiontype, ...)
796 Node.__init__(self, ...)
797 self.sectiontype = sectiontype
799 self.config = map.config
804 self.tag_invalid = {}
805 self.tag_deperror = {}
809 self.addremove = false
813 -- Define a tab for the section
814 function AbstractSection.tab(self, tab, title, desc)
815 self.tabs = self.tabs or { }
816 self.tab_names = self.tab_names or { }
818 self.tab_names[#self.tab_names+1] = tab
826 -- Appends a new option
827 function AbstractSection.option(self, class, option, ...)
828 -- Autodetect from UVL
829 if class == true and self.map:get_scheme(self.sectiontype, option) then
830 local vs = self.map:get_scheme(self.sectiontype, option)
831 if vs.type == "boolean" then
833 elseif vs.type == "list" then
835 elseif vs.type == "enum" or vs.type == "reference" then
842 if instanceof(class, AbstractValue) then
843 local obj = class(self.map, self, option, ...)
845 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
848 self.fields[option] = obj
850 elseif class == true then
851 error("No valid class was given and autodetection failed.")
853 error("class must be a descendant of AbstractValue")
857 -- Appends a new tabbed option
858 function AbstractSection.taboption(self, tab, ...)
860 assert(tab and self.tabs and self.tabs[tab],
861 "Cannot assign option to not existing tab %q" % tostring(tab))
863 local l = self.tabs[tab].childs
864 local o = AbstractSection.option(self, ...)
866 if o then l[#l+1] = o end
871 -- Render a single tab
872 function AbstractSection.render_tab(self, tab, ...)
874 assert(tab and self.tabs and self.tabs[tab],
875 "Cannot render not existing tab %q" % tostring(tab))
877 for _, node in ipairs(self.tabs[tab].childs) do
882 -- Parse optional options
883 function AbstractSection.parse_optionals(self, section)
884 if not self.optional then
888 self.optionals[section] = {}
890 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
891 for k,v in ipairs(self.children) do
892 if v.optional and not v:cfgvalue(section) then
893 if field == v.option then
895 self.map.proceed = true
897 table.insert(self.optionals[section], v)
902 if field and #field > 0 and self.dynamic then
903 self:add_dynamic(field)
907 -- Add a dynamic option
908 function AbstractSection.add_dynamic(self, field, optional)
909 local o = self:option(Value, field, field)
910 o.optional = optional
913 -- Parse all dynamic options
914 function AbstractSection.parse_dynamic(self, section)
915 if not self.dynamic then
919 local arr = luci.util.clone(self:cfgvalue(section))
920 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
921 for k, v in pairs(form) do
925 for key,val in pairs(arr) do
928 for i,c in ipairs(self.children) do
929 if c.option == key then
934 if create and key:sub(1, 1) ~= "." then
935 self.map.proceed = true
936 self:add_dynamic(key, true)
941 -- Returns the section's UCI table
942 function AbstractSection.cfgvalue(self, section)
943 return self.map:get(section)
947 function AbstractSection.push_events(self)
948 --luci.util.append(self.map.events, self.events)
949 self.map.changed = true
952 -- Removes the section
953 function AbstractSection.remove(self, section)
954 self.map.proceed = true
955 return self.map:del(section)
958 -- Creates the section
959 function AbstractSection.create(self, section)
963 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
965 section = self.map:add(self.sectiontype)
970 for k,v in pairs(self.children) do
972 self.map:set(section, v.option, v.default)
976 for k,v in pairs(self.defaults) do
977 self.map:set(section, k, v)
981 self.map.proceed = true
987 SimpleSection = class(AbstractSection)
989 function SimpleSection.__init__(self, form, ...)
990 AbstractSection.__init__(self, form, nil, ...)
991 self.template = "cbi/nullsection"
995 Table = class(AbstractSection)
997 function Table.__init__(self, form, data, ...)
998 local datasource = {}
1000 datasource.config = "table"
1001 self.data = data or {}
1003 datasource.formvalue = Map.formvalue
1004 datasource.formvaluetable = Map.formvaluetable
1005 datasource.readinput = true
1007 function datasource.get(self, section, option)
1008 return tself.data[section] and tself.data[section][option]
1011 function datasource.submitstate(self)
1012 return Map.formvalue(self, "cbi.submit")
1015 function datasource.del(...)
1019 function datasource.get_scheme()
1023 AbstractSection.__init__(self, datasource, "table", ...)
1024 self.template = "cbi/tblsection"
1025 self.rowcolors = true
1026 self.anonymous = true
1029 function Table.parse(self, readinput)
1030 self.map.readinput = (readinput ~= false)
1031 for i, k in ipairs(self:cfgsections()) do
1032 if self.map:submitstate() then
1038 function Table.cfgsections(self)
1041 for i, v in luci.util.kspairs(self.data) do
1042 table.insert(sections, i)
1048 function Table.update(self, data)
1055 NamedSection - A fixed configuration section defined by its name
1057 NamedSection = class(AbstractSection)
1059 function NamedSection.__init__(self, map, section, stype, ...)
1060 AbstractSection.__init__(self, map, stype, ...)
1061 Node._i18n(self, map.config, section, nil, ...)
1064 self.addremove = false
1066 -- Use defaults from UVL
1067 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1068 local vs = self.map:get_scheme(self.sectiontype)
1069 self.addremove = not vs.unique and not vs.required
1070 self.dynamic = vs.dynamic
1071 self.title = self.title or vs.title
1072 self.description = self.description or vs.descr
1075 self.template = "cbi/nsection"
1076 self.section = section
1079 function NamedSection.parse(self, novld)
1080 local s = self.section
1081 local active = self:cfgvalue(s)
1083 if self.addremove then
1084 local path = self.config.."."..s
1085 if active then -- Remove the section
1086 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1090 else -- Create and apply default values
1091 if self.map:formvalue("cbi.cns."..path) then
1099 AbstractSection.parse_dynamic(self, s)
1100 if self.map:submitstate() then
1103 if not novld and not self.override_scheme and self.map.scheme then
1104 _uvl_validate_section(self, s)
1107 AbstractSection.parse_optionals(self, s)
1109 if self.changed then
1117 TypedSection - A (set of) configuration section(s) defined by the type
1118 addremove: Defines whether the user can add/remove sections of this type
1119 anonymous: Allow creating anonymous sections
1120 validate: a validation function returning nil if the section is invalid
1122 TypedSection = class(AbstractSection)
1124 function TypedSection.__init__(self, map, type, ...)
1125 AbstractSection.__init__(self, map, type, ...)
1126 Node._i18n(self, map.config, type, nil, ...)
1128 self.template = "cbi/tsection"
1130 self.anonymous = false
1132 -- Use defaults from UVL
1133 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1134 local vs = self.map:get_scheme(self.sectiontype)
1135 self.addremove = not vs.unique and not vs.required
1136 self.dynamic = vs.dynamic
1137 self.anonymous = not vs.named
1138 self.title = self.title or vs.title
1139 self.description = self.description or vs.descr
1143 -- Return all matching UCI sections for this TypedSection
1144 function TypedSection.cfgsections(self)
1146 self.map.uci:foreach(self.map.config, self.sectiontype,
1148 if self:checkscope(section[".name"]) then
1149 table.insert(sections, section[".name"])
1156 -- Limits scope to sections that have certain option => value pairs
1157 function TypedSection.depends(self, option, value)
1158 table.insert(self.deps, {option=option, value=value})
1161 function TypedSection.parse(self, novld)
1162 if self.addremove then
1164 local crval = REMOVE_PREFIX .. self.config
1165 local name = self.map:formvaluetable(crval)
1166 for k,v in pairs(name) do
1167 if k:sub(-2) == ".x" then
1168 k = k:sub(1, #k - 2)
1170 if self:cfgvalue(k) and self:checkscope(k) then
1177 for i, k in ipairs(self:cfgsections()) do
1178 AbstractSection.parse_dynamic(self, k)
1179 if self.map:submitstate() then
1180 Node.parse(self, k, novld)
1182 if not novld and not self.override_scheme and self.map.scheme then
1183 _uvl_validate_section(self, k)
1186 AbstractSection.parse_optionals(self, k)
1189 if self.addremove then
1192 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1193 local name = self.map:formvalue(crval)
1194 if self.anonymous then
1196 created = self:create()
1200 -- Ignore if it already exists
1201 if self:cfgvalue(name) then
1205 name = self:checkscope(name)
1208 self.err_invalid = true
1211 if name and #name > 0 then
1212 created = self:create(name) and name
1214 self.invalid_cts = true
1221 AbstractSection.parse_optionals(self, created)
1225 if created or self.changed then
1230 -- Verifies scope of sections
1231 function TypedSection.checkscope(self, section)
1232 -- Check if we are not excluded
1233 if self.filter and not self:filter(section) then
1237 -- Check if at least one dependency is met
1238 if #self.deps > 0 and self:cfgvalue(section) then
1241 for k, v in ipairs(self.deps) do
1242 if self:cfgvalue(section)[v.option] == v.value then
1252 return self:validate(section)
1256 -- Dummy validate function
1257 function TypedSection.validate(self, section)
1263 AbstractValue - An abstract Value Type
1264 null: Value can be empty
1265 valid: A function returning the value if it is valid otherwise nil
1266 depends: A table of option => value pairs of which one must be true
1267 default: The default value
1268 size: The size of the input fields
1269 rmempty: Unset value if empty
1270 optional: This value is optional (see AbstractSection.optionals)
1272 AbstractValue = class(Node)
1274 function AbstractValue.__init__(self, map, section, option, ...)
1275 Node.__init__(self, ...)
1276 self.section = section
1277 self.option = option
1279 self.config = map.config
1280 self.tag_invalid = {}
1281 self.tag_missing = {}
1282 self.tag_reqerror = {}
1285 --self.cast = "string"
1287 self.track_missing = false
1291 self.optional = false
1294 function AbstractValue.prepare(self)
1295 -- Use defaults from UVL
1296 if not self.override_scheme
1297 and self.map:get_scheme(self.section.sectiontype, self.option) then
1298 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1299 if self.cast == nil then
1300 self.cast = (vs.type == "list") and "list" or "string"
1302 self.title = self.title or vs.title
1303 self.description = self.description or vs.descr
1304 if self.default == nil then
1305 self.default = vs.default
1308 if vs.depends and not self.override_dependencies then
1309 for i, deps in ipairs(vs.depends) do
1310 deps = _uvl_strip_remote_dependencies(deps)
1318 self.cast = self.cast or "string"
1321 -- Add a dependencie to another section field
1322 function AbstractValue.depends(self, field, value)
1324 if type(field) == "string" then
1331 table.insert(self.deps, {deps=deps, add=""})
1334 -- Generates the unique CBID
1335 function AbstractValue.cbid(self, section)
1336 return "cbid."..self.map.config.."."..section.."."..self.option
1339 -- Return whether this object should be created
1340 function AbstractValue.formcreated(self, section)
1341 local key = "cbi.opt."..self.config.."."..section
1342 return (self.map:formvalue(key) == self.option)
1345 -- Returns the formvalue for this object
1346 function AbstractValue.formvalue(self, section)
1347 return self.map:formvalue(self:cbid(section))
1350 function AbstractValue.additional(self, value)
1351 self.optional = value
1354 function AbstractValue.mandatory(self, value)
1355 self.rmempty = not value
1358 function AbstractValue.parse(self, section, novld)
1359 local fvalue = self:formvalue(section)
1360 local cvalue = self:cfgvalue(section)
1362 -- If favlue and cvalue are both tables and have the same content
1363 -- make them identical
1364 if type(fvalue) == "table" and type(cvalue) == "table" then
1365 local equal = #fvalue == #cvalue
1368 if cvalue[i] ~= fvalue[i] then
1378 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1379 fvalue = self:transform(self:validate(fvalue, section))
1380 if not fvalue and not novld then
1382 self.error[section] = "invalid"
1384 self.error = { [section] = "invalid" }
1386 if self.section.error then
1387 table.insert(self.section.error[section], "invalid")
1389 self.section.error = {[section] = {"invalid"}}
1391 self.map.save = false
1393 if fvalue and not (fvalue == cvalue) then
1394 if self:write(section, fvalue) then
1396 self.section.changed = true
1397 --luci.util.append(self.map.events, self.events)
1400 else -- Unset the UCI or error
1401 if self.rmempty or self.optional then
1402 if self:remove(section) then
1404 self.section.changed = true
1405 --luci.util.append(self.map.events, self.events)
1407 elseif cvalue ~= fvalue and not novld then
1408 self:write(section, fvalue or "")
1410 self.error[section] = "missing"
1412 self.error = { [section] = "missing" }
1414 self.map.save = false
1419 -- Render if this value exists or if it is mandatory
1420 function AbstractValue.render(self, s, scope)
1421 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1424 scope.cbid = self:cbid(s)
1425 scope.striptags = luci.util.striptags
1426 scope.pcdata = luci.util.pcdata
1428 scope.ifattr = function(cond,key,val)
1430 return string.format(
1431 ' %s="%s"', tostring(key),
1432 luci.util.pcdata(tostring( val
1434 or (type(self[key]) ~= "function" and self[key])
1442 scope.attr = function(...)
1443 return scope.ifattr( true, ... )
1446 Node.render(self, scope)
1450 -- Return the UCI value of this object
1451 function AbstractValue.cfgvalue(self, section)
1452 local value = self.map:get(section, self.option)
1455 elseif not self.cast or self.cast == type(value) then
1457 elseif self.cast == "string" then
1458 if type(value) == "table" then
1461 elseif self.cast == "table" then
1462 return luci.util.split(value, "%s+", nil, true)
1466 -- Validate the form value
1467 function AbstractValue.validate(self, value)
1471 AbstractValue.transform = AbstractValue.validate
1475 function AbstractValue.write(self, section, value)
1476 return self.map:set(section, self.option, value)
1480 function AbstractValue.remove(self, section)
1481 return self.map:del(section, self.option)
1488 Value - A one-line value
1489 maxlength: The maximum length
1491 Value = class(AbstractValue)
1493 function Value.__init__(self, ...)
1494 AbstractValue.__init__(self, ...)
1495 self.template = "cbi/value"
1500 function Value.value(self, key, val)
1502 table.insert(self.keylist, tostring(key))
1503 table.insert(self.vallist, tostring(val))
1507 -- DummyValue - This does nothing except being there
1508 DummyValue = class(AbstractValue)
1510 function DummyValue.__init__(self, ...)
1511 AbstractValue.__init__(self, ...)
1512 self.template = "cbi/dvalue"
1516 function DummyValue.cfgvalue(self, section)
1519 if type(self.value) == "function" then
1520 value = self:value(section)
1525 value = AbstractValue.cfgvalue(self, section)
1530 function DummyValue.parse(self)
1536 Flag - A flag being enabled or disabled
1538 Flag = class(AbstractValue)
1540 function Flag.__init__(self, ...)
1541 AbstractValue.__init__(self, ...)
1542 self.template = "cbi/fvalue"
1548 -- A flag can only have two states: set or unset
1549 function Flag.parse(self, section)
1550 local fvalue = self:formvalue(section)
1553 fvalue = self.enabled
1555 fvalue = self.disabled
1558 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1559 if not(fvalue == self:cfgvalue(section)) then
1560 self:write(section, fvalue)
1563 self:remove(section)
1570 ListValue - A one-line value predefined in a list
1571 widget: The widget that will be used (select, radio)
1573 ListValue = class(AbstractValue)
1575 function ListValue.__init__(self, ...)
1576 AbstractValue.__init__(self, ...)
1577 self.template = "cbi/lvalue"
1582 self.widget = "select"
1585 function ListValue.prepare(self, ...)
1586 AbstractValue.prepare(self, ...)
1587 if not self.override_scheme
1588 and self.map:get_scheme(self.section.sectiontype, self.option) then
1589 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1590 if self.value and vs.valuelist and not self.override_values then
1591 for k, v in ipairs(vs.valuelist) do
1593 if not self.override_dependencies
1594 and vs.enum_depends and vs.enum_depends[v.value] then
1595 for i, dep in ipairs(vs.enum_depends[v.value]) do
1596 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1599 self:value(v.value, v.title or v.value, unpack(deps))
1605 function ListValue.value(self, key, val, ...)
1606 if luci.util.contains(self.keylist, key) then
1611 table.insert(self.keylist, tostring(key))
1612 table.insert(self.vallist, tostring(val))
1614 for i, deps in ipairs({...}) do
1615 table.insert(self.deps, {add = "-"..key, deps=deps})
1619 function ListValue.validate(self, val)
1620 if luci.util.contains(self.keylist, val) then
1630 MultiValue - Multiple delimited values
1631 widget: The widget that will be used (select, checkbox)
1632 delimiter: The delimiter that will separate the values (default: " ")
1634 MultiValue = class(AbstractValue)
1636 function MultiValue.__init__(self, ...)
1637 AbstractValue.__init__(self, ...)
1638 self.template = "cbi/mvalue"
1643 self.widget = "checkbox"
1644 self.delimiter = " "
1647 function MultiValue.render(self, ...)
1648 if self.widget == "select" and not self.size then
1649 self.size = #self.vallist
1652 AbstractValue.render(self, ...)
1655 function MultiValue.value(self, key, val)
1656 if luci.util.contains(self.keylist, key) then
1661 table.insert(self.keylist, tostring(key))
1662 table.insert(self.vallist, tostring(val))
1665 function MultiValue.valuelist(self, section)
1666 local val = self:cfgvalue(section)
1668 if not(type(val) == "string") then
1672 return luci.util.split(val, self.delimiter)
1675 function MultiValue.validate(self, val)
1676 val = (type(val) == "table") and val or {val}
1680 for i, value in ipairs(val) do
1681 if luci.util.contains(self.keylist, value) then
1682 result = result and (result .. self.delimiter .. value) or value
1690 StaticList = class(MultiValue)
1692 function StaticList.__init__(self, ...)
1693 MultiValue.__init__(self, ...)
1695 self.valuelist = self.cfgvalue
1697 if not self.override_scheme
1698 and self.map:get_scheme(self.section.sectiontype, self.option) then
1699 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1700 if self.value and vs.values and not self.override_values then
1701 for k, v in pairs(vs.values) do
1708 function StaticList.validate(self, value)
1709 value = (type(value) == "table") and value or {value}
1712 for i, v in ipairs(value) do
1713 if luci.util.contains(self.keylist, v) then
1714 table.insert(valid, v)
1721 DynamicList = class(AbstractValue)
1723 function DynamicList.__init__(self, ...)
1724 AbstractValue.__init__(self, ...)
1725 self.template = "cbi/dynlist"
1731 function DynamicList.value(self, key, val)
1733 table.insert(self.keylist, tostring(key))
1734 table.insert(self.vallist, tostring(val))
1737 function DynamicList.write(self, ...)
1738 self.map.proceed = true
1739 return AbstractValue.write(self, ...)
1742 function DynamicList.formvalue(self, section)
1743 local value = AbstractValue.formvalue(self, section)
1744 value = (type(value) == "table") and value or {value}
1747 for i, v in ipairs(value) do
1749 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1750 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1751 table.insert(valid, v)
1760 TextValue - A multi-line value
1763 TextValue = class(AbstractValue)
1765 function TextValue.__init__(self, ...)
1766 AbstractValue.__init__(self, ...)
1767 self.template = "cbi/tvalue"
1773 Button = class(AbstractValue)
1775 function Button.__init__(self, ...)
1776 AbstractValue.__init__(self, ...)
1777 self.template = "cbi/button"
1778 self.inputstyle = nil
1783 FileUpload = class(AbstractValue)
1785 function FileUpload.__init__(self, ...)
1786 AbstractValue.__init__(self, ...)
1787 self.template = "cbi/upload"
1788 if not self.map.upload_fields then
1789 self.map.upload_fields = { self }
1791 self.map.upload_fields[#self.map.upload_fields+1] = self
1795 function FileUpload.formcreated(self, section)
1796 return AbstractValue.formcreated(self, section) or
1797 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1798 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1801 function FileUpload.cfgvalue(self, section)
1802 local val = AbstractValue.cfgvalue(self, section)
1803 if val and fs.access(val) then
1809 function FileUpload.formvalue(self, section)
1810 local val = AbstractValue.formvalue(self, section)
1812 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1813 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1823 function FileUpload.remove(self, section)
1824 local val = AbstractValue.formvalue(self, section)
1825 if val and fs.access(val) then fs.unlink(val) end
1826 return AbstractValue.remove(self, section)
1830 FileBrowser = class(AbstractValue)
1832 function FileBrowser.__init__(self, ...)
1833 AbstractValue.__init__(self, ...)
1834 self.template = "cbi/browser"