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 = {}
1269 --self.cast = "string"
1271 self.track_missing = false
1275 self.optional = false
1278 function AbstractValue.prepare(self)
1279 -- Use defaults from UVL
1280 if not self.override_scheme
1281 and self.map:get_scheme(self.section.sectiontype, self.option) then
1282 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1283 if self.cast == nil then
1284 self.cast = (vs.type == "list") and "list" or "string"
1286 self.title = self.title or vs.title
1287 self.description = self.description or vs.descr
1288 if self.default == nil then
1289 self.default = vs.default
1292 if vs.depends and not self.override_dependencies then
1293 for i, deps in ipairs(vs.depends) do
1294 deps = _uvl_strip_remote_dependencies(deps)
1302 self.cast = self.cast or "string"
1305 -- Add a dependencie to another section field
1306 function AbstractValue.depends(self, field, value)
1308 if type(field) == "string" then
1315 table.insert(self.deps, {deps=deps, add=""})
1318 -- Generates the unique CBID
1319 function AbstractValue.cbid(self, section)
1320 return "cbid."..self.map.config.."."..section.."."..self.option
1323 -- Return whether this object should be created
1324 function AbstractValue.formcreated(self, section)
1325 local key = "cbi.opt."..self.config.."."..section
1326 return (self.map:formvalue(key) == self.option)
1329 -- Returns the formvalue for this object
1330 function AbstractValue.formvalue(self, section)
1331 return self.map:formvalue(self:cbid(section))
1334 function AbstractValue.additional(self, value)
1335 self.optional = value
1338 function AbstractValue.mandatory(self, value)
1339 self.rmempty = not value
1342 function AbstractValue.parse(self, section, novld)
1343 local fvalue = self:formvalue(section)
1344 local cvalue = self:cfgvalue(section)
1346 -- If favlue and cvalue are both tables and have the same content
1347 -- make them identical
1348 if type(fvalue) == "table" and type(cvalue) == "table" then
1349 local equal = #fvalue == #cvalue
1352 if cvalue[i] ~= fvalue[i] then
1362 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1363 fvalue = self:transform(self:validate(fvalue, section))
1364 if not fvalue and not novld then
1366 self.error[section] = "invalid"
1368 self.error = { [section] = "invalid" }
1370 if self.section.error then
1371 table.insert(self.section.error[section], "invalid")
1373 self.section.error = {[section] = {"invalid"}}
1375 self.map.save = false
1377 if fvalue and not (fvalue == cvalue) then
1378 if self:write(section, fvalue) then
1380 self.section.changed = true
1381 --luci.util.append(self.map.events, self.events)
1384 else -- Unset the UCI or error
1385 if self.rmempty or self.optional then
1386 if self:remove(section) then
1388 self.section.changed = true
1389 --luci.util.append(self.map.events, self.events)
1391 elseif cvalue ~= fvalue and not novld then
1392 self:write(section, fvalue or "")
1394 self.error[section] = "missing"
1396 self.error = { [section] = "missing" }
1398 self.map.save = false
1403 -- Render if this value exists or if it is mandatory
1404 function AbstractValue.render(self, s, scope)
1405 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1408 scope.cbid = self:cbid(s)
1409 scope.striptags = luci.util.striptags
1410 scope.pcdata = luci.util.pcdata
1412 scope.ifattr = function(cond,key,val)
1414 return string.format(
1415 ' %s="%s"', tostring(key),
1416 luci.util.pcdata(tostring( val
1418 or (type(self[key]) ~= "function" and self[key])
1426 scope.attr = function(...)
1427 return scope.ifattr( true, ... )
1430 Node.render(self, scope)
1434 -- Return the UCI value of this object
1435 function AbstractValue.cfgvalue(self, section)
1436 local value = self.map:get(section, self.option)
1439 elseif not self.cast or self.cast == type(value) then
1441 elseif self.cast == "string" then
1442 if type(value) == "table" then
1445 elseif self.cast == "table" then
1446 return luci.util.split(value, "%s+", nil, true)
1450 -- Validate the form value
1451 function AbstractValue.validate(self, value)
1455 AbstractValue.transform = AbstractValue.validate
1459 function AbstractValue.write(self, section, value)
1460 return self.map:set(section, self.option, value)
1464 function AbstractValue.remove(self, section)
1465 return self.map:del(section, self.option)
1472 Value - A one-line value
1473 maxlength: The maximum length
1475 Value = class(AbstractValue)
1477 function Value.__init__(self, ...)
1478 AbstractValue.__init__(self, ...)
1479 self.template = "cbi/value"
1484 function Value.value(self, key, val)
1486 table.insert(self.keylist, tostring(key))
1487 table.insert(self.vallist, tostring(val))
1491 -- DummyValue - This does nothing except being there
1492 DummyValue = class(AbstractValue)
1494 function DummyValue.__init__(self, ...)
1495 AbstractValue.__init__(self, ...)
1496 self.template = "cbi/dvalue"
1500 function DummyValue.cfgvalue(self, section)
1503 if type(self.value) == "function" then
1504 value = self:value(section)
1509 value = AbstractValue.cfgvalue(self, section)
1514 function DummyValue.parse(self)
1520 Flag - A flag being enabled or disabled
1522 Flag = class(AbstractValue)
1524 function Flag.__init__(self, ...)
1525 AbstractValue.__init__(self, ...)
1526 self.template = "cbi/fvalue"
1532 -- A flag can only have two states: set or unset
1533 function Flag.parse(self, section)
1534 local fvalue = self:formvalue(section)
1537 fvalue = self.enabled
1539 fvalue = self.disabled
1542 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1543 if not(fvalue == self:cfgvalue(section)) then
1544 self:write(section, fvalue)
1547 self:remove(section)
1554 ListValue - A one-line value predefined in a list
1555 widget: The widget that will be used (select, radio)
1557 ListValue = class(AbstractValue)
1559 function ListValue.__init__(self, ...)
1560 AbstractValue.__init__(self, ...)
1561 self.template = "cbi/lvalue"
1566 self.widget = "select"
1569 function ListValue.prepare(self, ...)
1570 AbstractValue.prepare(self, ...)
1571 if not self.override_scheme
1572 and self.map:get_scheme(self.section.sectiontype, self.option) then
1573 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1574 if self.value and vs.valuelist and not self.override_values then
1575 for k, v in ipairs(vs.valuelist) do
1577 if not self.override_dependencies
1578 and vs.enum_depends and vs.enum_depends[v.value] then
1579 for i, dep in ipairs(vs.enum_depends[v.value]) do
1580 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1583 self:value(v.value, v.title or v.value, unpack(deps))
1589 function ListValue.value(self, key, val, ...)
1590 if luci.util.contains(self.keylist, key) then
1595 table.insert(self.keylist, tostring(key))
1596 table.insert(self.vallist, tostring(val))
1598 for i, deps in ipairs({...}) do
1599 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1603 function ListValue.validate(self, val)
1604 if luci.util.contains(self.keylist, val) then
1614 MultiValue - Multiple delimited values
1615 widget: The widget that will be used (select, checkbox)
1616 delimiter: The delimiter that will separate the values (default: " ")
1618 MultiValue = class(AbstractValue)
1620 function MultiValue.__init__(self, ...)
1621 AbstractValue.__init__(self, ...)
1622 self.template = "cbi/mvalue"
1627 self.widget = "checkbox"
1628 self.delimiter = " "
1631 function MultiValue.render(self, ...)
1632 if self.widget == "select" and not self.size then
1633 self.size = #self.vallist
1636 AbstractValue.render(self, ...)
1639 function MultiValue.value(self, key, val)
1640 if luci.util.contains(self.keylist, key) then
1645 table.insert(self.keylist, tostring(key))
1646 table.insert(self.vallist, tostring(val))
1649 function MultiValue.valuelist(self, section)
1650 local val = self:cfgvalue(section)
1652 if not(type(val) == "string") then
1656 return luci.util.split(val, self.delimiter)
1659 function MultiValue.validate(self, val)
1660 val = (type(val) == "table") and val or {val}
1664 for i, value in ipairs(val) do
1665 if luci.util.contains(self.keylist, value) then
1666 result = result and (result .. self.delimiter .. value) or value
1674 StaticList = class(MultiValue)
1676 function StaticList.__init__(self, ...)
1677 MultiValue.__init__(self, ...)
1679 self.valuelist = self.cfgvalue
1681 if not self.override_scheme
1682 and self.map:get_scheme(self.section.sectiontype, self.option) then
1683 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1684 if self.value and vs.values and not self.override_values then
1685 for k, v in pairs(vs.values) do
1692 function StaticList.validate(self, value)
1693 value = (type(value) == "table") and value or {value}
1696 for i, v in ipairs(value) do
1697 if luci.util.contains(self.keylist, v) then
1698 table.insert(valid, v)
1705 DynamicList = class(AbstractValue)
1707 function DynamicList.__init__(self, ...)
1708 AbstractValue.__init__(self, ...)
1709 self.template = "cbi/dynlist"
1715 function DynamicList.value(self, key, val)
1717 table.insert(self.keylist, tostring(key))
1718 table.insert(self.vallist, tostring(val))
1721 function DynamicList.write(self, ...)
1722 self.map.proceed = true
1723 return AbstractValue.write(self, ...)
1726 function DynamicList.formvalue(self, section)
1727 local value = AbstractValue.formvalue(self, section)
1728 value = (type(value) == "table") and value or {value}
1731 for i, v in ipairs(value) do
1733 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1734 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1735 table.insert(valid, v)
1744 TextValue - A multi-line value
1747 TextValue = class(AbstractValue)
1749 function TextValue.__init__(self, ...)
1750 AbstractValue.__init__(self, ...)
1751 self.template = "cbi/tvalue"
1757 Button = class(AbstractValue)
1759 function Button.__init__(self, ...)
1760 AbstractValue.__init__(self, ...)
1761 self.template = "cbi/button"
1762 self.inputstyle = nil
1767 FileUpload = class(AbstractValue)
1769 function FileUpload.__init__(self, ...)
1770 AbstractValue.__init__(self, ...)
1771 self.template = "cbi/upload"
1772 if not self.map.upload_fields then
1773 self.map.upload_fields = { self }
1775 self.map.upload_fields[#self.map.upload_fields+1] = self
1779 function FileUpload.formcreated(self, section)
1780 return AbstractValue.formcreated(self, section) or
1781 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1782 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1785 function FileUpload.cfgvalue(self, section)
1786 local val = AbstractValue.cfgvalue(self, section)
1787 if val and fs.access(val) then
1793 function FileUpload.formvalue(self, section)
1794 local val = AbstractValue.formvalue(self, section)
1796 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1797 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1807 function FileUpload.remove(self, section)
1808 local val = AbstractValue.formvalue(self, section)
1809 if val and fs.access(val) then fs.unlink(val) end
1810 return AbstractValue.remove(self, section)
1814 FileBrowser = class(AbstractValue)
1816 function FileBrowser.__init__(self, ...)
1817 AbstractValue.__init__(self, ...)
1818 self.template = "cbi/browser"