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/"
64 assert(fs.stat(cbimap) or
65 fs.stat(cbidir..cbimap..".lua") or
66 fs.stat(cbidir..cbimap..".lua.gz"),
69 local func, err = loadfile(cbimap)
71 func, err = loadfile(cbidir..cbimap..".lua") or
72 loadfile(cbidir..cbimap..".lua.gz")
76 luci.i18n.loadc("cbi")
77 luci.i18n.loadc("uvl")
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._i18n(self, config, section, option, title, description)
228 if type(luci.i18n) == "table" then
230 local key = config and config:gsub("[^%w]+", "") or ""
232 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
233 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
235 self.title = title or luci.i18n.translate( key, option or section or config )
236 self.description = description or luci.i18n.translate( key .. "_desc", "" )
241 function Node.prepare(self, ...)
242 for k, child in ipairs(self.children) do
247 -- Append child nodes
248 function Node.append(self, obj)
249 table.insert(self.children, obj)
252 -- Parse this node and its children
253 function Node.parse(self, ...)
254 for k, child in ipairs(self.children) do
260 function Node.render(self, scope)
264 luci.template.render(self.template, scope)
267 -- Render the children
268 function Node.render_children(self, ...)
269 for k, node in ipairs(self.children) do
276 A simple template element
278 Template = class(Node)
280 function Template.__init__(self, template)
282 self.template = template
285 function Template.render(self)
286 luci.template.render(self.template, {self=self})
289 function Template.parse(self, readinput)
290 self.readinput = (readinput ~= false)
291 return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA
296 Map - A map describing a configuration file
300 function Map.__init__(self, config, ...)
301 Node.__init__(self, ...)
302 Node._i18n(self, config, nil, nil, ...)
305 self.parsechain = {self.config}
306 self.template = "cbi/map"
307 self.apply_on_parse = nil
308 self.readinput = true
312 self.uci = uci.cursor()
317 if not self.uci:load(self.config) then
318 error("Unable to read UCI data: " .. self.config)
321 self.validator = luci.uvl.UVL()
322 self.scheme = self.validator:get_scheme(self.config)
326 function Map.formvalue(self, key)
327 return self.readinput and luci.http.formvalue(key)
330 function Map.formvaluetable(self, key)
331 return self.readinput and luci.http.formvaluetable(key) or {}
334 function Map.get_scheme(self, sectiontype, option)
336 return self.scheme and self.scheme.sections[sectiontype]
338 return self.scheme and self.scheme.variables[sectiontype]
339 and self.scheme.variables[sectiontype][option]
343 function Map.submitstate(self)
344 return self:formvalue("cbi.submit")
347 -- Chain foreign config
348 function Map.chain(self, config)
349 table.insert(self.parsechain, config)
352 function Map.state_handler(self, state)
356 -- Use optimized UCI writing
357 function Map.parse(self, readinput, ...)
358 self.readinput = (readinput ~= false)
360 if self:formvalue("cbi.skip") then
361 self.state = FORM_SKIP
362 return self:state_handler(self.state)
365 Node.parse(self, ...)
368 for i, config in ipairs(self.parsechain) do
369 self.uci:save(config)
371 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
372 for i, config in ipairs(self.parsechain) do
373 self.uci:commit(config)
375 -- Refresh data because commit changes section names
376 self.uci:load(config)
378 if self.apply_on_parse then
379 self.uci:apply(self.parsechain)
381 self._apply = function()
382 local cmd = self.uci:apply(self.parsechain, true)
388 Node.parse(self, true)
391 for i, config in ipairs(self.parsechain) do
392 self.uci:unload(config)
394 if type(self.commit_handler) == "function" then
395 self:commit_handler(self:submitstate())
399 if self:submitstate() then
400 if not self.save then
401 self.state = FORM_INVALID
402 elseif self.proceed then
403 self.state = FORM_PROCEED
405 self.state = self.changed and FORM_CHANGED or FORM_VALID
408 self.state = FORM_NODATA
411 return self:state_handler(self.state)
414 function Map.render(self, ...)
415 Node.render(self, ...)
417 local fp = self._apply()
423 -- Creates a child section
424 function Map.section(self, class, ...)
425 if instanceof(class, AbstractSection) then
426 local obj = class(self, ...)
430 error("class must be a descendent of AbstractSection")
435 function Map.add(self, sectiontype)
436 return self.uci:add(self.config, sectiontype)
440 function Map.set(self, section, option, value)
442 return self.uci:set(self.config, section, option, value)
444 return self.uci:set(self.config, section, value)
449 function Map.del(self, section, option)
451 return self.uci:delete(self.config, section, option)
453 return self.uci:delete(self.config, section)
458 function Map.get(self, section, option)
460 return self.uci:get_all(self.config)
462 return self.uci:get(self.config, section, option)
464 return self.uci:get_all(self.config, section)
471 Compound = class(Node)
473 function Compound.__init__(self, ...)
475 self.template = "cbi/compound"
476 self.children = {...}
479 function Compound.populate_delegator(self, delegator)
480 for _, v in ipairs(self.children) do
481 v.delegator = delegator
485 function Compound.parse(self, ...)
486 local cstate, state = 0
488 for k, child in ipairs(self.children) do
489 cstate = child:parse(...)
490 state = (not state or cstate < state) and cstate or state
498 Delegator - Node controller
500 Delegator = class(Node)
501 function Delegator.__init__(self, ...)
502 Node.__init__(self, ...)
504 self.defaultpath = {}
505 self.pageaction = false
506 self.readinput = true
507 self.allow_reset = false
508 self.allow_back = false
509 self.allow_finish = false
510 self.template = "cbi/delegator"
513 function Delegator.set(self, name, node)
514 if type(node) == "table" and getmetatable(node) == nil then
515 node = Compound(unpack(node))
517 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
518 assert(not self.nodes[name], "Duplicate entry")
520 self.nodes[name] = node
523 function Delegator.add(self, name, node)
524 node = self:set(name, node)
525 self.defaultpath[#self.defaultpath+1] = name
528 function Delegator.insert_after(self, name, after)
529 local n = #self.chain
530 for k, v in ipairs(self.chain) do
536 table.insert(self.chain, n, name)
539 function Delegator.set_route(self, ...)
540 local n, chain, route = 0, self.chain, {...}
542 if chain[i] == self.current then
551 for i = n + 1, #chain do
556 function Delegator.get(self, name)
557 return self.nodes[name]
560 function Delegator.parse(self, ...)
562 self.chain = self.chain or self:get_chain()
563 self.current = self.current or self:get_active()
564 self.active = self.active or self:get(self.current)
565 assert(self.active, "Invalid state")
567 local stat = FORM_DONE
568 if type(self.active) ~= "function" then
569 self.active:populate_delegator(self)
570 stat = self.active:parse()
575 if stat > FORM_PROCEED then
576 if Map.formvalue(self, "cbi.delg.back") then
577 newcurrent = self:get_prev(self.current)
579 newcurrent = self:get_next(self.current)
583 if not Map.formvalue(self, "cbi.submit") then
585 elseif not newcurrent or not self:get(newcurrent) then
588 self.current = newcurrent
589 self.active = self:get(self.current)
590 if type(self.active) ~= "function" then
591 self.active:parse(false)
594 return self:parse(...)
599 function Delegator.get_next(self, state)
600 for k, v in ipairs(self.chain) do
602 return self.chain[k+1]
607 function Delegator.get_prev(self, state)
608 for k, v in ipairs(self.chain) do
610 return self.chain[k-1]
615 function Delegator.get_chain(self)
616 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
617 return type(x) == "table" and x or {x}
620 function Delegator.get_active(self)
621 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
629 Page.__init__ = Node.__init__
630 Page.parse = function() end
634 SimpleForm - A Simple non-UCI form
636 SimpleForm = class(Node)
638 function SimpleForm.__init__(self, config, title, description, data)
639 Node.__init__(self, title, description)
641 self.data = data or {}
642 self.template = "cbi/simpleform"
644 self.pageaction = false
645 self.readinput = true
648 SimpleForm.formvalue = Map.formvalue
649 SimpleForm.formvaluetable = Map.formvaluetable
651 function SimpleForm.parse(self, readinput, ...)
652 self.readinput = (readinput ~= false)
654 if self:formvalue("cbi.skip") then
658 if self:submitstate() then
659 Node.parse(self, 1, ...)
663 for k, j in ipairs(self.children) do
664 for i, v in ipairs(j.children) do
666 and (not v.tag_missing or not v.tag_missing[1])
667 and (not v.tag_invalid or not v.tag_invalid[1])
673 not self:submitstate() and FORM_NODATA
674 or valid and FORM_VALID
677 self.dorender = not self.handle
679 local nrender, nstate = self:handle(state, self.data)
680 self.dorender = self.dorender or (nrender ~= false)
681 state = nstate or state
686 function SimpleForm.render(self, ...)
687 if self.dorender then
688 Node.render(self, ...)
692 function SimpleForm.submitstate(self)
693 return self:formvalue("cbi.submit")
696 function SimpleForm.section(self, class, ...)
697 if instanceof(class, AbstractSection) then
698 local obj = class(self, ...)
702 error("class must be a descendent of AbstractSection")
706 -- Creates a child field
707 function SimpleForm.field(self, class, ...)
709 for k, v in ipairs(self.children) do
710 if instanceof(v, SimpleSection) then
716 section = self:section(SimpleSection)
719 if instanceof(class, AbstractValue) then
720 local obj = class(self, section, ...)
721 obj.track_missing = true
725 error("class must be a descendent of AbstractValue")
729 function SimpleForm.set(self, section, option, value)
730 self.data[option] = value
734 function SimpleForm.del(self, section, option)
735 self.data[option] = nil
739 function SimpleForm.get(self, section, option)
740 return self.data[option]
744 function SimpleForm.get_scheme()
749 Form = class(SimpleForm)
751 function Form.__init__(self, ...)
752 SimpleForm.__init__(self, ...)
760 AbstractSection = class(Node)
762 function AbstractSection.__init__(self, map, sectiontype, ...)
763 Node.__init__(self, ...)
764 self.sectiontype = sectiontype
766 self.config = map.config
771 self.tag_invalid = {}
772 self.tag_deperror = {}
776 self.addremove = false
780 -- Appends a new option
781 function AbstractSection.option(self, class, option, ...)
782 -- Autodetect from UVL
783 if class == true and self.map:get_scheme(self.sectiontype, option) then
784 local vs = self.map:get_scheme(self.sectiontype, option)
785 if vs.type == "boolean" then
787 elseif vs.type == "list" then
789 elseif vs.type == "enum" or vs.type == "reference" then
796 if instanceof(class, AbstractValue) then
797 local obj = class(self.map, self, option, ...)
799 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
802 self.fields[option] = obj
804 elseif class == true then
805 error("No valid class was given and autodetection failed.")
807 error("class must be a descendant of AbstractValue")
811 -- Parse optional options
812 function AbstractSection.parse_optionals(self, section)
813 if not self.optional then
817 self.optionals[section] = {}
819 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
820 for k,v in ipairs(self.children) do
821 if v.optional and not v:cfgvalue(section) then
822 if field == v.option then
824 self.map.proceed = true
826 table.insert(self.optionals[section], v)
831 if field and #field > 0 and self.dynamic then
832 self:add_dynamic(field)
836 -- Add a dynamic option
837 function AbstractSection.add_dynamic(self, field, optional)
838 local o = self:option(Value, field, field)
839 o.optional = optional
842 -- Parse all dynamic options
843 function AbstractSection.parse_dynamic(self, section)
844 if not self.dynamic then
848 local arr = luci.util.clone(self:cfgvalue(section))
849 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
850 for k, v in pairs(form) do
854 for key,val in pairs(arr) do
857 for i,c in ipairs(self.children) do
858 if c.option == key then
863 if create and key:sub(1, 1) ~= "." then
864 self.map.proceed = true
865 self:add_dynamic(key, true)
870 -- Returns the section's UCI table
871 function AbstractSection.cfgvalue(self, section)
872 return self.map:get(section)
876 function AbstractSection.push_events(self)
877 --luci.util.append(self.map.events, self.events)
878 self.map.changed = true
881 -- Removes the section
882 function AbstractSection.remove(self, section)
883 self.map.proceed = true
884 return self.map:del(section)
887 -- Creates the section
888 function AbstractSection.create(self, section)
892 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
894 section = self.map:add(self.sectiontype)
899 for k,v in pairs(self.children) do
901 self.map:set(section, v.option, v.default)
905 for k,v in pairs(self.defaults) do
906 self.map:set(section, k, v)
910 self.map.proceed = true
916 SimpleSection = class(AbstractSection)
918 function SimpleSection.__init__(self, form, ...)
919 AbstractSection.__init__(self, form, nil, ...)
920 self.template = "cbi/nullsection"
924 Table = class(AbstractSection)
926 function Table.__init__(self, form, data, ...)
927 local datasource = {}
929 datasource.config = "table"
930 self.data = data or {}
932 datasource.formvalue = Map.formvalue
933 datasource.formvaluetable = Map.formvaluetable
934 datasource.readinput = true
936 function datasource.get(self, section, option)
937 return tself.data[section] and tself.data[section][option]
940 function datasource.submitstate(self)
941 return Map.formvalue(self, "cbi.submit")
944 function datasource.del(...)
948 function datasource.get_scheme()
952 AbstractSection.__init__(self, datasource, "table", ...)
953 self.template = "cbi/tblsection"
954 self.rowcolors = true
955 self.anonymous = true
958 function Table.parse(self, readinput)
959 self.map.readinput = (readinput ~= false)
960 for i, k in ipairs(self:cfgsections()) do
961 if self.map:submitstate() then
967 function Table.cfgsections(self)
970 for i, v in luci.util.kspairs(self.data) do
971 table.insert(sections, i)
977 function Table.update(self, data)
984 NamedSection - A fixed configuration section defined by its name
986 NamedSection = class(AbstractSection)
988 function NamedSection.__init__(self, map, section, stype, ...)
989 AbstractSection.__init__(self, map, stype, ...)
990 Node._i18n(self, map.config, section, nil, ...)
993 self.addremove = false
995 -- Use defaults from UVL
996 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
997 local vs = self.map:get_scheme(self.sectiontype)
998 self.addremove = not vs.unique and not vs.required
999 self.dynamic = vs.dynamic
1000 self.title = self.title or vs.title
1001 self.description = self.description or vs.descr
1004 self.template = "cbi/nsection"
1005 self.section = section
1008 function NamedSection.parse(self, novld)
1009 local s = self.section
1010 local active = self:cfgvalue(s)
1012 if self.addremove then
1013 local path = self.config.."."..s
1014 if active then -- Remove the section
1015 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1019 else -- Create and apply default values
1020 if self.map:formvalue("cbi.cns."..path) then
1028 AbstractSection.parse_dynamic(self, s)
1029 if self.map:submitstate() then
1032 if not novld and not self.override_scheme and self.map.scheme then
1033 _uvl_validate_section(self, s)
1036 AbstractSection.parse_optionals(self, s)
1038 if self.changed then
1046 TypedSection - A (set of) configuration section(s) defined by the type
1047 addremove: Defines whether the user can add/remove sections of this type
1048 anonymous: Allow creating anonymous sections
1049 validate: a validation function returning nil if the section is invalid
1051 TypedSection = class(AbstractSection)
1053 function TypedSection.__init__(self, map, type, ...)
1054 AbstractSection.__init__(self, map, type, ...)
1055 Node._i18n(self, map.config, type, nil, ...)
1057 self.template = "cbi/tsection"
1059 self.anonymous = 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.anonymous = not vs.named
1067 self.title = self.title or vs.title
1068 self.description = self.description or vs.descr
1072 -- Return all matching UCI sections for this TypedSection
1073 function TypedSection.cfgsections(self)
1075 self.map.uci:foreach(self.map.config, self.sectiontype,
1077 if self:checkscope(section[".name"]) then
1078 table.insert(sections, section[".name"])
1085 -- Limits scope to sections that have certain option => value pairs
1086 function TypedSection.depends(self, option, value)
1087 table.insert(self.deps, {option=option, value=value})
1090 function TypedSection.parse(self, novld)
1091 if self.addremove then
1093 local crval = REMOVE_PREFIX .. self.config
1094 local name = self.map:formvaluetable(crval)
1095 for k,v in pairs(name) do
1096 if k:sub(-2) == ".x" then
1097 k = k:sub(1, #k - 2)
1099 if self:cfgvalue(k) and self:checkscope(k) then
1106 for i, k in ipairs(self:cfgsections()) do
1107 AbstractSection.parse_dynamic(self, k)
1108 if self.map:submitstate() then
1109 Node.parse(self, k, novld)
1111 if not novld and not self.override_scheme and self.map.scheme then
1112 _uvl_validate_section(self, k)
1115 AbstractSection.parse_optionals(self, k)
1118 if self.addremove then
1121 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1122 local name = self.map:formvalue(crval)
1123 if self.anonymous then
1125 created = self:create()
1129 -- Ignore if it already exists
1130 if self:cfgvalue(name) then
1134 name = self:checkscope(name)
1137 self.err_invalid = true
1140 if name and #name > 0 then
1141 created = self:create(name) and name
1143 self.invalid_cts = true
1150 AbstractSection.parse_optionals(self, created)
1154 if created or self.changed then
1159 -- Verifies scope of sections
1160 function TypedSection.checkscope(self, section)
1161 -- Check if we are not excluded
1162 if self.filter and not self:filter(section) then
1166 -- Check if at least one dependency is met
1167 if #self.deps > 0 and self:cfgvalue(section) then
1170 for k, v in ipairs(self.deps) do
1171 if self:cfgvalue(section)[v.option] == v.value then
1181 return self:validate(section)
1185 -- Dummy validate function
1186 function TypedSection.validate(self, section)
1192 AbstractValue - An abstract Value Type
1193 null: Value can be empty
1194 valid: A function returning the value if it is valid otherwise nil
1195 depends: A table of option => value pairs of which one must be true
1196 default: The default value
1197 size: The size of the input fields
1198 rmempty: Unset value if empty
1199 optional: This value is optional (see AbstractSection.optionals)
1201 AbstractValue = class(Node)
1203 function AbstractValue.__init__(self, map, section, option, ...)
1204 Node.__init__(self, ...)
1205 self.section = section
1206 self.option = option
1208 self.config = map.config
1209 self.tag_invalid = {}
1210 self.tag_missing = {}
1211 self.tag_reqerror = {}
1214 --self.cast = "string"
1216 self.track_missing = false
1220 self.optional = false
1223 function AbstractValue.prepare(self)
1224 -- Use defaults from UVL
1225 if not self.override_scheme
1226 and self.map:get_scheme(self.section.sectiontype, self.option) then
1227 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1228 if self.cast == nil then
1229 self.cast = (vs.type == "list") and "list" or "string"
1231 self.title = self.title or vs.title
1232 self.description = self.description or vs.descr
1233 if self.default == nil then
1234 self.default = vs.default
1237 if vs.depends and not self.override_dependencies then
1238 for i, deps in ipairs(vs.depends) do
1239 deps = _uvl_strip_remote_dependencies(deps)
1247 self.cast = self.cast or "string"
1250 -- Add a dependencie to another section field
1251 function AbstractValue.depends(self, field, value)
1253 if type(field) == "string" then
1260 table.insert(self.deps, {deps=deps, add=""})
1263 -- Generates the unique CBID
1264 function AbstractValue.cbid(self, section)
1265 return "cbid."..self.map.config.."."..section.."."..self.option
1268 -- Return whether this object should be created
1269 function AbstractValue.formcreated(self, section)
1270 local key = "cbi.opt."..self.config.."."..section
1271 return (self.map:formvalue(key) == self.option)
1274 -- Returns the formvalue for this object
1275 function AbstractValue.formvalue(self, section)
1276 return self.map:formvalue(self:cbid(section))
1279 function AbstractValue.additional(self, value)
1280 self.optional = value
1283 function AbstractValue.mandatory(self, value)
1284 self.rmempty = not value
1287 function AbstractValue.parse(self, section, novld)
1288 local fvalue = self:formvalue(section)
1289 local cvalue = self:cfgvalue(section)
1291 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1292 fvalue = self:transform(self:validate(fvalue, section))
1293 if not fvalue and not novld then
1295 self.error[section] = "invalid"
1297 self.error = { [section] = "invalid" }
1299 if self.section.error then
1300 table.insert(self.section.error[section], "invalid")
1302 self.section.error = {[section] = {"invalid"}}
1304 self.map.save = false
1306 if fvalue and not (fvalue == cvalue) then
1307 if self:write(section, fvalue) then
1309 self.section.changed = true
1310 --luci.util.append(self.map.events, self.events)
1313 else -- Unset the UCI or error
1314 if self.rmempty or self.optional then
1315 if self:remove(section) then
1317 self.section.changed = true
1318 --luci.util.append(self.map.events, self.events)
1320 elseif cvalue ~= fvalue and not novld then
1321 self:write(section, fvalue or "")
1323 self.error[section] = "missing"
1325 self.error = { [section] = "missing" }
1327 self.map.save = false
1332 -- Render if this value exists or if it is mandatory
1333 function AbstractValue.render(self, s, scope)
1334 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1337 scope.cbid = self:cbid(s)
1338 scope.striptags = luci.util.striptags
1340 scope.ifattr = function(cond,key,val)
1342 return string.format(
1343 ' %s="%s"', tostring(key),
1344 luci.util.pcdata(tostring( val
1346 or (type(self[key]) ~= "function" and self[key])
1354 scope.attr = function(...)
1355 return scope.ifattr( true, ... )
1358 Node.render(self, scope)
1362 -- Return the UCI value of this object
1363 function AbstractValue.cfgvalue(self, section)
1364 local value = self.map:get(section, self.option)
1367 elseif not self.cast or self.cast == type(value) then
1369 elseif self.cast == "string" then
1370 if type(value) == "table" then
1373 elseif self.cast == "table" then
1374 return luci.util.split(value, "%s+", nil, true)
1378 -- Validate the form value
1379 function AbstractValue.validate(self, value)
1383 AbstractValue.transform = AbstractValue.validate
1387 function AbstractValue.write(self, section, value)
1388 return self.map:set(section, self.option, value)
1392 function AbstractValue.remove(self, section)
1393 return self.map:del(section, self.option)
1400 Value - A one-line value
1401 maxlength: The maximum length
1403 Value = class(AbstractValue)
1405 function Value.__init__(self, ...)
1406 AbstractValue.__init__(self, ...)
1407 self.template = "cbi/value"
1412 function Value.value(self, key, val)
1414 table.insert(self.keylist, tostring(key))
1415 table.insert(self.vallist, tostring(val))
1419 -- DummyValue - This does nothing except being there
1420 DummyValue = class(AbstractValue)
1422 function DummyValue.__init__(self, ...)
1423 AbstractValue.__init__(self, ...)
1424 self.template = "cbi/dvalue"
1428 function DummyValue.cfgvalue(self, section)
1431 if type(self.value) == "function" then
1432 value = self:value(section)
1437 value = AbstractValue.cfgvalue(self, section)
1442 function DummyValue.parse(self)
1448 Flag - A flag being enabled or disabled
1450 Flag = class(AbstractValue)
1452 function Flag.__init__(self, ...)
1453 AbstractValue.__init__(self, ...)
1454 self.template = "cbi/fvalue"
1460 -- A flag can only have two states: set or unset
1461 function Flag.parse(self, section)
1462 local fvalue = self:formvalue(section)
1465 fvalue = self.enabled
1467 fvalue = self.disabled
1470 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1471 if not(fvalue == self:cfgvalue(section)) then
1472 self:write(section, fvalue)
1475 self:remove(section)
1482 ListValue - A one-line value predefined in a list
1483 widget: The widget that will be used (select, radio)
1485 ListValue = class(AbstractValue)
1487 function ListValue.__init__(self, ...)
1488 AbstractValue.__init__(self, ...)
1489 self.template = "cbi/lvalue"
1494 self.widget = "select"
1497 function ListValue.prepare(self, ...)
1498 AbstractValue.prepare(self, ...)
1499 if not self.override_scheme
1500 and self.map:get_scheme(self.section.sectiontype, self.option) then
1501 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1502 if self.value and vs.valuelist and not self.override_values then
1503 for k, v in ipairs(vs.valuelist) do
1505 if not self.override_dependencies
1506 and vs.enum_depends and vs.enum_depends[v.value] then
1507 for i, dep in ipairs(vs.enum_depends[v.value]) do
1508 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1511 self:value(v.value, v.title or v.value, unpack(deps))
1517 function ListValue.value(self, key, val, ...)
1518 if luci.util.contains(self.keylist, key) then
1523 table.insert(self.keylist, tostring(key))
1524 table.insert(self.vallist, tostring(val))
1526 for i, deps in ipairs({...}) do
1527 table.insert(self.deps, {add = "-"..key, deps=deps})
1531 function ListValue.validate(self, val)
1532 if luci.util.contains(self.keylist, val) then
1542 MultiValue - Multiple delimited values
1543 widget: The widget that will be used (select, checkbox)
1544 delimiter: The delimiter that will separate the values (default: " ")
1546 MultiValue = class(AbstractValue)
1548 function MultiValue.__init__(self, ...)
1549 AbstractValue.__init__(self, ...)
1550 self.template = "cbi/mvalue"
1555 self.widget = "checkbox"
1556 self.delimiter = " "
1559 function MultiValue.render(self, ...)
1560 if self.widget == "select" and not self.size then
1561 self.size = #self.vallist
1564 AbstractValue.render(self, ...)
1567 function MultiValue.value(self, key, val)
1568 if luci.util.contains(self.keylist, key) then
1573 table.insert(self.keylist, tostring(key))
1574 table.insert(self.vallist, tostring(val))
1577 function MultiValue.valuelist(self, section)
1578 local val = self:cfgvalue(section)
1580 if not(type(val) == "string") then
1584 return luci.util.split(val, self.delimiter)
1587 function MultiValue.validate(self, val)
1588 val = (type(val) == "table") and val or {val}
1592 for i, value in ipairs(val) do
1593 if luci.util.contains(self.keylist, value) then
1594 result = result and (result .. self.delimiter .. value) or value
1602 StaticList = class(MultiValue)
1604 function StaticList.__init__(self, ...)
1605 MultiValue.__init__(self, ...)
1607 self.valuelist = self.cfgvalue
1609 if not self.override_scheme
1610 and self.map:get_scheme(self.section.sectiontype, self.option) then
1611 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1612 if self.value and vs.values and not self.override_values then
1613 for k, v in pairs(vs.values) do
1620 function StaticList.validate(self, value)
1621 value = (type(value) == "table") and value or {value}
1624 for i, v in ipairs(value) do
1625 if luci.util.contains(self.keylist, v) then
1626 table.insert(valid, v)
1633 DynamicList = class(AbstractValue)
1635 function DynamicList.__init__(self, ...)
1636 AbstractValue.__init__(self, ...)
1637 self.template = "cbi/dynlist"
1643 function DynamicList.value(self, key, val)
1645 table.insert(self.keylist, tostring(key))
1646 table.insert(self.vallist, tostring(val))
1649 function DynamicList.write(self, ...)
1650 self.map.proceed = true
1651 return AbstractValue.write(self, ...)
1654 function DynamicList.formvalue(self, section)
1655 local value = AbstractValue.formvalue(self, section)
1656 value = (type(value) == "table") and value or {value}
1659 for i, v in ipairs(value) do
1661 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1662 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1663 table.insert(valid, v)
1672 TextValue - A multi-line value
1675 TextValue = class(AbstractValue)
1677 function TextValue.__init__(self, ...)
1678 AbstractValue.__init__(self, ...)
1679 self.template = "cbi/tvalue"
1685 Button = class(AbstractValue)
1687 function Button.__init__(self, ...)
1688 AbstractValue.__init__(self, ...)
1689 self.template = "cbi/button"
1690 self.inputstyle = nil
1695 FileUpload = class(AbstractValue)
1697 function FileUpload.__init__(self, ...)
1698 AbstractValue.__init__(self, ...)
1699 self.template = "cbi/upload"
1700 if not self.map.upload_fields then
1701 self.map.upload_fields = { self }
1703 self.map.upload_fields[#self.map.upload_fields+1] = self
1707 function FileUpload.formcreated(self, section)
1708 return AbstractValue.formcreated(self, section) or
1709 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1710 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1713 function FileUpload.cfgvalue(self, section)
1714 local val = AbstractValue.cfgvalue(self, section)
1715 if val and fs.access(val) then
1721 function FileUpload.formvalue(self, section)
1722 local val = AbstractValue.formvalue(self, section)
1724 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1725 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1735 function FileUpload.remove(self, section)
1736 local val = AbstractValue.formvalue(self, section)
1737 if val and fs.access(val) then fs.unlink(val) end
1738 return AbstractValue.remove(self, section)
1742 FileBrowser = class(AbstractValue)
1744 function FileBrowser.__init__(self, ...)
1745 AbstractValue.__init__(self, ...)
1746 self.template = "cbi/browser"