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_hook(self, hook)
226 if type(self[hook]) == "function" then
227 return self[hook](self)
231 function Node._run_hooks(self, ...)
234 for _, f in ipairs(arg) do
235 if type(self[f]) == "function" then
244 function Node.prepare(self, ...)
245 for k, child in ipairs(self.children) do
250 -- Append child nodes
251 function Node.append(self, obj)
252 table.insert(self.children, obj)
255 -- Parse this node and its children
256 function Node.parse(self, ...)
257 for k, child in ipairs(self.children) do
263 function Node.render(self, scope)
267 luci.template.render(self.template, scope)
270 -- Render the children
271 function Node.render_children(self, ...)
272 for k, node in ipairs(self.children) do
279 A simple template element
281 Template = class(Node)
283 function Template.__init__(self, template)
285 self.template = template
288 function Template.render(self)
289 luci.template.render(self.template, {self=self})
292 function Template.parse(self, readinput)
293 self.readinput = (readinput ~= false)
294 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
299 Map - A map describing a configuration file
303 function Map.__init__(self, config, ...)
304 Node.__init__(self, ...)
307 self.parsechain = {self.config}
308 self.template = "cbi/map"
309 self.apply_on_parse = nil
310 self.readinput = true
314 self.uci = uci.cursor()
319 if not self.uci:load(self.config) then
320 error("Unable to read UCI data: " .. self.config)
323 self.validator = luci.uvl.UVL()
324 self.scheme = self.validator:get_scheme(self.config)
327 function Map.formvalue(self, key)
328 return self.readinput and luci.http.formvalue(key)
331 function Map.formvaluetable(self, key)
332 return self.readinput and luci.http.formvaluetable(key) or {}
335 function Map.get_scheme(self, sectiontype, option)
337 return self.scheme and self.scheme.sections[sectiontype]
339 return self.scheme and self.scheme.variables[sectiontype]
340 and self.scheme.variables[sectiontype][option]
344 function Map.submitstate(self)
345 return self:formvalue("cbi.submit")
348 -- Chain foreign config
349 function Map.chain(self, config)
350 table.insert(self.parsechain, config)
353 function Map.state_handler(self, state)
357 -- Use optimized UCI writing
358 function Map.parse(self, readinput, ...)
359 self.readinput = (readinput ~= false)
360 self:_run_hooks("on_parse")
362 if self:formvalue("cbi.skip") then
363 self.state = FORM_SKIP
364 return self:state_handler(self.state)
367 Node.parse(self, ...)
370 for i, config in ipairs(self.parsechain) do
371 self.uci:save(config)
373 if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
374 self:_run_hooks("on_before_commit")
375 for i, config in ipairs(self.parsechain) do
376 self.uci:commit(config)
378 -- Refresh data because commit changes section names
379 self.uci:load(config)
381 self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
382 if self.apply_on_parse then
383 self.uci:apply(self.parsechain)
384 self:_run_hooks("on_apply", "on_after_apply")
386 self._apply = function()
387 local cmd = self.uci:apply(self.parsechain, true)
393 Node.parse(self, true)
396 for i, config in ipairs(self.parsechain) do
397 self.uci:unload(config)
399 if type(self.commit_handler) == "function" then
400 self:commit_handler(self:submitstate())
404 if self:submitstate() then
405 if not self.save then
406 self.state = FORM_INVALID
407 elseif self.proceed then
408 self.state = FORM_PROCEED
410 self.state = self.changed and FORM_CHANGED or FORM_VALID
413 self.state = FORM_NODATA
416 return self:state_handler(self.state)
419 function Map.render(self, ...)
420 self:_run_hooks("on_init")
421 Node.render(self, ...)
423 local fp = self._apply()
426 self:_run_hooks("on_apply")
430 -- Creates a child section
431 function Map.section(self, class, ...)
432 if instanceof(class, AbstractSection) then
433 local obj = class(self, ...)
437 error("class must be a descendent of AbstractSection")
442 function Map.add(self, sectiontype)
443 return self.uci:add(self.config, sectiontype)
447 function Map.set(self, section, option, value)
449 return self.uci:set(self.config, section, option, value)
451 return self.uci:set(self.config, section, value)
456 function Map.del(self, section, option)
458 return self.uci:delete(self.config, section, option)
460 return self.uci:delete(self.config, section)
465 function Map.get(self, section, option)
467 return self.uci:get_all(self.config)
469 return self.uci:get(self.config, section, option)
471 return self.uci:get_all(self.config, section)
478 Compound = class(Node)
480 function Compound.__init__(self, ...)
482 self.template = "cbi/compound"
483 self.children = {...}
486 function Compound.populate_delegator(self, delegator)
487 for _, v in ipairs(self.children) do
488 v.delegator = delegator
492 function Compound.parse(self, ...)
493 local cstate, state = 0
495 for k, child in ipairs(self.children) do
496 cstate = child:parse(...)
497 state = (not state or cstate < state) and cstate or state
505 Delegator - Node controller
507 Delegator = class(Node)
508 function Delegator.__init__(self, ...)
509 Node.__init__(self, ...)
511 self.defaultpath = {}
512 self.pageaction = false
513 self.readinput = true
514 self.allow_reset = false
515 self.allow_cancel = false
516 self.allow_back = false
517 self.allow_finish = false
518 self.template = "cbi/delegator"
521 function Delegator.set(self, name, node)
522 assert(not self.nodes[name], "Duplicate entry")
524 self.nodes[name] = node
527 function Delegator.add(self, name, node)
528 node = self:set(name, node)
529 self.defaultpath[#self.defaultpath+1] = name
532 function Delegator.insert_after(self, name, after)
533 local n = #self.chain + 1
534 for k, v in ipairs(self.chain) do
540 table.insert(self.chain, n, name)
543 function Delegator.set_route(self, ...)
544 local n, chain, route = 0, self.chain, {...}
546 if chain[i] == self.current then
555 for i = n + 1, #chain do
560 function Delegator.get(self, name)
561 local node = self.nodes[name]
563 if type(node) == "string" then
567 if type(node) == "table" and getmetatable(node) == nil then
568 node = Compound(unpack(node))
574 function Delegator.parse(self, ...)
575 if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then
576 if self:_run_hooks("on_cancel") then
581 if not Map.formvalue(self, "cbi.delg.current") then
582 self:_run_hooks("on_init")
586 self.chain = self.chain or self:get_chain()
587 self.current = self.current or self:get_active()
588 self.active = self.active or self:get(self.current)
589 assert(self.active, "Invalid state")
591 local stat = FORM_DONE
592 if type(self.active) ~= "function" then
593 self.active:populate_delegator(self)
594 stat = self.active:parse()
599 if stat > FORM_PROCEED then
600 if Map.formvalue(self, "cbi.delg.back") then
601 newcurrent = self:get_prev(self.current)
603 newcurrent = self:get_next(self.current)
605 elseif stat < FORM_PROCEED then
610 if not Map.formvalue(self, "cbi.submit") then
612 elseif stat > FORM_PROCEED
613 and (not newcurrent or not self:get(newcurrent)) then
614 return self:_run_hook("on_done") or FORM_DONE
616 self.current = newcurrent or self.current
617 self.active = self:get(self.current)
618 if type(self.active) ~= "function" then
619 self.active:parse(false)
622 return self:parse(...)
627 function Delegator.get_next(self, state)
628 for k, v in ipairs(self.chain) do
630 return self.chain[k+1]
635 function Delegator.get_prev(self, state)
636 for k, v in ipairs(self.chain) do
638 return self.chain[k-1]
643 function Delegator.get_chain(self)
644 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
645 return type(x) == "table" and x or {x}
648 function Delegator.get_active(self)
649 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
657 Page.__init__ = Node.__init__
658 Page.parse = function() end
662 SimpleForm - A Simple non-UCI form
664 SimpleForm = class(Node)
666 function SimpleForm.__init__(self, config, title, description, data)
667 Node.__init__(self, title, description)
669 self.data = data or {}
670 self.template = "cbi/simpleform"
672 self.pageaction = false
673 self.readinput = true
676 SimpleForm.formvalue = Map.formvalue
677 SimpleForm.formvaluetable = Map.formvaluetable
679 function SimpleForm.parse(self, readinput, ...)
680 self.readinput = (readinput ~= false)
682 if self:formvalue("cbi.skip") then
686 if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then
690 if self:submitstate() then
691 Node.parse(self, 1, ...)
695 for k, j in ipairs(self.children) do
696 for i, v in ipairs(j.children) do
698 and (not v.tag_missing or not v.tag_missing[1])
699 and (not v.tag_invalid or not v.tag_invalid[1])
705 not self:submitstate() and FORM_NODATA
706 or valid and FORM_VALID
709 self.dorender = not self.handle
711 local nrender, nstate = self:handle(state, self.data)
712 self.dorender = self.dorender or (nrender ~= false)
713 state = nstate or state
718 function SimpleForm.render(self, ...)
719 if self.dorender then
720 Node.render(self, ...)
724 function SimpleForm.submitstate(self)
725 return self:formvalue("cbi.submit")
728 function SimpleForm.section(self, class, ...)
729 if instanceof(class, AbstractSection) then
730 local obj = class(self, ...)
734 error("class must be a descendent of AbstractSection")
738 -- Creates a child field
739 function SimpleForm.field(self, class, ...)
741 for k, v in ipairs(self.children) do
742 if instanceof(v, SimpleSection) then
748 section = self:section(SimpleSection)
751 if instanceof(class, AbstractValue) then
752 local obj = class(self, section, ...)
753 obj.track_missing = true
757 error("class must be a descendent of AbstractValue")
761 function SimpleForm.set(self, section, option, value)
762 self.data[option] = value
766 function SimpleForm.del(self, section, option)
767 self.data[option] = nil
771 function SimpleForm.get(self, section, option)
772 return self.data[option]
776 function SimpleForm.get_scheme()
781 Form = class(SimpleForm)
783 function Form.__init__(self, ...)
784 SimpleForm.__init__(self, ...)
792 AbstractSection = class(Node)
794 function AbstractSection.__init__(self, map, sectiontype, ...)
795 Node.__init__(self, ...)
796 self.sectiontype = sectiontype
798 self.config = map.config
803 self.tag_invalid = {}
804 self.tag_deperror = {}
808 self.addremove = false
812 -- Define a tab for the section
813 function AbstractSection.tab(self, tab, title, desc)
814 self.tabs = self.tabs or { }
815 self.tab_names = self.tab_names or { }
817 self.tab_names[#self.tab_names+1] = tab
825 -- Appends a new option
826 function AbstractSection.option(self, class, option, ...)
827 -- Autodetect from UVL
828 if class == true and self.map:get_scheme(self.sectiontype, option) then
829 local vs = self.map:get_scheme(self.sectiontype, option)
830 if vs.type == "boolean" then
832 elseif vs.type == "list" then
834 elseif vs.type == "enum" or vs.type == "reference" then
841 if instanceof(class, AbstractValue) then
842 local obj = class(self.map, self, option, ...)
844 self.fields[option] = obj
846 elseif class == true then
847 error("No valid class was given and autodetection failed.")
849 error("class must be a descendant of AbstractValue")
853 -- Appends a new tabbed option
854 function AbstractSection.taboption(self, tab, ...)
856 assert(tab and self.tabs and self.tabs[tab],
857 "Cannot assign option to not existing tab %q" % tostring(tab))
859 local l = self.tabs[tab].childs
860 local o = AbstractSection.option(self, ...)
862 if o then l[#l+1] = o end
867 -- Render a single tab
868 function AbstractSection.render_tab(self, tab, ...)
870 assert(tab and self.tabs and self.tabs[tab],
871 "Cannot render not existing tab %q" % tostring(tab))
873 for _, node in ipairs(self.tabs[tab].childs) do
878 -- Parse optional options
879 function AbstractSection.parse_optionals(self, section)
880 if not self.optional then
884 self.optionals[section] = {}
886 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
887 for k,v in ipairs(self.children) do
888 if v.optional and not v:cfgvalue(section) then
889 if field == v.option then
891 self.map.proceed = true
893 table.insert(self.optionals[section], v)
898 if field and #field > 0 and self.dynamic then
899 self:add_dynamic(field)
903 -- Add a dynamic option
904 function AbstractSection.add_dynamic(self, field, optional)
905 local o = self:option(Value, field, field)
906 o.optional = optional
909 -- Parse all dynamic options
910 function AbstractSection.parse_dynamic(self, section)
911 if not self.dynamic then
915 local arr = luci.util.clone(self:cfgvalue(section))
916 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
917 for k, v in pairs(form) do
921 for key,val in pairs(arr) do
924 for i,c in ipairs(self.children) do
925 if c.option == key then
930 if create and key:sub(1, 1) ~= "." then
931 self.map.proceed = true
932 self:add_dynamic(key, true)
937 -- Returns the section's UCI table
938 function AbstractSection.cfgvalue(self, section)
939 return self.map:get(section)
943 function AbstractSection.push_events(self)
944 --luci.util.append(self.map.events, self.events)
945 self.map.changed = true
948 -- Removes the section
949 function AbstractSection.remove(self, section)
950 self.map.proceed = true
951 return self.map:del(section)
954 -- Creates the section
955 function AbstractSection.create(self, section)
959 stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype)
961 section = self.map:add(self.sectiontype)
966 for k,v in pairs(self.children) do
968 self.map:set(section, v.option, v.default)
972 for k,v in pairs(self.defaults) do
973 self.map:set(section, k, v)
977 self.map.proceed = true
983 SimpleSection = class(AbstractSection)
985 function SimpleSection.__init__(self, form, ...)
986 AbstractSection.__init__(self, form, nil, ...)
987 self.template = "cbi/nullsection"
991 Table = class(AbstractSection)
993 function Table.__init__(self, form, data, ...)
994 local datasource = {}
996 datasource.config = "table"
997 self.data = data or {}
999 datasource.formvalue = Map.formvalue
1000 datasource.formvaluetable = Map.formvaluetable
1001 datasource.readinput = true
1003 function datasource.get(self, section, option)
1004 return tself.data[section] and tself.data[section][option]
1007 function datasource.submitstate(self)
1008 return Map.formvalue(self, "cbi.submit")
1011 function datasource.del(...)
1015 function datasource.get_scheme()
1019 AbstractSection.__init__(self, datasource, "table", ...)
1020 self.template = "cbi/tblsection"
1021 self.rowcolors = true
1022 self.anonymous = true
1025 function Table.parse(self, readinput)
1026 self.map.readinput = (readinput ~= false)
1027 for i, k in ipairs(self:cfgsections()) do
1028 if self.map:submitstate() then
1034 function Table.cfgsections(self)
1037 for i, v in luci.util.kspairs(self.data) do
1038 table.insert(sections, i)
1044 function Table.update(self, data)
1051 NamedSection - A fixed configuration section defined by its name
1053 NamedSection = class(AbstractSection)
1055 function NamedSection.__init__(self, map, section, stype, ...)
1056 AbstractSection.__init__(self, map, stype, ...)
1059 self.addremove = false
1061 -- Use defaults from UVL
1062 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1063 local vs = self.map:get_scheme(self.sectiontype)
1064 self.addremove = not vs.unique and not vs.required
1065 self.dynamic = vs.dynamic
1066 self.title = self.title or vs.title
1067 self.description = self.description or vs.descr
1070 self.template = "cbi/nsection"
1071 self.section = section
1074 function NamedSection.parse(self, novld)
1075 local s = self.section
1076 local active = self:cfgvalue(s)
1078 if self.addremove then
1079 local path = self.config.."."..s
1080 if active then -- Remove the section
1081 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1085 else -- Create and apply default values
1086 if self.map:formvalue("cbi.cns."..path) then
1094 AbstractSection.parse_dynamic(self, s)
1095 if self.map:submitstate() then
1098 if not novld and not self.override_scheme and self.map.scheme then
1099 _uvl_validate_section(self, s)
1102 AbstractSection.parse_optionals(self, s)
1104 if self.changed then
1112 TypedSection - A (set of) configuration section(s) defined by the type
1113 addremove: Defines whether the user can add/remove sections of this type
1114 anonymous: Allow creating anonymous sections
1115 validate: a validation function returning nil if the section is invalid
1117 TypedSection = class(AbstractSection)
1119 function TypedSection.__init__(self, map, type, ...)
1120 AbstractSection.__init__(self, map, type, ...)
1122 self.template = "cbi/tsection"
1124 self.anonymous = false
1126 -- Use defaults from UVL
1127 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1128 local vs = self.map:get_scheme(self.sectiontype)
1129 self.addremove = not vs.unique and not vs.required
1130 self.dynamic = vs.dynamic
1131 self.anonymous = not vs.named
1132 self.title = self.title or vs.title
1133 self.description = self.description or vs.descr
1137 -- Return all matching UCI sections for this TypedSection
1138 function TypedSection.cfgsections(self)
1140 self.map.uci:foreach(self.map.config, self.sectiontype,
1142 if self:checkscope(section[".name"]) then
1143 table.insert(sections, section[".name"])
1150 -- Limits scope to sections that have certain option => value pairs
1151 function TypedSection.depends(self, option, value)
1152 table.insert(self.deps, {option=option, value=value})
1155 function TypedSection.parse(self, novld)
1156 if self.addremove then
1158 local crval = REMOVE_PREFIX .. self.config
1159 local name = self.map:formvaluetable(crval)
1160 for k,v in pairs(name) do
1161 if k:sub(-2) == ".x" then
1162 k = k:sub(1, #k - 2)
1164 if self:cfgvalue(k) and self:checkscope(k) then
1171 for i, k in ipairs(self:cfgsections()) do
1172 AbstractSection.parse_dynamic(self, k)
1173 if self.map:submitstate() then
1174 Node.parse(self, k, novld)
1176 if not novld and not self.override_scheme and self.map.scheme then
1177 _uvl_validate_section(self, k)
1180 AbstractSection.parse_optionals(self, k)
1183 if self.addremove then
1186 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1187 local name = self.map:formvalue(crval)
1188 if self.anonymous then
1190 created = self:create()
1194 -- Ignore if it already exists
1195 if self:cfgvalue(name) then
1199 name = self:checkscope(name)
1202 self.err_invalid = true
1205 if name and #name > 0 then
1206 created = self:create(name) and name
1208 self.invalid_cts = true
1215 AbstractSection.parse_optionals(self, created)
1219 if created or self.changed then
1224 -- Verifies scope of sections
1225 function TypedSection.checkscope(self, section)
1226 -- Check if we are not excluded
1227 if self.filter and not self:filter(section) then
1231 -- Check if at least one dependency is met
1232 if #self.deps > 0 and self:cfgvalue(section) then
1235 for k, v in ipairs(self.deps) do
1236 if self:cfgvalue(section)[v.option] == v.value then
1246 return self:validate(section)
1250 -- Dummy validate function
1251 function TypedSection.validate(self, section)
1257 AbstractValue - An abstract Value Type
1258 null: Value can be empty
1259 valid: A function returning the value if it is valid otherwise nil
1260 depends: A table of option => value pairs of which one must be true
1261 default: The default value
1262 size: The size of the input fields
1263 rmempty: Unset value if empty
1264 optional: This value is optional (see AbstractSection.optionals)
1266 AbstractValue = class(Node)
1268 function AbstractValue.__init__(self, map, section, option, ...)
1269 Node.__init__(self, ...)
1270 self.section = section
1271 self.option = option
1273 self.config = map.config
1274 self.tag_invalid = {}
1275 self.tag_missing = {}
1276 self.tag_reqerror = {}
1280 --self.cast = "string"
1282 self.track_missing = false
1286 self.optional = false
1289 function AbstractValue.prepare(self)
1290 -- Use defaults from UVL
1291 if not self.override_scheme
1292 and self.map:get_scheme(self.section.sectiontype, self.option) then
1293 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1294 if self.cast == nil then
1295 self.cast = (vs.type == "list") and "list" or "string"
1297 self.title = self.title or vs.title
1298 self.description = self.description or vs.descr
1299 if self.default == nil then
1300 self.default = vs.default
1303 if vs.depends and not self.override_dependencies then
1304 for i, deps in ipairs(vs.depends) do
1305 deps = _uvl_strip_remote_dependencies(deps)
1313 self.cast = self.cast or "string"
1316 -- Add a dependencie to another section field
1317 function AbstractValue.depends(self, field, value)
1319 if type(field) == "string" then
1326 table.insert(self.deps, {deps=deps, add=""})
1329 -- Generates the unique CBID
1330 function AbstractValue.cbid(self, section)
1331 return "cbid."..self.map.config.."."..section.."."..self.option
1334 -- Return whether this object should be created
1335 function AbstractValue.formcreated(self, section)
1336 local key = "cbi.opt."..self.config.."."..section
1337 return (self.map:formvalue(key) == self.option)
1340 -- Returns the formvalue for this object
1341 function AbstractValue.formvalue(self, section)
1342 return self.map:formvalue(self:cbid(section))
1345 function AbstractValue.additional(self, value)
1346 self.optional = value
1349 function AbstractValue.mandatory(self, value)
1350 self.rmempty = not value
1353 function AbstractValue.parse(self, section, novld)
1354 local fvalue = self:formvalue(section)
1355 local cvalue = self:cfgvalue(section)
1357 -- If favlue and cvalue are both tables and have the same content
1358 -- make them identical
1359 if type(fvalue) == "table" and type(cvalue) == "table" then
1360 local equal = #fvalue == #cvalue
1363 if cvalue[i] ~= fvalue[i] then
1373 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1374 fvalue = self:transform(self:validate(fvalue, section))
1375 if not fvalue and not novld then
1377 self.error[section] = "invalid"
1379 self.error = { [section] = "invalid" }
1381 if self.section.error then
1382 table.insert(self.section.error[section], "invalid")
1384 self.section.error = {[section] = {"invalid"}}
1386 self.map.save = false
1388 if fvalue and not (fvalue == cvalue) then
1389 if self:write(section, fvalue) then
1391 self.section.changed = true
1392 --luci.util.append(self.map.events, self.events)
1395 else -- Unset the UCI or error
1396 if self.rmempty or self.optional then
1397 if self:remove(section) then
1399 self.section.changed = true
1400 --luci.util.append(self.map.events, self.events)
1402 elseif cvalue ~= fvalue and not novld then
1403 self:write(section, fvalue or "")
1405 self.error[section] = "missing"
1407 self.error = { [section] = "missing" }
1409 self.map.save = false
1414 -- Render if this value exists or if it is mandatory
1415 function AbstractValue.render(self, s, scope)
1416 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1419 scope.cbid = self:cbid(s)
1420 scope.striptags = luci.util.striptags
1421 scope.pcdata = luci.util.pcdata
1423 scope.ifattr = function(cond,key,val)
1425 return string.format(
1426 ' %s="%s"', tostring(key),
1427 luci.util.pcdata(tostring( val
1429 or (type(self[key]) ~= "function" and self[key])
1437 scope.attr = function(...)
1438 return scope.ifattr( true, ... )
1441 Node.render(self, scope)
1445 -- Return the UCI value of this object
1446 function AbstractValue.cfgvalue(self, section)
1447 local value = self.map:get(section, self.option)
1450 elseif not self.cast or self.cast == type(value) then
1452 elseif self.cast == "string" then
1453 if type(value) == "table" then
1456 elseif self.cast == "table" then
1457 return luci.util.split(value, "%s+", nil, true)
1461 -- Validate the form value
1462 function AbstractValue.validate(self, value)
1466 AbstractValue.transform = AbstractValue.validate
1470 function AbstractValue.write(self, section, value)
1471 return self.map:set(section, self.option, value)
1475 function AbstractValue.remove(self, section)
1476 return self.map:del(section, self.option)
1483 Value - A one-line value
1484 maxlength: The maximum length
1486 Value = class(AbstractValue)
1488 function Value.__init__(self, ...)
1489 AbstractValue.__init__(self, ...)
1490 self.template = "cbi/value"
1495 function Value.value(self, key, val)
1497 table.insert(self.keylist, tostring(key))
1498 table.insert(self.vallist, tostring(val))
1502 -- DummyValue - This does nothing except being there
1503 DummyValue = class(AbstractValue)
1505 function DummyValue.__init__(self, ...)
1506 AbstractValue.__init__(self, ...)
1507 self.template = "cbi/dvalue"
1511 function DummyValue.cfgvalue(self, section)
1514 if type(self.value) == "function" then
1515 value = self:value(section)
1520 value = AbstractValue.cfgvalue(self, section)
1525 function DummyValue.parse(self)
1531 Flag - A flag being enabled or disabled
1533 Flag = class(AbstractValue)
1535 function Flag.__init__(self, ...)
1536 AbstractValue.__init__(self, ...)
1537 self.template = "cbi/fvalue"
1543 -- A flag can only have two states: set or unset
1544 function Flag.parse(self, section)
1545 local fvalue = self:formvalue(section)
1548 fvalue = self.enabled
1550 fvalue = self.disabled
1553 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1554 if not(fvalue == self:cfgvalue(section)) then
1555 self:write(section, fvalue)
1558 self:remove(section)
1565 ListValue - A one-line value predefined in a list
1566 widget: The widget that will be used (select, radio)
1568 ListValue = class(AbstractValue)
1570 function ListValue.__init__(self, ...)
1571 AbstractValue.__init__(self, ...)
1572 self.template = "cbi/lvalue"
1577 self.widget = "select"
1580 function ListValue.prepare(self, ...)
1581 AbstractValue.prepare(self, ...)
1582 if not self.override_scheme
1583 and self.map:get_scheme(self.section.sectiontype, self.option) then
1584 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1585 if self.value and vs.valuelist and not self.override_values then
1586 for k, v in ipairs(vs.valuelist) do
1588 if not self.override_dependencies
1589 and vs.enum_depends and vs.enum_depends[v.value] then
1590 for i, dep in ipairs(vs.enum_depends[v.value]) do
1591 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1594 self:value(v.value, v.title or v.value, unpack(deps))
1600 function ListValue.value(self, key, val, ...)
1601 if luci.util.contains(self.keylist, key) then
1606 table.insert(self.keylist, tostring(key))
1607 table.insert(self.vallist, tostring(val))
1609 for i, deps in ipairs({...}) do
1610 self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps}
1614 function ListValue.validate(self, val)
1615 if luci.util.contains(self.keylist, val) then
1625 MultiValue - Multiple delimited values
1626 widget: The widget that will be used (select, checkbox)
1627 delimiter: The delimiter that will separate the values (default: " ")
1629 MultiValue = class(AbstractValue)
1631 function MultiValue.__init__(self, ...)
1632 AbstractValue.__init__(self, ...)
1633 self.template = "cbi/mvalue"
1638 self.widget = "checkbox"
1639 self.delimiter = " "
1642 function MultiValue.render(self, ...)
1643 if self.widget == "select" and not self.size then
1644 self.size = #self.vallist
1647 AbstractValue.render(self, ...)
1650 function MultiValue.value(self, key, val)
1651 if luci.util.contains(self.keylist, key) then
1656 table.insert(self.keylist, tostring(key))
1657 table.insert(self.vallist, tostring(val))
1660 function MultiValue.valuelist(self, section)
1661 local val = self:cfgvalue(section)
1663 if not(type(val) == "string") then
1667 return luci.util.split(val, self.delimiter)
1670 function MultiValue.validate(self, val)
1671 val = (type(val) == "table") and val or {val}
1675 for i, value in ipairs(val) do
1676 if luci.util.contains(self.keylist, value) then
1677 result = result and (result .. self.delimiter .. value) or value
1685 StaticList = class(MultiValue)
1687 function StaticList.__init__(self, ...)
1688 MultiValue.__init__(self, ...)
1690 self.valuelist = self.cfgvalue
1692 if not self.override_scheme
1693 and self.map:get_scheme(self.section.sectiontype, self.option) then
1694 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1695 if self.value and vs.values and not self.override_values then
1696 for k, v in pairs(vs.values) do
1703 function StaticList.validate(self, value)
1704 value = (type(value) == "table") and value or {value}
1707 for i, v in ipairs(value) do
1708 if luci.util.contains(self.keylist, v) then
1709 table.insert(valid, v)
1716 DynamicList = class(AbstractValue)
1718 function DynamicList.__init__(self, ...)
1719 AbstractValue.__init__(self, ...)
1720 self.template = "cbi/dynlist"
1726 function DynamicList.value(self, key, val)
1728 table.insert(self.keylist, tostring(key))
1729 table.insert(self.vallist, tostring(val))
1732 function DynamicList.write(self, ...)
1733 self.map.proceed = true
1734 return AbstractValue.write(self, ...)
1737 function DynamicList.formvalue(self, section)
1738 local value = AbstractValue.formvalue(self, section)
1739 value = (type(value) == "table") and value or {value}
1742 for i, v in ipairs(value) do
1744 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1745 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1746 table.insert(valid, v)
1755 TextValue - A multi-line value
1758 TextValue = class(AbstractValue)
1760 function TextValue.__init__(self, ...)
1761 AbstractValue.__init__(self, ...)
1762 self.template = "cbi/tvalue"
1768 Button = class(AbstractValue)
1770 function Button.__init__(self, ...)
1771 AbstractValue.__init__(self, ...)
1772 self.template = "cbi/button"
1773 self.inputstyle = nil
1778 FileUpload = class(AbstractValue)
1780 function FileUpload.__init__(self, ...)
1781 AbstractValue.__init__(self, ...)
1782 self.template = "cbi/upload"
1783 if not self.map.upload_fields then
1784 self.map.upload_fields = { self }
1786 self.map.upload_fields[#self.map.upload_fields+1] = self
1790 function FileUpload.formcreated(self, section)
1791 return AbstractValue.formcreated(self, section) or
1792 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1793 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1796 function FileUpload.cfgvalue(self, section)
1797 local val = AbstractValue.cfgvalue(self, section)
1798 if val and fs.access(val) then
1804 function FileUpload.formvalue(self, section)
1805 local val = AbstractValue.formvalue(self, section)
1807 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1808 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1818 function FileUpload.remove(self, section)
1819 local val = AbstractValue.formvalue(self, section)
1820 if val and fs.access(val) then fs.unlink(val) end
1821 return AbstractValue.remove(self, section)
1825 FileBrowser = class(AbstractValue)
1827 function FileBrowser.__init__(self, ...)
1828 AbstractValue.__init__(self, ...)
1829 self.template = "cbi/browser"