2 LuCI - Configuration Bind Interface
5 Offers an interface for binding configuration values to certain
6 data types. Supports value and range validation and basic dependencies.
12 Copyright 2008 Steven Barth <steven@midlink.org>
14 Licensed under the Apache License, Version 2.0 (the "License");
15 you may not use this file except in compliance with the License.
16 You may obtain a copy of the License at
18 http://www.apache.org/licenses/LICENSE-2.0
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
27 module("luci.cbi", package.seeall)
29 require("luci.template")
30 local util = require("luci.util")
35 --local event = require "luci.sys.event"
36 local fs = require("nixio.fs")
37 local uci = require("luci.model.uci")
38 local class = util.class
39 local instanceof = util.instanceof
51 CREATE_PREFIX = "cbi.cts."
52 REMOVE_PREFIX = "cbi.rts."
54 -- Loads a CBI map from given file, creating an environment and returns it
55 function load(cbimap, ...)
56 local fs = require "nixio.fs"
57 local i18n = require "luci.i18n"
58 require("luci.config")
61 local upldir = "/lib/uci/upload/"
62 local cbidir = luci.util.libpath() .. "/model/cbi/"
65 if fs.access(cbimap) then
66 func, err = loadfile(cbimap)
67 elseif fs.access(cbidir..cbimap..".lua") then
68 func, err = loadfile(cbidir..cbimap..".lua")
69 elseif fs.access(cbidir..cbimap..".lua.gz") then
70 func, err = loadfile(cbidir..cbimap..".lua.gz")
72 func, err = nil, "Model '" .. cbimap .. "' not found!"
77 luci.i18n.loadc("base")
80 translate=i18n.translate,
81 translatef=i18n.translatef,
85 setfenv(func, setmetatable(env, {__index =
87 return rawget(tbl, key) or _M[key] or _G[key]
90 local maps = { func() }
92 local has_upload = false
94 for i, map in ipairs(maps) do
95 if not instanceof(map, Node) then
96 error("CBI map returns no valid map object!")
100 if map.upload_fields then
102 for _, field in ipairs(map.upload_fields) do
104 field.config .. '.' ..
105 field.section.sectiontype .. '.' ..
114 local uci = luci.model.uci.cursor()
115 local prm = luci.http.context.request.message.params
118 luci.http.setfilehandler(
119 function( field, chunk, eof )
120 if not field then return end
121 if field.name and not cbid then
122 local c, s, o = field.name:gmatch(
123 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
126 if c and s and o then
127 local t = uci:get( c, s )
128 if t and uploads[c.."."..t.."."..o] then
129 local path = upldir .. field.name
130 fd = io.open(path, "w")
139 if field.name == cbid and fd then
155 local function _uvl_validate_section(node, name)
156 local co = node.map:get()
158 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
159 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
161 local function tag_fields(e)
162 if e.option and node.fields[e.option] then
163 if node.fields[e.option].error then
164 node.fields[e.option].error[name] = e
166 node.fields[e.option].error = { [name] = e }
169 for _, c in ipairs(e.childs) do tag_fields(c) end
173 local function tag_section(e)
175 for _, c in ipairs(e.childs or { e }) do
176 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
177 table.insert( s, c.childs[1]:string() )
179 table.insert( s, c:string() )
186 node.error = { [name] = s }
191 local stat, err = node.map.validator:validate_section(node.config, name, co)
193 node.map.save = false
200 local function _uvl_strip_remote_dependencies(deps)
203 for k, v in pairs(deps) do
204 k = k:gsub("%$config%.%$section%.", "")
205 if k:match("^[%w_]+$") and type(v) == "string" then
214 -- Node pseudo abstract class
217 function Node.__init__(self, title, description)
219 self.title = title or ""
220 self.description = description or ""
221 self.template = "cbi/node"
225 function Node._run_hooks(self, ...)
228 for _, f in ipairs(arg) do
229 if type(self[f]) == "function" then
238 function Node.prepare(self, ...)
239 for k, child in ipairs(self.children) do
244 -- Append child nodes
245 function Node.append(self, obj)
246 table.insert(self.children, obj)
249 -- Parse this node and its children
250 function Node.parse(self, ...)
251 for k, child in ipairs(self.children) do
257 function Node.render(self, scope)
261 luci.template.render(self.template, scope)
264 -- Render the children
265 function Node.render_children(self, ...)
266 for k, node in ipairs(self.children) do
273 A simple template element
275 Template = class(Node)
277 function Template.__init__(self, template)
279 self.template = template
282 function Template.render(self)
283 luci.template.render(self.template, {self=self})
286 function Template.parse(self, readinput)
287 self.readinput = (readinput ~= false)
288 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
293 Map - A map describing a configuration file
297 function Map.__init__(self, config, ...)
298 Node.__init__(self, ...)
301 self.parsechain = {self.config}
302 self.template = "cbi/map"
303 self.apply_on_parse = nil
304 self.readinput = true
308 self.uci = uci.cursor()
313 if not self.uci:load(self.config) then
314 error("Unable to read UCI data: " .. self.config)
317 self.validator = luci.uvl.UVL()
318 self.scheme = self.validator:get_scheme(self.config)
321 function Map.formvalue(self, key)
322 return self.readinput and luci.http.formvalue(key)
325 function Map.formvaluetable(self, key)
326 return self.readinput and luci.http.formvaluetable(key) or {}
329 function Map.get_scheme(self, sectiontype, option)
331 return self.scheme and self.scheme.sections[sectiontype]
333 return self.scheme and self.scheme.variables[sectiontype]
334 and self.scheme.variables[sectiontype][option]
338 function Map.submitstate(self)
339 return self:formvalue("cbi.submit")
342 -- Chain foreign config
343 function Map.chain(self, config)
344 table.insert(self.parsechain, config)
347 function Map.state_handler(self, state)
351 -- Use optimized UCI writing
352 function Map.parse(self, readinput, ...)
353 self.readinput = (readinput ~= false)
354 self:_run_hooks("on_parse")
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:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
679 if self:submitstate() then
680 Node.parse(self, 1, ...)
684 for k, j in ipairs(self.children) do
685 for i, v in ipairs(j.children) do
687 and (not v.tag_missing or not v.tag_missing[1])
688 and (not v.tag_invalid or not v.tag_invalid[1])
694 not self:submitstate() and FORM_NODATA
695 or valid and FORM_VALID
698 self.dorender = not self.handle
700 local nrender, nstate = self:handle(state, self.data)
701 self.dorender = self.dorender or (nrender ~= false)
702 state = nstate or state
707 function SimpleForm.render(self, ...)
708 if self.dorender then
709 Node.render(self, ...)
713 function SimpleForm.submitstate(self)
714 return self:formvalue("cbi.submit")
717 function SimpleForm.section(self, class, ...)
718 if instanceof(class, AbstractSection) then
719 local obj = class(self, ...)
723 error("class must be a descendent of AbstractSection")
727 -- Creates a child field
728 function SimpleForm.field(self, class, ...)
730 for k, v in ipairs(self.children) do
731 if instanceof(v, SimpleSection) then
737 section = self:section(SimpleSection)
740 if instanceof(class, AbstractValue) then
741 local obj = class(self, section, ...)
742 obj.track_missing = true
746 error("class must be a descendent of AbstractValue")
750 function SimpleForm.set(self, section, option, value)
751 self.data[option] = value
755 function SimpleForm.del(self, section, option)
756 self.data[option] = nil
760 function SimpleForm.get(self, section, option)
761 return self.data[option]
765 function SimpleForm.get_scheme()
770 Form = class(SimpleForm)
772 function Form.__init__(self, ...)
773 SimpleForm.__init__(self, ...)
781 AbstractSection = class(Node)
783 function AbstractSection.__init__(self, map, sectiontype, ...)
784 Node.__init__(self, ...)
785 self.sectiontype = sectiontype
787 self.config = map.config
792 self.tag_invalid = {}
793 self.tag_deperror = {}
797 self.addremove = false
801 -- Define a tab for the section
802 function AbstractSection.tab(self, tab, title, desc)
803 self.tabs = self.tabs or { }
804 self.tab_names = self.tab_names or { }
806 self.tab_names[#self.tab_names+1] = tab
814 -- Appends a new option
815 function AbstractSection.option(self, class, option, ...)
816 -- Autodetect from UVL
817 if class == true and self.map:get_scheme(self.sectiontype, option) then
818 local vs = self.map:get_scheme(self.sectiontype, option)
819 if vs.type == "boolean" then
821 elseif vs.type == "list" then
823 elseif vs.type == "enum" or vs.type == "reference" then
830 if instanceof(class, AbstractValue) then
831 local obj = class(self.map, self, option, ...)
833 self.fields[option] = obj
835 elseif class == true then
836 error("No valid class was given and autodetection failed.")
838 error("class must be a descendant of AbstractValue")
842 -- Appends a new tabbed option
843 function AbstractSection.taboption(self, tab, ...)
845 assert(tab and self.tabs and self.tabs[tab],
846 "Cannot assign option to not existing tab %q" % tostring(tab))
848 local l = self.tabs[tab].childs
849 local o = AbstractSection.option(self, ...)
851 if o then l[#l+1] = o end
856 -- Render a single tab
857 function AbstractSection.render_tab(self, tab, ...)
859 assert(tab and self.tabs and self.tabs[tab],
860 "Cannot render not existing tab %q" % tostring(tab))
862 for _, node in ipairs(self.tabs[tab].childs) do
867 -- Parse optional options
868 function AbstractSection.parse_optionals(self, section)
869 if not self.optional then
873 self.optionals[section] = {}
875 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
876 for k,v in ipairs(self.children) do
877 if v.optional and not v:cfgvalue(section) then
878 if field == v.option then
880 self.map.proceed = true
882 table.insert(self.optionals[section], v)
887 if field and #field > 0 and self.dynamic then
888 self:add_dynamic(field)
892 -- Add a dynamic option
893 function AbstractSection.add_dynamic(self, field, optional)
894 local o = self:option(Value, field, field)
895 o.optional = optional
898 -- Parse all dynamic options
899 function AbstractSection.parse_dynamic(self, section)
900 if not self.dynamic then
904 local arr = luci.util.clone(self:cfgvalue(section))
905 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
906 for k, v in pairs(form) do
910 for key,val in pairs(arr) do
913 for i,c in ipairs(self.children) do
914 if c.option == key then
919 if create and key:sub(1, 1) ~= "." then
920 self.map.proceed = true
921 self:add_dynamic(key, true)
926 -- Returns the section's UCI table
927 function AbstractSection.cfgvalue(self, section)
928 return self.map:get(section)
932 function AbstractSection.push_events(self)
933 --luci.util.append(self.map.events, self.events)
934 self.map.changed = true
937 -- Removes the section
938 function AbstractSection.remove(self, section)
939 self.map.proceed = true
940 return self.map:del(section)
943 -- Creates the section
944 function AbstractSection.create(self, section)
948 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
950 section = self.map:add(self.sectiontype)
955 for k,v in pairs(self.children) do
957 self.map:set(section, v.option, v.default)
961 for k,v in pairs(self.defaults) do
962 self.map:set(section, k, v)
966 self.map.proceed = true
972 SimpleSection = class(AbstractSection)
974 function SimpleSection.__init__(self, form, ...)
975 AbstractSection.__init__(self, form, nil, ...)
976 self.template = "cbi/nullsection"
980 Table = class(AbstractSection)
982 function Table.__init__(self, form, data, ...)
983 local datasource = {}
985 datasource.config = "table"
986 self.data = data or {}
988 datasource.formvalue = Map.formvalue
989 datasource.formvaluetable = Map.formvaluetable
990 datasource.readinput = true
992 function datasource.get(self, section, option)
993 return tself.data[section] and tself.data[section][option]
996 function datasource.submitstate(self)
997 return Map.formvalue(self, "cbi.submit")
1000 function datasource.del(...)
1004 function datasource.get_scheme()
1008 AbstractSection.__init__(self, datasource, "table", ...)
1009 self.template = "cbi/tblsection"
1010 self.rowcolors = true
1011 self.anonymous = true
1014 function Table.parse(self, readinput)
1015 self.map.readinput = (readinput ~= false)
1016 for i, k in ipairs(self:cfgsections()) do
1017 if self.map:submitstate() then
1023 function Table.cfgsections(self)
1026 for i, v in luci.util.kspairs(self.data) do
1027 table.insert(sections, i)
1033 function Table.update(self, data)
1040 NamedSection - A fixed configuration section defined by its name
1042 NamedSection = class(AbstractSection)
1044 function NamedSection.__init__(self, map, section, stype, ...)
1045 AbstractSection.__init__(self, map, stype, ...)
1048 self.addremove = false
1050 -- Use defaults from UVL
1051 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1052 local vs = self.map:get_scheme(self.sectiontype)
1053 self.addremove = not vs.unique and not vs.required
1054 self.dynamic = vs.dynamic
1055 self.title = self.title or vs.title
1056 self.description = self.description or vs.descr
1059 self.template = "cbi/nsection"
1060 self.section = section
1063 function NamedSection.parse(self, novld)
1064 local s = self.section
1065 local active = self:cfgvalue(s)
1067 if self.addremove then
1068 local path = self.config.."."..s
1069 if active then -- Remove the section
1070 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1074 else -- Create and apply default values
1075 if self.map:formvalue("cbi.cns."..path) then
1083 AbstractSection.parse_dynamic(self, s)
1084 if self.map:submitstate() then
1087 if not novld and not self.override_scheme and self.map.scheme then
1088 _uvl_validate_section(self, s)
1091 AbstractSection.parse_optionals(self, s)
1093 if self.changed then
1101 TypedSection - A (set of) configuration section(s) defined by the type
1102 addremove: Defines whether the user can add/remove sections of this type
1103 anonymous: Allow creating anonymous sections
1104 validate: a validation function returning nil if the section is invalid
1106 TypedSection = class(AbstractSection)
1108 function TypedSection.__init__(self, map, type, ...)
1109 AbstractSection.__init__(self, map, type, ...)
1111 self.template = "cbi/tsection"
1113 self.anonymous = false
1115 -- Use defaults from UVL
1116 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1117 local vs = self.map:get_scheme(self.sectiontype)
1118 self.addremove = not vs.unique and not vs.required
1119 self.dynamic = vs.dynamic
1120 self.anonymous = not vs.named
1121 self.title = self.title or vs.title
1122 self.description = self.description or vs.descr
1126 -- Return all matching UCI sections for this TypedSection
1127 function TypedSection.cfgsections(self)
1129 self.map.uci:foreach(self.map.config, self.sectiontype,
1131 if self:checkscope(section[".name"]) then
1132 table.insert(sections, section[".name"])
1139 -- Limits scope to sections that have certain option => value pairs
1140 function TypedSection.depends(self, option, value)
1141 table.insert(self.deps, {option=option, value=value})
1144 function TypedSection.parse(self, novld)
1145 if self.addremove then
1147 local crval = REMOVE_PREFIX .. self.config
1148 local name = self.map:formvaluetable(crval)
1149 for k,v in pairs(name) do
1150 if k:sub(-2) == ".x" then
1151 k = k:sub(1, #k - 2)
1153 if self:cfgvalue(k) and self:checkscope(k) then
1160 for i, k in ipairs(self:cfgsections()) do
1161 AbstractSection.parse_dynamic(self, k)
1162 if self.map:submitstate() then
1163 Node.parse(self, k, novld)
1165 if not novld and not self.override_scheme and self.map.scheme then
1166 _uvl_validate_section(self, k)
1169 AbstractSection.parse_optionals(self, k)
1172 if self.addremove then
1175 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1176 local name = self.map:formvalue(crval)
1177 if self.anonymous then
1179 created = self:create()
1183 -- Ignore if it already exists
1184 if self:cfgvalue(name) then
1188 name = self:checkscope(name)
1191 self.err_invalid = true
1194 if name and #name > 0 then
1195 created = self:create(name) and name
1197 self.invalid_cts = true
1204 AbstractSection.parse_optionals(self, created)
1208 if created or self.changed then
1213 -- Verifies scope of sections
1214 function TypedSection.checkscope(self, section)
1215 -- Check if we are not excluded
1216 if self.filter and not self:filter(section) then
1220 -- Check if at least one dependency is met
1221 if #self.deps > 0 and self:cfgvalue(section) then
1224 for k, v in ipairs(self.deps) do
1225 if self:cfgvalue(section)[v.option] == v.value then
1235 return self:validate(section)
1239 -- Dummy validate function
1240 function TypedSection.validate(self, section)
1246 AbstractValue - An abstract Value Type
1247 null: Value can be empty
1248 valid: A function returning the value if it is valid otherwise nil
1249 depends: A table of option => value pairs of which one must be true
1250 default: The default value
1251 size: The size of the input fields
1252 rmempty: Unset value if empty
1253 optional: This value is optional (see AbstractSection.optionals)
1255 AbstractValue = class(Node)
1257 function AbstractValue.__init__(self, map, section, option, ...)
1258 Node.__init__(self, ...)
1259 self.section = section
1260 self.option = option
1262 self.config = map.config
1263 self.tag_invalid = {}
1264 self.tag_missing = {}
1265 self.tag_reqerror = {}
1268 --self.cast = "string"
1270 self.track_missing = false
1274 self.optional = false
1277 function AbstractValue.prepare(self)
1278 -- Use defaults from UVL
1279 if not self.override_scheme
1280 and self.map:get_scheme(self.section.sectiontype, self.option) then
1281 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1282 if self.cast == nil then
1283 self.cast = (vs.type == "list") and "list" or "string"
1285 self.title = self.title or vs.title
1286 self.description = self.description or vs.descr
1287 if self.default == nil then
1288 self.default = vs.default
1291 if vs.depends and not self.override_dependencies then
1292 for i, deps in ipairs(vs.depends) do
1293 deps = _uvl_strip_remote_dependencies(deps)
1301 self.cast = self.cast or "string"
1304 -- Add a dependencie to another section field
1305 function AbstractValue.depends(self, field, value)
1307 if type(field) == "string" then
1314 table.insert(self.deps, {deps=deps, add=""})
1317 -- Generates the unique CBID
1318 function AbstractValue.cbid(self, section)
1319 return "cbid."..self.map.config.."."..section.."."..self.option
1322 -- Return whether this object should be created
1323 function AbstractValue.formcreated(self, section)
1324 local key = "cbi.opt."..self.config.."."..section
1325 return (self.map:formvalue(key) == self.option)
1328 -- Returns the formvalue for this object
1329 function AbstractValue.formvalue(self, section)
1330 return self.map:formvalue(self:cbid(section))
1333 function AbstractValue.additional(self, value)
1334 self.optional = value
1337 function AbstractValue.mandatory(self, value)
1338 self.rmempty = not value
1341 function AbstractValue.parse(self, section, novld)
1342 local fvalue = self:formvalue(section)
1343 local cvalue = self:cfgvalue(section)
1345 -- If favlue and cvalue are both tables and have the same content
1346 -- make them identical
1347 if type(fvalue) == "table" and type(cvalue) == "table" then
1348 local equal = #fvalue == #cvalue
1351 if cvalue[i] ~= fvalue[i] then
1361 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1362 fvalue = self:transform(self:validate(fvalue, section))
1363 if not fvalue and not novld then
1365 self.error[section] = "invalid"
1367 self.error = { [section] = "invalid" }
1369 if self.section.error then
1370 table.insert(self.section.error[section], "invalid")
1372 self.section.error = {[section] = {"invalid"}}
1374 self.map.save = false
1376 if fvalue and not (fvalue == cvalue) then
1377 if self:write(section, fvalue) then
1379 self.section.changed = true
1380 --luci.util.append(self.map.events, self.events)
1383 else -- Unset the UCI or error
1384 if self.rmempty or self.optional then
1385 if self:remove(section) then
1387 self.section.changed = true
1388 --luci.util.append(self.map.events, self.events)
1390 elseif cvalue ~= fvalue and not novld then
1391 self:write(section, fvalue or "")
1393 self.error[section] = "missing"
1395 self.error = { [section] = "missing" }
1397 self.map.save = false
1402 -- Render if this value exists or if it is mandatory
1403 function AbstractValue.render(self, s, scope)
1404 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1407 scope.cbid = self:cbid(s)
1408 scope.striptags = luci.util.striptags
1409 scope.pcdata = luci.util.pcdata
1411 scope.ifattr = function(cond,key,val)
1413 return string.format(
1414 ' %s="%s"', tostring(key),
1415 luci.util.pcdata(tostring( val
1417 or (type(self[key]) ~= "function" and self[key])
1425 scope.attr = function(...)
1426 return scope.ifattr( true, ... )
1429 Node.render(self, scope)
1433 -- Return the UCI value of this object
1434 function AbstractValue.cfgvalue(self, section)
1435 local value = self.map:get(section, self.option)
1438 elseif not self.cast or self.cast == type(value) then
1440 elseif self.cast == "string" then
1441 if type(value) == "table" then
1444 elseif self.cast == "table" then
1445 return luci.util.split(value, "%s+", nil, true)
1449 -- Validate the form value
1450 function AbstractValue.validate(self, value)
1454 AbstractValue.transform = AbstractValue.validate
1458 function AbstractValue.write(self, section, value)
1459 return self.map:set(section, self.option, value)
1463 function AbstractValue.remove(self, section)
1464 return self.map:del(section, self.option)
1471 Value - A one-line value
1472 maxlength: The maximum length
1474 Value = class(AbstractValue)
1476 function Value.__init__(self, ...)
1477 AbstractValue.__init__(self, ...)
1478 self.template = "cbi/value"
1483 function Value.value(self, key, val)
1485 table.insert(self.keylist, tostring(key))
1486 table.insert(self.vallist, tostring(val))
1490 -- DummyValue - This does nothing except being there
1491 DummyValue = class(AbstractValue)
1493 function DummyValue.__init__(self, ...)
1494 AbstractValue.__init__(self, ...)
1495 self.template = "cbi/dvalue"
1499 function DummyValue.cfgvalue(self, section)
1502 if type(self.value) == "function" then
1503 value = self:value(section)
1508 value = AbstractValue.cfgvalue(self, section)
1513 function DummyValue.parse(self)
1519 Flag - A flag being enabled or disabled
1521 Flag = class(AbstractValue)
1523 function Flag.__init__(self, ...)
1524 AbstractValue.__init__(self, ...)
1525 self.template = "cbi/fvalue"
1531 -- A flag can only have two states: set or unset
1532 function Flag.parse(self, section)
1533 local fvalue = self:formvalue(section)
1536 fvalue = self.enabled
1538 fvalue = self.disabled
1541 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1542 if not(fvalue == self:cfgvalue(section)) then
1543 self:write(section, fvalue)
1546 self:remove(section)
1553 ListValue - A one-line value predefined in a list
1554 widget: The widget that will be used (select, radio)
1556 ListValue = class(AbstractValue)
1558 function ListValue.__init__(self, ...)
1559 AbstractValue.__init__(self, ...)
1560 self.template = "cbi/lvalue"
1565 self.widget = "select"
1568 function ListValue.prepare(self, ...)
1569 AbstractValue.prepare(self, ...)
1570 if not self.override_scheme
1571 and self.map:get_scheme(self.section.sectiontype, self.option) then
1572 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1573 if self.value and vs.valuelist and not self.override_values then
1574 for k, v in ipairs(vs.valuelist) do
1576 if not self.override_dependencies
1577 and vs.enum_depends and vs.enum_depends[v.value] then
1578 for i, dep in ipairs(vs.enum_depends[v.value]) do
1579 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1582 self:value(v.value, v.title or v.value, unpack(deps))
1588 function ListValue.value(self, key, val, ...)
1589 if luci.util.contains(self.keylist, key) then
1594 table.insert(self.keylist, tostring(key))
1595 table.insert(self.vallist, tostring(val))
1597 for i, deps in ipairs({...}) do
1598 table.insert(self.deps, {add = "-"..key, deps=deps})
1602 function ListValue.validate(self, val)
1603 if luci.util.contains(self.keylist, val) then
1613 MultiValue - Multiple delimited values
1614 widget: The widget that will be used (select, checkbox)
1615 delimiter: The delimiter that will separate the values (default: " ")
1617 MultiValue = class(AbstractValue)
1619 function MultiValue.__init__(self, ...)
1620 AbstractValue.__init__(self, ...)
1621 self.template = "cbi/mvalue"
1626 self.widget = "checkbox"
1627 self.delimiter = " "
1630 function MultiValue.render(self, ...)
1631 if self.widget == "select" and not self.size then
1632 self.size = #self.vallist
1635 AbstractValue.render(self, ...)
1638 function MultiValue.value(self, key, val)
1639 if luci.util.contains(self.keylist, key) then
1644 table.insert(self.keylist, tostring(key))
1645 table.insert(self.vallist, tostring(val))
1648 function MultiValue.valuelist(self, section)
1649 local val = self:cfgvalue(section)
1651 if not(type(val) == "string") then
1655 return luci.util.split(val, self.delimiter)
1658 function MultiValue.validate(self, val)
1659 val = (type(val) == "table") and val or {val}
1663 for i, value in ipairs(val) do
1664 if luci.util.contains(self.keylist, value) then
1665 result = result and (result .. self.delimiter .. value) or value
1673 StaticList = class(MultiValue)
1675 function StaticList.__init__(self, ...)
1676 MultiValue.__init__(self, ...)
1678 self.valuelist = self.cfgvalue
1680 if not self.override_scheme
1681 and self.map:get_scheme(self.section.sectiontype, self.option) then
1682 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1683 if self.value and vs.values and not self.override_values then
1684 for k, v in pairs(vs.values) do
1691 function StaticList.validate(self, value)
1692 value = (type(value) == "table") and value or {value}
1695 for i, v in ipairs(value) do
1696 if luci.util.contains(self.keylist, v) then
1697 table.insert(valid, v)
1704 DynamicList = class(AbstractValue)
1706 function DynamicList.__init__(self, ...)
1707 AbstractValue.__init__(self, ...)
1708 self.template = "cbi/dynlist"
1714 function DynamicList.value(self, key, val)
1716 table.insert(self.keylist, tostring(key))
1717 table.insert(self.vallist, tostring(val))
1720 function DynamicList.write(self, ...)
1721 self.map.proceed = true
1722 return AbstractValue.write(self, ...)
1725 function DynamicList.formvalue(self, section)
1726 local value = AbstractValue.formvalue(self, section)
1727 value = (type(value) == "table") and value or {value}
1730 for i, v in ipairs(value) do
1732 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1733 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1734 table.insert(valid, v)
1743 TextValue - A multi-line value
1746 TextValue = class(AbstractValue)
1748 function TextValue.__init__(self, ...)
1749 AbstractValue.__init__(self, ...)
1750 self.template = "cbi/tvalue"
1756 Button = class(AbstractValue)
1758 function Button.__init__(self, ...)
1759 AbstractValue.__init__(self, ...)
1760 self.template = "cbi/button"
1761 self.inputstyle = nil
1766 FileUpload = class(AbstractValue)
1768 function FileUpload.__init__(self, ...)
1769 AbstractValue.__init__(self, ...)
1770 self.template = "cbi/upload"
1771 if not self.map.upload_fields then
1772 self.map.upload_fields = { self }
1774 self.map.upload_fields[#self.map.upload_fields+1] = self
1778 function FileUpload.formcreated(self, section)
1779 return AbstractValue.formcreated(self, section) or
1780 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1781 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1784 function FileUpload.cfgvalue(self, section)
1785 local val = AbstractValue.cfgvalue(self, section)
1786 if val and fs.access(val) then
1792 function FileUpload.formvalue(self, section)
1793 local val = AbstractValue.formvalue(self, section)
1795 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1796 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1806 function FileUpload.remove(self, section)
1807 local val = AbstractValue.formvalue(self, section)
1808 if val and fs.access(val) then fs.unlink(val) end
1809 return AbstractValue.remove(self, section)
1813 FileBrowser = class(AbstractValue)
1815 function FileBrowser.__init__(self, ...)
1816 AbstractValue.__init__(self, ...)
1817 self.template = "cbi/browser"