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._run_hooks(self, ...)
229 for _, f in ipairs(arg) do
230 if type(self[f]) == "function" then
239 function Node.prepare(self, ...)
240 for k, child in ipairs(self.children) do
245 -- Append child nodes
246 function Node.append(self, obj)
247 table.insert(self.children, obj)
250 -- Parse this node and its children
251 function Node.parse(self, ...)
252 for k, child in ipairs(self.children) do
258 function Node.render(self, scope)
262 luci.template.render(self.template, scope)
265 -- Render the children
266 function Node.render_children(self, ...)
267 for k, node in ipairs(self.children) do
274 A simple template element
276 Template = class(Node)
278 function Template.__init__(self, template)
280 self.template = template
283 function Template.render(self)
284 luci.template.render(self.template, {self=self})
287 function Template.parse(self, readinput)
288 self.readinput = (readinput ~= false)
289 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
294 Map - A map describing a configuration file
298 function Map.__init__(self, config, ...)
299 Node.__init__(self, ...)
302 self.parsechain = {self.config}
303 self.template = "cbi/map"
304 self.apply_on_parse = nil
305 self.readinput = true
309 self.uci = uci.cursor()
314 if not self.uci:load(self.config) then
315 error("Unable to read UCI data: " .. self.config)
318 self.validator = luci.uvl.UVL()
319 self.scheme = self.validator:get_scheme(self.config)
322 function Map.formvalue(self, key)
323 return self.readinput and luci.http.formvalue(key)
326 function Map.formvaluetable(self, key)
327 return self.readinput and luci.http.formvaluetable(key) or {}
330 function Map.get_scheme(self, sectiontype, option)
332 return self.scheme and self.scheme.sections[sectiontype]
334 return self.scheme and self.scheme.variables[sectiontype]
335 and self.scheme.variables[sectiontype][option]
339 function Map.submitstate(self)
340 return self:formvalue("cbi.submit")
343 -- Chain foreign config
344 function Map.chain(self, config)
345 table.insert(self.parsechain, config)
348 function Map.state_handler(self, state)
352 -- Use optimized UCI writing
353 function Map.parse(self, readinput, ...)
354 self.readinput = (readinput ~= false)
356 if self:formvalue("cbi.skip") then
357 self.state = FORM_SKIP
358 return self:state_handler(self.state)
361 Node.parse(self, ...)
364 for i, config in ipairs(self.parsechain) do
365 self.uci:save(config)
367 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
368 self:_run_hooks("on_before_commit")
369 for i, config in ipairs(self.parsechain) do
370 self.uci:commit(config)
372 -- Refresh data because commit changes section names
373 self.uci:load(config)
375 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
376 if self.apply_on_parse then
377 self.uci:apply(self.parsechain)
378 self:_run_hooks("on_apply", "on_after_apply")
380 self._apply = function()
381 local cmd = self.uci:apply(self.parsechain, true)
387 Node.parse(self, true)
390 for i, config in ipairs(self.parsechain) do
391 self.uci:unload(config)
393 if type(self.commit_handler) == "function" then
394 self:commit_handler(self:submitstate())
398 if self:submitstate() then
399 if not self.save then
400 self.state = FORM_INVALID
401 elseif self.proceed then
402 self.state = FORM_PROCEED
404 self.state = self.changed and FORM_CHANGED or FORM_VALID
407 self.state = FORM_NODATA
410 return self:state_handler(self.state)
413 function Map.render(self, ...)
414 self:_run_hooks("on_init")
415 Node.render(self, ...)
417 local fp = self._apply()
420 self:_run_hooks("on_apply")
424 -- Creates a child section
425 function Map.section(self, class, ...)
426 if instanceof(class, AbstractSection) then
427 local obj = class(self, ...)
431 error("class must be a descendent of AbstractSection")
436 function Map.add(self, sectiontype)
437 return self.uci:add(self.config, sectiontype)
441 function Map.set(self, section, option, value)
443 return self.uci:set(self.config, section, option, value)
445 return self.uci:set(self.config, section, value)
450 function Map.del(self, section, option)
452 return self.uci:delete(self.config, section, option)
454 return self.uci:delete(self.config, section)
459 function Map.get(self, section, option)
461 return self.uci:get_all(self.config)
463 return self.uci:get(self.config, section, option)
465 return self.uci:get_all(self.config, section)
472 Compound = class(Node)
474 function Compound.__init__(self, ...)
476 self.template = "cbi/compound"
477 self.children = {...}
480 function Compound.populate_delegator(self, delegator)
481 for _, v in ipairs(self.children) do
482 v.delegator = delegator
486 function Compound.parse(self, ...)
487 local cstate, state = 0
489 for k, child in ipairs(self.children) do
490 cstate = child:parse(...)
491 state = (not state or cstate < state) and cstate or state
499 Delegator - Node controller
501 Delegator = class(Node)
502 function Delegator.__init__(self, ...)
503 Node.__init__(self, ...)
505 self.defaultpath = {}
506 self.pageaction = false
507 self.readinput = true
508 self.allow_reset = false
509 self.allow_cancel = false
510 self.allow_back = false
511 self.allow_finish = false
512 self.template = "cbi/delegator"
515 function Delegator.set(self, name, node)
516 if type(node) == "table" and getmetatable(node) == nil then
517 node = Compound(unpack(node))
519 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
520 assert(not self.nodes[name], "Duplicate entry")
522 self.nodes[name] = node
525 function Delegator.add(self, name, node)
526 node = self:set(name, node)
527 self.defaultpath[#self.defaultpath+1] = name
530 function Delegator.insert_after(self, name, after)
531 local n = #self.chain
532 for k, v in ipairs(self.chain) do
538 table.insert(self.chain, n, name)
541 function Delegator.set_route(self, ...)
542 local n, chain, route = 0, self.chain, {...}
544 if chain[i] == self.current then
553 for i = n + 1, #chain do
558 function Delegator.get(self, name)
559 return self.nodes[name]
562 function Delegator.parse(self, ...)
563 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
564 if self:_run_hooks("on_cancel") then
569 if not Map.formvalue(self, "cbi.delg.current") then
570 self:_run_hooks("on_init")
574 self.chain = self.chain or self:get_chain()
575 self.current = self.current or self:get_active()
576 self.active = self.active or self:get(self.current)
577 assert(self.active, "Invalid state")
579 local stat = FORM_DONE
580 if type(self.active) ~= "function" then
581 self.active:populate_delegator(self)
582 stat = self.active:parse()
587 if stat > FORM_PROCEED then
588 if Map.formvalue(self, "cbi.delg.back") then
589 newcurrent = self:get_prev(self.current)
591 newcurrent = self:get_next(self.current)
593 elseif stat < FORM_PROCEED then
598 if not Map.formvalue(self, "cbi.submit") then
600 elseif stat > FORM_PROCEED
601 and (not newcurrent or not self:get(newcurrent)) then
602 self:_run_hooks("on_done")
605 self.current = newcurrent or self.current
606 self.active = self:get(self.current)
607 if type(self.active) ~= "function" then
608 self.active:parse(false)
611 return self:parse(...)
616 function Delegator.get_next(self, state)
617 for k, v in ipairs(self.chain) do
619 return self.chain[k+1]
624 function Delegator.get_prev(self, state)
625 for k, v in ipairs(self.chain) do
627 return self.chain[k-1]
632 function Delegator.get_chain(self)
633 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
634 return type(x) == "table" and x or {x}
637 function Delegator.get_active(self)
638 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
646 Page.__init__ = Node.__init__
647 Page.parse = function() end
651 SimpleForm - A Simple non-UCI form
653 SimpleForm = class(Node)
655 function SimpleForm.__init__(self, config, title, description, data)
656 Node.__init__(self, title, description)
658 self.data = data or {}
659 self.template = "cbi/simpleform"
661 self.pageaction = false
662 self.readinput = true
665 SimpleForm.formvalue = Map.formvalue
666 SimpleForm.formvaluetable = Map.formvaluetable
668 function SimpleForm.parse(self, readinput, ...)
669 self.readinput = (readinput ~= false)
671 if self:formvalue("cbi.skip") then
675 if self:submitstate() then
676 Node.parse(self, 1, ...)
680 for k, j in ipairs(self.children) do
681 for i, v in ipairs(j.children) do
683 and (not v.tag_missing or not v.tag_missing[1])
684 and (not v.tag_invalid or not v.tag_invalid[1])
690 not self:submitstate() and FORM_NODATA
691 or valid and FORM_VALID
694 self.dorender = not self.handle
696 local nrender, nstate = self:handle(state, self.data)
697 self.dorender = self.dorender or (nrender ~= false)
698 state = nstate or state
703 function SimpleForm.render(self, ...)
704 if self.dorender then
705 Node.render(self, ...)
709 function SimpleForm.submitstate(self)
710 return self:formvalue("cbi.submit")
713 function SimpleForm.section(self, class, ...)
714 if instanceof(class, AbstractSection) then
715 local obj = class(self, ...)
719 error("class must be a descendent of AbstractSection")
723 -- Creates a child field
724 function SimpleForm.field(self, class, ...)
726 for k, v in ipairs(self.children) do
727 if instanceof(v, SimpleSection) then
733 section = self:section(SimpleSection)
736 if instanceof(class, AbstractValue) then
737 local obj = class(self, section, ...)
738 obj.track_missing = true
742 error("class must be a descendent of AbstractValue")
746 function SimpleForm.set(self, section, option, value)
747 self.data[option] = value
751 function SimpleForm.del(self, section, option)
752 self.data[option] = nil
756 function SimpleForm.get(self, section, option)
757 return self.data[option]
761 function SimpleForm.get_scheme()
766 Form = class(SimpleForm)
768 function Form.__init__(self, ...)
769 SimpleForm.__init__(self, ...)
777 AbstractSection = class(Node)
779 function AbstractSection.__init__(self, map, sectiontype, ...)
780 Node.__init__(self, ...)
781 self.sectiontype = sectiontype
783 self.config = map.config
788 self.tag_invalid = {}
789 self.tag_deperror = {}
793 self.addremove = false
797 -- Define a tab for the section
798 function AbstractSection.tab(self, tab, title, desc)
799 self.tabs = self.tabs or { }
800 self.tab_names = self.tab_names or { }
802 self.tab_names[#self.tab_names+1] = tab
810 -- Appends a new option
811 function AbstractSection.option(self, class, option, ...)
812 -- Autodetect from UVL
813 if class == true and self.map:get_scheme(self.sectiontype, option) then
814 local vs = self.map:get_scheme(self.sectiontype, option)
815 if vs.type == "boolean" then
817 elseif vs.type == "list" then
819 elseif vs.type == "enum" or vs.type == "reference" then
826 if instanceof(class, AbstractValue) then
827 local obj = class(self.map, self, option, ...)
829 self.fields[option] = obj
831 elseif class == true then
832 error("No valid class was given and autodetection failed.")
834 error("class must be a descendant of AbstractValue")
838 -- Appends a new tabbed option
839 function AbstractSection.taboption(self, tab, ...)
841 assert(tab and self.tabs and self.tabs[tab],
842 "Cannot assign option to not existing tab %q" % tostring(tab))
844 local l = self.tabs[tab].childs
845 local o = AbstractSection.option(self, ...)
847 if o then l[#l+1] = o end
852 -- Render a single tab
853 function AbstractSection.render_tab(self, tab, ...)
855 assert(tab and self.tabs and self.tabs[tab],
856 "Cannot render not existing tab %q" % tostring(tab))
858 for _, node in ipairs(self.tabs[tab].childs) do
863 -- Parse optional options
864 function AbstractSection.parse_optionals(self, section)
865 if not self.optional then
869 self.optionals[section] = {}
871 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
872 for k,v in ipairs(self.children) do
873 if v.optional and not v:cfgvalue(section) then
874 if field == v.option then
876 self.map.proceed = true
878 table.insert(self.optionals[section], v)
883 if field and #field > 0 and self.dynamic then
884 self:add_dynamic(field)
888 -- Add a dynamic option
889 function AbstractSection.add_dynamic(self, field, optional)
890 local o = self:option(Value, field, field)
891 o.optional = optional
894 -- Parse all dynamic options
895 function AbstractSection.parse_dynamic(self, section)
896 if not self.dynamic then
900 local arr = luci.util.clone(self:cfgvalue(section))
901 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
902 for k, v in pairs(form) do
906 for key,val in pairs(arr) do
909 for i,c in ipairs(self.children) do
910 if c.option == key then
915 if create and key:sub(1, 1) ~= "." then
916 self.map.proceed = true
917 self:add_dynamic(key, true)
922 -- Returns the section's UCI table
923 function AbstractSection.cfgvalue(self, section)
924 return self.map:get(section)
928 function AbstractSection.push_events(self)
929 --luci.util.append(self.map.events, self.events)
930 self.map.changed = true
933 -- Removes the section
934 function AbstractSection.remove(self, section)
935 self.map.proceed = true
936 return self.map:del(section)
939 -- Creates the section
940 function AbstractSection.create(self, section)
944 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
946 section = self.map:add(self.sectiontype)
951 for k,v in pairs(self.children) do
953 self.map:set(section, v.option, v.default)
957 for k,v in pairs(self.defaults) do
958 self.map:set(section, k, v)
962 self.map.proceed = true
968 SimpleSection = class(AbstractSection)
970 function SimpleSection.__init__(self, form, ...)
971 AbstractSection.__init__(self, form, nil, ...)
972 self.template = "cbi/nullsection"
976 Table = class(AbstractSection)
978 function Table.__init__(self, form, data, ...)
979 local datasource = {}
981 datasource.config = "table"
982 self.data = data or {}
984 datasource.formvalue = Map.formvalue
985 datasource.formvaluetable = Map.formvaluetable
986 datasource.readinput = true
988 function datasource.get(self, section, option)
989 return tself.data[section] and tself.data[section][option]
992 function datasource.submitstate(self)
993 return Map.formvalue(self, "cbi.submit")
996 function datasource.del(...)
1000 function datasource.get_scheme()
1004 AbstractSection.__init__(self, datasource, "table", ...)
1005 self.template = "cbi/tblsection"
1006 self.rowcolors = true
1007 self.anonymous = true
1010 function Table.parse(self, readinput)
1011 self.map.readinput = (readinput ~= false)
1012 for i, k in ipairs(self:cfgsections()) do
1013 if self.map:submitstate() then
1019 function Table.cfgsections(self)
1022 for i, v in luci.util.kspairs(self.data) do
1023 table.insert(sections, i)
1029 function Table.update(self, data)
1036 NamedSection - A fixed configuration section defined by its name
1038 NamedSection = class(AbstractSection)
1040 function NamedSection.__init__(self, map, section, stype, ...)
1041 AbstractSection.__init__(self, map, stype, ...)
1044 self.addremove = false
1046 -- Use defaults from UVL
1047 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1048 local vs = self.map:get_scheme(self.sectiontype)
1049 self.addremove = not vs.unique and not vs.required
1050 self.dynamic = vs.dynamic
1051 self.title = self.title or vs.title
1052 self.description = self.description or vs.descr
1055 self.template = "cbi/nsection"
1056 self.section = section
1059 function NamedSection.parse(self, novld)
1060 local s = self.section
1061 local active = self:cfgvalue(s)
1063 if self.addremove then
1064 local path = self.config.."."..s
1065 if active then -- Remove the section
1066 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1070 else -- Create and apply default values
1071 if self.map:formvalue("cbi.cns."..path) then
1079 AbstractSection.parse_dynamic(self, s)
1080 if self.map:submitstate() then
1083 if not novld and not self.override_scheme and self.map.scheme then
1084 _uvl_validate_section(self, s)
1087 AbstractSection.parse_optionals(self, s)
1089 if self.changed then
1097 TypedSection - A (set of) configuration section(s) defined by the type
1098 addremove: Defines whether the user can add/remove sections of this type
1099 anonymous: Allow creating anonymous sections
1100 validate: a validation function returning nil if the section is invalid
1102 TypedSection = class(AbstractSection)
1104 function TypedSection.__init__(self, map, type, ...)
1105 AbstractSection.__init__(self, map, type, ...)
1107 self.template = "cbi/tsection"
1109 self.anonymous = false
1111 -- Use defaults from UVL
1112 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1113 local vs = self.map:get_scheme(self.sectiontype)
1114 self.addremove = not vs.unique and not vs.required
1115 self.dynamic = vs.dynamic
1116 self.anonymous = not vs.named
1117 self.title = self.title or vs.title
1118 self.description = self.description or vs.descr
1122 -- Return all matching UCI sections for this TypedSection
1123 function TypedSection.cfgsections(self)
1125 self.map.uci:foreach(self.map.config, self.sectiontype,
1127 if self:checkscope(section[".name"]) then
1128 table.insert(sections, section[".name"])
1135 -- Limits scope to sections that have certain option => value pairs
1136 function TypedSection.depends(self, option, value)
1137 table.insert(self.deps, {option=option, value=value})
1140 function TypedSection.parse(self, novld)
1141 if self.addremove then
1143 local crval = REMOVE_PREFIX .. self.config
1144 local name = self.map:formvaluetable(crval)
1145 for k,v in pairs(name) do
1146 if k:sub(-2) == ".x" then
1147 k = k:sub(1, #k - 2)
1149 if self:cfgvalue(k) and self:checkscope(k) then
1156 for i, k in ipairs(self:cfgsections()) do
1157 AbstractSection.parse_dynamic(self, k)
1158 if self.map:submitstate() then
1159 Node.parse(self, k, novld)
1161 if not novld and not self.override_scheme and self.map.scheme then
1162 _uvl_validate_section(self, k)
1165 AbstractSection.parse_optionals(self, k)
1168 if self.addremove then
1171 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1172 local name = self.map:formvalue(crval)
1173 if self.anonymous then
1175 created = self:create()
1179 -- Ignore if it already exists
1180 if self:cfgvalue(name) then
1184 name = self:checkscope(name)
1187 self.err_invalid = true
1190 if name and #name > 0 then
1191 created = self:create(name) and name
1193 self.invalid_cts = true
1200 AbstractSection.parse_optionals(self, created)
1204 if created or self.changed then
1209 -- Verifies scope of sections
1210 function TypedSection.checkscope(self, section)
1211 -- Check if we are not excluded
1212 if self.filter and not self:filter(section) then
1216 -- Check if at least one dependency is met
1217 if #self.deps > 0 and self:cfgvalue(section) then
1220 for k, v in ipairs(self.deps) do
1221 if self:cfgvalue(section)[v.option] == v.value then
1231 return self:validate(section)
1235 -- Dummy validate function
1236 function TypedSection.validate(self, section)
1242 AbstractValue - An abstract Value Type
1243 null: Value can be empty
1244 valid: A function returning the value if it is valid otherwise nil
1245 depends: A table of option => value pairs of which one must be true
1246 default: The default value
1247 size: The size of the input fields
1248 rmempty: Unset value if empty
1249 optional: This value is optional (see AbstractSection.optionals)
1251 AbstractValue = class(Node)
1253 function AbstractValue.__init__(self, map, section, option, ...)
1254 Node.__init__(self, ...)
1255 self.section = section
1256 self.option = option
1258 self.config = map.config
1259 self.tag_invalid = {}
1260 self.tag_missing = {}
1261 self.tag_reqerror = {}
1264 --self.cast = "string"
1266 self.track_missing = false
1270 self.optional = false
1273 function AbstractValue.prepare(self)
1274 -- Use defaults from UVL
1275 if not self.override_scheme
1276 and self.map:get_scheme(self.section.sectiontype, self.option) then
1277 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1278 if self.cast == nil then
1279 self.cast = (vs.type == "list") and "list" or "string"
1281 self.title = self.title or vs.title
1282 self.description = self.description or vs.descr
1283 if self.default == nil then
1284 self.default = vs.default
1287 if vs.depends and not self.override_dependencies then
1288 for i, deps in ipairs(vs.depends) do
1289 deps = _uvl_strip_remote_dependencies(deps)
1297 self.cast = self.cast or "string"
1300 -- Add a dependencie to another section field
1301 function AbstractValue.depends(self, field, value)
1303 if type(field) == "string" then
1310 table.insert(self.deps, {deps=deps, add=""})
1313 -- Generates the unique CBID
1314 function AbstractValue.cbid(self, section)
1315 return "cbid."..self.map.config.."."..section.."."..self.option
1318 -- Return whether this object should be created
1319 function AbstractValue.formcreated(self, section)
1320 local key = "cbi.opt."..self.config.."."..section
1321 return (self.map:formvalue(key) == self.option)
1324 -- Returns the formvalue for this object
1325 function AbstractValue.formvalue(self, section)
1326 return self.map:formvalue(self:cbid(section))
1329 function AbstractValue.additional(self, value)
1330 self.optional = value
1333 function AbstractValue.mandatory(self, value)
1334 self.rmempty = not value
1337 function AbstractValue.parse(self, section, novld)
1338 local fvalue = self:formvalue(section)
1339 local cvalue = self:cfgvalue(section)
1341 -- If favlue and cvalue are both tables and have the same content
1342 -- make them identical
1343 if type(fvalue) == "table" and type(cvalue) == "table" then
1344 local equal = #fvalue == #cvalue
1347 if cvalue[i] ~= fvalue[i] then
1357 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1358 fvalue = self:transform(self:validate(fvalue, section))
1359 if not fvalue and not novld then
1361 self.error[section] = "invalid"
1363 self.error = { [section] = "invalid" }
1365 if self.section.error then
1366 table.insert(self.section.error[section], "invalid")
1368 self.section.error = {[section] = {"invalid"}}
1370 self.map.save = false
1372 if fvalue and not (fvalue == cvalue) then
1373 if self:write(section, fvalue) then
1375 self.section.changed = true
1376 --luci.util.append(self.map.events, self.events)
1379 else -- Unset the UCI or error
1380 if self.rmempty or self.optional then
1381 if self:remove(section) then
1383 self.section.changed = true
1384 --luci.util.append(self.map.events, self.events)
1386 elseif cvalue ~= fvalue and not novld then
1387 self:write(section, fvalue or "")
1389 self.error[section] = "missing"
1391 self.error = { [section] = "missing" }
1393 self.map.save = false
1398 -- Render if this value exists or if it is mandatory
1399 function AbstractValue.render(self, s, scope)
1400 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1403 scope.cbid = self:cbid(s)
1404 scope.striptags = luci.util.striptags
1405 scope.pcdata = luci.util.pcdata
1407 scope.ifattr = function(cond,key,val)
1409 return string.format(
1410 ' %s="%s"', tostring(key),
1411 luci.util.pcdata(tostring( val
1413 or (type(self[key]) ~= "function" and self[key])
1421 scope.attr = function(...)
1422 return scope.ifattr( true, ... )
1425 Node.render(self, scope)
1429 -- Return the UCI value of this object
1430 function AbstractValue.cfgvalue(self, section)
1431 local value = self.map:get(section, self.option)
1434 elseif not self.cast or self.cast == type(value) then
1436 elseif self.cast == "string" then
1437 if type(value) == "table" then
1440 elseif self.cast == "table" then
1441 return luci.util.split(value, "%s+", nil, true)
1445 -- Validate the form value
1446 function AbstractValue.validate(self, value)
1450 AbstractValue.transform = AbstractValue.validate
1454 function AbstractValue.write(self, section, value)
1455 return self.map:set(section, self.option, value)
1459 function AbstractValue.remove(self, section)
1460 return self.map:del(section, self.option)
1467 Value - A one-line value
1468 maxlength: The maximum length
1470 Value = class(AbstractValue)
1472 function Value.__init__(self, ...)
1473 AbstractValue.__init__(self, ...)
1474 self.template = "cbi/value"
1479 function Value.value(self, key, val)
1481 table.insert(self.keylist, tostring(key))
1482 table.insert(self.vallist, tostring(val))
1486 -- DummyValue - This does nothing except being there
1487 DummyValue = class(AbstractValue)
1489 function DummyValue.__init__(self, ...)
1490 AbstractValue.__init__(self, ...)
1491 self.template = "cbi/dvalue"
1495 function DummyValue.cfgvalue(self, section)
1498 if type(self.value) == "function" then
1499 value = self:value(section)
1504 value = AbstractValue.cfgvalue(self, section)
1509 function DummyValue.parse(self)
1515 Flag - A flag being enabled or disabled
1517 Flag = class(AbstractValue)
1519 function Flag.__init__(self, ...)
1520 AbstractValue.__init__(self, ...)
1521 self.template = "cbi/fvalue"
1527 -- A flag can only have two states: set or unset
1528 function Flag.parse(self, section)
1529 local fvalue = self:formvalue(section)
1532 fvalue = self.enabled
1534 fvalue = self.disabled
1537 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1538 if not(fvalue == self:cfgvalue(section)) then
1539 self:write(section, fvalue)
1542 self:remove(section)
1549 ListValue - A one-line value predefined in a list
1550 widget: The widget that will be used (select, radio)
1552 ListValue = class(AbstractValue)
1554 function ListValue.__init__(self, ...)
1555 AbstractValue.__init__(self, ...)
1556 self.template = "cbi/lvalue"
1561 self.widget = "select"
1564 function ListValue.prepare(self, ...)
1565 AbstractValue.prepare(self, ...)
1566 if not self.override_scheme
1567 and self.map:get_scheme(self.section.sectiontype, self.option) then
1568 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1569 if self.value and vs.valuelist and not self.override_values then
1570 for k, v in ipairs(vs.valuelist) do
1572 if not self.override_dependencies
1573 and vs.enum_depends and vs.enum_depends[v.value] then
1574 for i, dep in ipairs(vs.enum_depends[v.value]) do
1575 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1578 self:value(v.value, v.title or v.value, unpack(deps))
1584 function ListValue.value(self, key, val, ...)
1585 if luci.util.contains(self.keylist, key) then
1590 table.insert(self.keylist, tostring(key))
1591 table.insert(self.vallist, tostring(val))
1593 for i, deps in ipairs({...}) do
1594 table.insert(self.deps, {add = "-"..key, deps=deps})
1598 function ListValue.validate(self, val)
1599 if luci.util.contains(self.keylist, val) then
1609 MultiValue - Multiple delimited values
1610 widget: The widget that will be used (select, checkbox)
1611 delimiter: The delimiter that will separate the values (default: " ")
1613 MultiValue = class(AbstractValue)
1615 function MultiValue.__init__(self, ...)
1616 AbstractValue.__init__(self, ...)
1617 self.template = "cbi/mvalue"
1622 self.widget = "checkbox"
1623 self.delimiter = " "
1626 function MultiValue.render(self, ...)
1627 if self.widget == "select" and not self.size then
1628 self.size = #self.vallist
1631 AbstractValue.render(self, ...)
1634 function MultiValue.value(self, key, val)
1635 if luci.util.contains(self.keylist, key) then
1640 table.insert(self.keylist, tostring(key))
1641 table.insert(self.vallist, tostring(val))
1644 function MultiValue.valuelist(self, section)
1645 local val = self:cfgvalue(section)
1647 if not(type(val) == "string") then
1651 return luci.util.split(val, self.delimiter)
1654 function MultiValue.validate(self, val)
1655 val = (type(val) == "table") and val or {val}
1659 for i, value in ipairs(val) do
1660 if luci.util.contains(self.keylist, value) then
1661 result = result and (result .. self.delimiter .. value) or value
1669 StaticList = class(MultiValue)
1671 function StaticList.__init__(self, ...)
1672 MultiValue.__init__(self, ...)
1674 self.valuelist = self.cfgvalue
1676 if not self.override_scheme
1677 and self.map:get_scheme(self.section.sectiontype, self.option) then
1678 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1679 if self.value and vs.values and not self.override_values then
1680 for k, v in pairs(vs.values) do
1687 function StaticList.validate(self, value)
1688 value = (type(value) == "table") and value or {value}
1691 for i, v in ipairs(value) do
1692 if luci.util.contains(self.keylist, v) then
1693 table.insert(valid, v)
1700 DynamicList = class(AbstractValue)
1702 function DynamicList.__init__(self, ...)
1703 AbstractValue.__init__(self, ...)
1704 self.template = "cbi/dynlist"
1710 function DynamicList.value(self, key, val)
1712 table.insert(self.keylist, tostring(key))
1713 table.insert(self.vallist, tostring(val))
1716 function DynamicList.write(self, ...)
1717 self.map.proceed = true
1718 return AbstractValue.write(self, ...)
1721 function DynamicList.formvalue(self, section)
1722 local value = AbstractValue.formvalue(self, section)
1723 value = (type(value) == "table") and value or {value}
1726 for i, v in ipairs(value) do
1728 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1729 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1730 table.insert(valid, v)
1739 TextValue - A multi-line value
1742 TextValue = class(AbstractValue)
1744 function TextValue.__init__(self, ...)
1745 AbstractValue.__init__(self, ...)
1746 self.template = "cbi/tvalue"
1752 Button = class(AbstractValue)
1754 function Button.__init__(self, ...)
1755 AbstractValue.__init__(self, ...)
1756 self.template = "cbi/button"
1757 self.inputstyle = nil
1762 FileUpload = class(AbstractValue)
1764 function FileUpload.__init__(self, ...)
1765 AbstractValue.__init__(self, ...)
1766 self.template = "cbi/upload"
1767 if not self.map.upload_fields then
1768 self.map.upload_fields = { self }
1770 self.map.upload_fields[#self.map.upload_fields+1] = self
1774 function FileUpload.formcreated(self, section)
1775 return AbstractValue.formcreated(self, section) or
1776 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1777 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1780 function FileUpload.cfgvalue(self, section)
1781 local val = AbstractValue.cfgvalue(self, section)
1782 if val and fs.access(val) then
1788 function FileUpload.formvalue(self, section)
1789 local val = AbstractValue.formvalue(self, section)
1791 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1792 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1802 function FileUpload.remove(self, section)
1803 local val = AbstractValue.formvalue(self, section)
1804 if val and fs.access(val) then fs.unlink(val) end
1805 return AbstractValue.remove(self, section)
1809 FileBrowser = class(AbstractValue)
1811 function FileBrowser.__init__(self, ...)
1812 AbstractValue.__init__(self, ...)
1813 self.template = "cbi/browser"