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})
291 Map - A map describing a configuration file
295 function Map.__init__(self, config, ...)
296 Node.__init__(self, ...)
297 Node._i18n(self, config, nil, nil, ...)
300 self.parsechain = {self.config}
301 self.template = "cbi/map"
302 self.apply_on_parse = nil
303 self.readinput = true
307 self.uci = uci.cursor()
312 if not self.uci:load(self.config) then
313 error("Unable to read UCI data: " .. self.config)
316 self.validator = luci.uvl.UVL()
317 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)
355 if self:formvalue("cbi.skip") then
356 self.state = FORM_SKIP
357 return self:state_handler(self.state)
360 Node.parse(self, ...)
363 for i, config in ipairs(self.parsechain) do
364 self.uci:save(config)
366 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
367 for i, config in ipairs(self.parsechain) do
368 self.uci:commit(config)
370 -- Refresh data because commit changes section names
371 self.uci:load(config)
373 if self.apply_on_parse then
374 self.uci:apply(self.parsechain)
376 self._apply = function()
377 local cmd = self.uci:apply(self.parsechain, true)
383 Node.parse(self, true)
386 for i, config in ipairs(self.parsechain) do
387 self.uci:unload(config)
389 if type(self.commit_handler) == "function" then
390 self:commit_handler(self:submitstate())
394 if self:submitstate() then
395 if not self.save then
396 self.state = FORM_INVALID
397 elseif self.proceed then
398 self.state = FORM_PROCEED
400 self.state = self.changed and FORM_CHANGED or FORM_VALID
403 self.state = FORM_NODATA
406 return self:state_handler(self.state)
409 function Map.render(self, ...)
410 Node.render(self, ...)
412 local fp = self._apply()
418 -- Creates a child section
419 function Map.section(self, class, ...)
420 if instanceof(class, AbstractSection) then
421 local obj = class(self, ...)
425 error("class must be a descendent of AbstractSection")
430 function Map.add(self, sectiontype)
431 return self.uci:add(self.config, sectiontype)
435 function Map.set(self, section, option, value)
437 return self.uci:set(self.config, section, option, value)
439 return self.uci:set(self.config, section, value)
444 function Map.del(self, section, option)
446 return self.uci:delete(self.config, section, option)
448 return self.uci:delete(self.config, section)
453 function Map.get(self, section, option)
455 return self.uci:get_all(self.config)
457 return self.uci:get(self.config, section, option)
459 return self.uci:get_all(self.config, section)
466 Compound = class(Node)
468 function Compound.__init__(self, ...)
470 self.template = "cbi/compound"
471 self.children = {...}
474 function Compound.populate_delegator(self, delegator)
475 for _, v in ipairs(self.children) do
476 v.delegator = delegator
480 function Compound.parse(self, ...)
481 local cstate, state = 0
483 for k, child in ipairs(self.children) do
484 cstate = child:parse(...)
485 state = (not state or cstate < state) and cstate or state
493 Delegator - Node controller
495 Delegator = class(Node)
496 function Delegator.__init__(self, ...)
497 Node.__init__(self, ...)
499 self.defaultpath = {}
500 self.pageaction = false
501 self.readinput = true
502 self.allow_back = false
503 self.allow_finish = false
504 self.template = "cbi/delegator"
507 function Delegator.set(self, name, node)
508 if type(node) == "table" and getmetatable(node) == nil then
509 node = Compound(unpack(node))
511 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
512 assert(not self.nodes[name], "Duplicate entry")
514 self.nodes[name] = node
517 function Delegator.add(self, name, node)
518 node = self:set(name, node)
519 self.defaultpath[#self.defaultpath+1] = name
522 function Delegator.insert_after(self, name, after)
523 local n = #self.chain
524 for k, v in ipairs(self.chain) do
530 table.insert(self.chain, n, name)
533 function Delegator.set_route(self, ...)
534 local n, chain, route = 0, self.chain, {...}
536 if chain[i] == self.current then
545 for i = n + 1, #chain do
550 function Delegator.get(self, name)
551 return self.nodes[name]
554 function Delegator.parse(self, ...)
556 self.chain = self.chain or self:get_chain()
557 self.current = self.current or self:get_active()
558 self.active = self.active or self:get(self.current)
559 assert(self.active, "Invalid state")
561 local stat = FORM_DONE
562 if type(self.active) ~= "function" then
563 self.active:populate_delegator(self)
564 stat = self.active:parse()
569 if stat > FORM_PROCEED then
570 if Map.formvalue(self, "cbi.delg.back") then
571 newcurrent = self:get_prev(self.current)
573 newcurrent = self:get_next(self.current)
577 if not newcurrent or not self:get(newcurrent) then
580 self.current = newcurrent
581 self.active = self:get(self.current)
582 if type(self.active) ~= "function" then
583 self.active:parse(false)
586 return self:parse(...)
591 function Delegator.get_next(self, state)
592 for k, v in ipairs(self.chain) do
594 return self.chain[k+1]
599 function Delegator.get_prev(self, state)
600 for k, v in ipairs(self.chain) do
602 return self.chain[k-1]
607 function Delegator.get_chain(self)
608 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
609 return type(x) == "table" and x or {x}
612 function Delegator.get_active(self)
613 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
621 Page.__init__ = Node.__init__
622 Page.parse = function() end
626 SimpleForm - A Simple non-UCI form
628 SimpleForm = class(Node)
630 function SimpleForm.__init__(self, config, title, description, data)
631 Node.__init__(self, title, description)
633 self.data = data or {}
634 self.template = "cbi/simpleform"
636 self.pageaction = false
637 self.readinput = true
640 SimpleForm.formvalue = Map.formvalue
641 SimpleForm.formvaluetable = Map.formvaluetable
643 function SimpleForm.parse(self, readinput, ...)
644 self.readinput = (readinput ~= false)
646 if self:formvalue("cbi.skip") then
650 if self:submitstate() then
651 Node.parse(self, 1, ...)
655 for k, j in ipairs(self.children) do
656 for i, v in ipairs(j.children) do
658 and (not v.tag_missing or not v.tag_missing[1])
659 and (not v.tag_invalid or not v.tag_invalid[1])
665 not self:submitstate() and FORM_NODATA
666 or valid and FORM_VALID
669 self.dorender = not self.handle
671 local nrender, nstate = self:handle(state, self.data)
672 self.dorender = self.dorender or (nrender ~= false)
673 state = nstate or state
678 function SimpleForm.render(self, ...)
679 if self.dorender then
680 Node.render(self, ...)
684 function SimpleForm.submitstate(self)
685 return self:formvalue("cbi.submit")
688 function SimpleForm.section(self, class, ...)
689 if instanceof(class, AbstractSection) then
690 local obj = class(self, ...)
694 error("class must be a descendent of AbstractSection")
698 -- Creates a child field
699 function SimpleForm.field(self, class, ...)
701 for k, v in ipairs(self.children) do
702 if instanceof(v, SimpleSection) then
708 section = self:section(SimpleSection)
711 if instanceof(class, AbstractValue) then
712 local obj = class(self, section, ...)
713 obj.track_missing = true
717 error("class must be a descendent of AbstractValue")
721 function SimpleForm.set(self, section, option, value)
722 self.data[option] = value
726 function SimpleForm.del(self, section, option)
727 self.data[option] = nil
731 function SimpleForm.get(self, section, option)
732 return self.data[option]
736 function SimpleForm.get_scheme()
741 Form = class(SimpleForm)
743 function Form.__init__(self, ...)
744 SimpleForm.__init__(self, ...)
752 AbstractSection = class(Node)
754 function AbstractSection.__init__(self, map, sectiontype, ...)
755 Node.__init__(self, ...)
756 self.sectiontype = sectiontype
758 self.config = map.config
763 self.tag_invalid = {}
764 self.tag_deperror = {}
768 self.addremove = false
772 -- Appends a new option
773 function AbstractSection.option(self, class, option, ...)
774 -- Autodetect from UVL
775 if class == true and self.map:get_scheme(self.sectiontype, option) then
776 local vs = self.map:get_scheme(self.sectiontype, option)
777 if vs.type == "boolean" then
779 elseif vs.type == "list" then
781 elseif vs.type == "enum" or vs.type == "reference" then
788 if instanceof(class, AbstractValue) then
789 local obj = class(self.map, self, option, ...)
791 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
794 self.fields[option] = obj
796 elseif class == true then
797 error("No valid class was given and autodetection failed.")
799 error("class must be a descendant of AbstractValue")
803 -- Parse optional options
804 function AbstractSection.parse_optionals(self, section)
805 if not self.optional then
809 self.optionals[section] = {}
811 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
812 for k,v in ipairs(self.children) do
813 if v.optional and not v:cfgvalue(section) then
814 if field == v.option then
816 self.map.proceed = true
818 table.insert(self.optionals[section], v)
823 if field and #field > 0 and self.dynamic then
824 self:add_dynamic(field)
828 -- Add a dynamic option
829 function AbstractSection.add_dynamic(self, field, optional)
830 local o = self:option(Value, field, field)
831 o.optional = optional
834 -- Parse all dynamic options
835 function AbstractSection.parse_dynamic(self, section)
836 if not self.dynamic then
840 local arr = luci.util.clone(self:cfgvalue(section))
841 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
842 for k, v in pairs(form) do
846 for key,val in pairs(arr) do
849 for i,c in ipairs(self.children) do
850 if c.option == key then
855 if create and key:sub(1, 1) ~= "." then
856 self.map.proceed = true
857 self:add_dynamic(key, true)
862 -- Returns the section's UCI table
863 function AbstractSection.cfgvalue(self, section)
864 return self.map:get(section)
868 function AbstractSection.push_events(self)
869 --luci.util.append(self.map.events, self.events)
870 self.map.changed = true
873 -- Removes the section
874 function AbstractSection.remove(self, section)
875 self.map.proceed = true
876 return self.map:del(section)
879 -- Creates the section
880 function AbstractSection.create(self, section)
884 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
886 section = self.map:add(self.sectiontype)
891 for k,v in pairs(self.children) do
893 self.map:set(section, v.option, v.default)
897 for k,v in pairs(self.defaults) do
898 self.map:set(section, k, v)
902 self.map.proceed = true
908 SimpleSection = class(AbstractSection)
910 function SimpleSection.__init__(self, form, ...)
911 AbstractSection.__init__(self, form, nil, ...)
912 self.template = "cbi/nullsection"
916 Table = class(AbstractSection)
918 function Table.__init__(self, form, data, ...)
919 local datasource = {}
921 datasource.config = "table"
922 self.data = data or {}
924 datasource.formvalue = Map.formvalue
925 datasource.formvaluetable = Map.formvaluetable
926 datasource.readinput = true
928 function datasource.get(self, section, option)
929 return tself.data[section] and tself.data[section][option]
932 function datasource.submitstate(self)
933 return Map.formvalue(self, "cbi.submit")
936 function datasource.del(...)
940 function datasource.get_scheme()
944 AbstractSection.__init__(self, datasource, "table", ...)
945 self.template = "cbi/tblsection"
946 self.rowcolors = true
947 self.anonymous = true
950 function Table.parse(self, readinput)
951 self.map.readinput = (readinput ~= false)
952 for i, k in ipairs(self:cfgsections()) do
953 if self.map:submitstate() then
959 function Table.cfgsections(self)
962 for i, v in luci.util.kspairs(self.data) do
963 table.insert(sections, i)
969 function Table.update(self, data)
976 NamedSection - A fixed configuration section defined by its name
978 NamedSection = class(AbstractSection)
980 function NamedSection.__init__(self, map, section, stype, ...)
981 AbstractSection.__init__(self, map, stype, ...)
982 Node._i18n(self, map.config, section, nil, ...)
985 self.addremove = false
987 -- Use defaults from UVL
988 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
989 local vs = self.map:get_scheme(self.sectiontype)
990 self.addremove = not vs.unique and not vs.required
991 self.dynamic = vs.dynamic
992 self.title = self.title or vs.title
993 self.description = self.description or vs.descr
996 self.template = "cbi/nsection"
997 self.section = section
1000 function NamedSection.parse(self, novld)
1001 local s = self.section
1002 local active = self:cfgvalue(s)
1004 if self.addremove then
1005 local path = self.config.."."..s
1006 if active then -- Remove the section
1007 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1011 else -- Create and apply default values
1012 if self.map:formvalue("cbi.cns."..path) then
1020 AbstractSection.parse_dynamic(self, s)
1021 if self.map:submitstate() then
1024 if not novld and not self.override_scheme and self.map.scheme then
1025 _uvl_validate_section(self, s)
1028 AbstractSection.parse_optionals(self, s)
1030 if self.changed then
1038 TypedSection - A (set of) configuration section(s) defined by the type
1039 addremove: Defines whether the user can add/remove sections of this type
1040 anonymous: Allow creating anonymous sections
1041 validate: a validation function returning nil if the section is invalid
1043 TypedSection = class(AbstractSection)
1045 function TypedSection.__init__(self, map, type, ...)
1046 AbstractSection.__init__(self, map, type, ...)
1047 Node._i18n(self, map.config, type, nil, ...)
1049 self.template = "cbi/tsection"
1051 self.anonymous = false
1053 -- Use defaults from UVL
1054 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1055 local vs = self.map:get_scheme(self.sectiontype)
1056 self.addremove = not vs.unique and not vs.required
1057 self.dynamic = vs.dynamic
1058 self.anonymous = not vs.named
1059 self.title = self.title or vs.title
1060 self.description = self.description or vs.descr
1064 -- Return all matching UCI sections for this TypedSection
1065 function TypedSection.cfgsections(self)
1067 self.map.uci:foreach(self.map.config, self.sectiontype,
1069 if self:checkscope(section[".name"]) then
1070 table.insert(sections, section[".name"])
1077 -- Limits scope to sections that have certain option => value pairs
1078 function TypedSection.depends(self, option, value)
1079 table.insert(self.deps, {option=option, value=value})
1082 function TypedSection.parse(self, novld)
1083 if self.addremove then
1085 local crval = REMOVE_PREFIX .. self.config
1086 local name = self.map:formvaluetable(crval)
1087 for k,v in pairs(name) do
1088 if k:sub(-2) == ".x" then
1089 k = k:sub(1, #k - 2)
1091 if self:cfgvalue(k) and self:checkscope(k) then
1098 for i, k in ipairs(self:cfgsections()) do
1099 AbstractSection.parse_dynamic(self, k)
1100 if self.map:submitstate() then
1101 Node.parse(self, k, novld)
1103 if not novld and not self.override_scheme and self.map.scheme then
1104 _uvl_validate_section(self, k)
1107 AbstractSection.parse_optionals(self, k)
1110 if self.addremove then
1113 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1114 local name = self.map:formvalue(crval)
1115 if self.anonymous then
1117 created = self:create()
1121 -- Ignore if it already exists
1122 if self:cfgvalue(name) then
1126 name = self:checkscope(name)
1129 self.err_invalid = true
1132 if name and #name > 0 then
1133 created = self:create(name) and name
1135 self.invalid_cts = true
1142 AbstractSection.parse_optionals(self, created)
1146 if created or self.changed then
1151 -- Verifies scope of sections
1152 function TypedSection.checkscope(self, section)
1153 -- Check if we are not excluded
1154 if self.filter and not self:filter(section) then
1158 -- Check if at least one dependency is met
1159 if #self.deps > 0 and self:cfgvalue(section) then
1162 for k, v in ipairs(self.deps) do
1163 if self:cfgvalue(section)[v.option] == v.value then
1173 return self:validate(section)
1177 -- Dummy validate function
1178 function TypedSection.validate(self, section)
1184 AbstractValue - An abstract Value Type
1185 null: Value can be empty
1186 valid: A function returning the value if it is valid otherwise nil
1187 depends: A table of option => value pairs of which one must be true
1188 default: The default value
1189 size: The size of the input fields
1190 rmempty: Unset value if empty
1191 optional: This value is optional (see AbstractSection.optionals)
1193 AbstractValue = class(Node)
1195 function AbstractValue.__init__(self, map, section, option, ...)
1196 Node.__init__(self, ...)
1197 self.section = section
1198 self.option = option
1200 self.config = map.config
1201 self.tag_invalid = {}
1202 self.tag_missing = {}
1203 self.tag_reqerror = {}
1206 --self.cast = "string"
1208 self.track_missing = false
1212 self.optional = false
1215 function AbstractValue.prepare(self)
1216 -- Use defaults from UVL
1217 if not self.override_scheme
1218 and self.map:get_scheme(self.section.sectiontype, self.option) then
1219 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1220 if self.cast == nil then
1221 self.cast = (vs.type == "list") and "list" or "string"
1223 self.title = self.title or vs.title
1224 self.description = self.description or vs.descr
1225 if self.default == nil then
1226 self.default = vs.default
1229 if vs.depends and not self.override_dependencies then
1230 for i, deps in ipairs(vs.depends) do
1231 deps = _uvl_strip_remote_dependencies(deps)
1239 self.cast = self.cast or "string"
1242 -- Add a dependencie to another section field
1243 function AbstractValue.depends(self, field, value)
1245 if type(field) == "string" then
1252 table.insert(self.deps, {deps=deps, add=""})
1255 -- Generates the unique CBID
1256 function AbstractValue.cbid(self, section)
1257 return "cbid."..self.map.config.."."..section.."."..self.option
1260 -- Return whether this object should be created
1261 function AbstractValue.formcreated(self, section)
1262 local key = "cbi.opt."..self.config.."."..section
1263 return (self.map:formvalue(key) == self.option)
1266 -- Returns the formvalue for this object
1267 function AbstractValue.formvalue(self, section)
1268 return self.map:formvalue(self:cbid(section))
1271 function AbstractValue.additional(self, value)
1272 self.optional = value
1275 function AbstractValue.mandatory(self, value)
1276 self.rmempty = not value
1279 function AbstractValue.parse(self, section, novld)
1280 local fvalue = self:formvalue(section)
1281 local cvalue = self:cfgvalue(section)
1283 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1284 fvalue = self:transform(self:validate(fvalue, section))
1285 if not fvalue and not novld then
1287 self.error[section] = "invalid"
1289 self.error = { [section] = "invalid" }
1291 if self.section.error then
1292 table.insert(self.section.error[section], "invalid")
1294 self.section.error = {[section] = {"invalid"}}
1296 self.map.save = false
1298 if fvalue and not (fvalue == cvalue) then
1299 if self:write(section, fvalue) then
1301 self.section.changed = true
1302 --luci.util.append(self.map.events, self.events)
1305 else -- Unset the UCI or error
1306 if self.rmempty or self.optional then
1307 if self:remove(section) then
1309 self.section.changed = true
1310 --luci.util.append(self.map.events, self.events)
1312 elseif cvalue ~= fvalue and not novld then
1313 self:write(section, fvalue or "")
1315 self.error[section] = "missing"
1317 self.error = { [section] = "missing" }
1319 self.map.save = false
1324 -- Render if this value exists or if it is mandatory
1325 function AbstractValue.render(self, s, scope)
1326 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1329 scope.cbid = self:cbid(s)
1330 scope.striptags = luci.util.striptags
1332 scope.ifattr = function(cond,key,val)
1334 return string.format(
1335 ' %s="%s"', tostring(key),
1336 luci.util.pcdata(tostring( val
1338 or (type(self[key]) ~= "function" and self[key])
1346 scope.attr = function(...)
1347 return scope.ifattr( true, ... )
1350 Node.render(self, scope)
1354 -- Return the UCI value of this object
1355 function AbstractValue.cfgvalue(self, section)
1356 local value = self.map:get(section, self.option)
1359 elseif not self.cast or self.cast == type(value) then
1361 elseif self.cast == "string" then
1362 if type(value) == "table" then
1365 elseif self.cast == "table" then
1366 return luci.util.split(value, "%s+", nil, true)
1370 -- Validate the form value
1371 function AbstractValue.validate(self, value)
1375 AbstractValue.transform = AbstractValue.validate
1379 function AbstractValue.write(self, section, value)
1380 return self.map:set(section, self.option, value)
1384 function AbstractValue.remove(self, section)
1385 return self.map:del(section, self.option)
1392 Value - A one-line value
1393 maxlength: The maximum length
1395 Value = class(AbstractValue)
1397 function Value.__init__(self, ...)
1398 AbstractValue.__init__(self, ...)
1399 self.template = "cbi/value"
1404 function Value.value(self, key, val)
1406 table.insert(self.keylist, tostring(key))
1407 table.insert(self.vallist, tostring(val))
1411 -- DummyValue - This does nothing except being there
1412 DummyValue = class(AbstractValue)
1414 function DummyValue.__init__(self, ...)
1415 AbstractValue.__init__(self, ...)
1416 self.template = "cbi/dvalue"
1420 function DummyValue.cfgvalue(self, section)
1423 if type(self.value) == "function" then
1424 value = self:value(section)
1429 value = AbstractValue.cfgvalue(self, section)
1434 function DummyValue.parse(self)
1440 Flag - A flag being enabled or disabled
1442 Flag = class(AbstractValue)
1444 function Flag.__init__(self, ...)
1445 AbstractValue.__init__(self, ...)
1446 self.template = "cbi/fvalue"
1452 -- A flag can only have two states: set or unset
1453 function Flag.parse(self, section)
1454 local fvalue = self:formvalue(section)
1457 fvalue = self.enabled
1459 fvalue = self.disabled
1462 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1463 if not(fvalue == self:cfgvalue(section)) then
1464 self:write(section, fvalue)
1467 self:remove(section)
1474 ListValue - A one-line value predefined in a list
1475 widget: The widget that will be used (select, radio)
1477 ListValue = class(AbstractValue)
1479 function ListValue.__init__(self, ...)
1480 AbstractValue.__init__(self, ...)
1481 self.template = "cbi/lvalue"
1486 self.widget = "select"
1489 function ListValue.prepare(self, ...)
1490 AbstractValue.prepare(self, ...)
1491 if not self.override_scheme
1492 and self.map:get_scheme(self.section.sectiontype, self.option) then
1493 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1494 if self.value and vs.valuelist and not self.override_values then
1495 for k, v in ipairs(vs.valuelist) do
1497 if not self.override_dependencies
1498 and vs.enum_depends and vs.enum_depends[v.value] then
1499 for i, dep in ipairs(vs.enum_depends[v.value]) do
1500 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1503 self:value(v.value, v.title or v.value, unpack(deps))
1509 function ListValue.value(self, key, val, ...)
1510 if luci.util.contains(self.keylist, key) then
1515 table.insert(self.keylist, tostring(key))
1516 table.insert(self.vallist, tostring(val))
1518 for i, deps in ipairs({...}) do
1519 table.insert(self.deps, {add = "-"..key, deps=deps})
1523 function ListValue.validate(self, val)
1524 if luci.util.contains(self.keylist, val) then
1534 MultiValue - Multiple delimited values
1535 widget: The widget that will be used (select, checkbox)
1536 delimiter: The delimiter that will separate the values (default: " ")
1538 MultiValue = class(AbstractValue)
1540 function MultiValue.__init__(self, ...)
1541 AbstractValue.__init__(self, ...)
1542 self.template = "cbi/mvalue"
1547 self.widget = "checkbox"
1548 self.delimiter = " "
1551 function MultiValue.render(self, ...)
1552 if self.widget == "select" and not self.size then
1553 self.size = #self.vallist
1556 AbstractValue.render(self, ...)
1559 function MultiValue.value(self, key, val)
1560 if luci.util.contains(self.keylist, key) then
1565 table.insert(self.keylist, tostring(key))
1566 table.insert(self.vallist, tostring(val))
1569 function MultiValue.valuelist(self, section)
1570 local val = self:cfgvalue(section)
1572 if not(type(val) == "string") then
1576 return luci.util.split(val, self.delimiter)
1579 function MultiValue.validate(self, val)
1580 val = (type(val) == "table") and val or {val}
1584 for i, value in ipairs(val) do
1585 if luci.util.contains(self.keylist, value) then
1586 result = result and (result .. self.delimiter .. value) or value
1594 StaticList = class(MultiValue)
1596 function StaticList.__init__(self, ...)
1597 MultiValue.__init__(self, ...)
1599 self.valuelist = self.cfgvalue
1601 if not self.override_scheme
1602 and self.map:get_scheme(self.section.sectiontype, self.option) then
1603 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1604 if self.value and vs.values and not self.override_values then
1605 for k, v in pairs(vs.values) do
1612 function StaticList.validate(self, value)
1613 value = (type(value) == "table") and value or {value}
1616 for i, v in ipairs(value) do
1617 if luci.util.contains(self.keylist, v) then
1618 table.insert(valid, v)
1625 DynamicList = class(AbstractValue)
1627 function DynamicList.__init__(self, ...)
1628 AbstractValue.__init__(self, ...)
1629 self.template = "cbi/dynlist"
1635 function DynamicList.value(self, key, val)
1637 table.insert(self.keylist, tostring(key))
1638 table.insert(self.vallist, tostring(val))
1641 function DynamicList.write(self, ...)
1642 self.map.proceed = true
1643 return AbstractValue.write(self, ...)
1646 function DynamicList.formvalue(self, section)
1647 local value = AbstractValue.formvalue(self, section)
1648 value = (type(value) == "table") and value or {value}
1651 for i, v in ipairs(value) do
1653 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1654 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1655 table.insert(valid, v)
1664 TextValue - A multi-line value
1667 TextValue = class(AbstractValue)
1669 function TextValue.__init__(self, ...)
1670 AbstractValue.__init__(self, ...)
1671 self.template = "cbi/tvalue"
1677 Button = class(AbstractValue)
1679 function Button.__init__(self, ...)
1680 AbstractValue.__init__(self, ...)
1681 self.template = "cbi/button"
1682 self.inputstyle = nil
1687 FileUpload = class(AbstractValue)
1689 function FileUpload.__init__(self, ...)
1690 AbstractValue.__init__(self, ...)
1691 self.template = "cbi/upload"
1692 if not self.map.upload_fields then
1693 self.map.upload_fields = { self }
1695 self.map.upload_fields[#self.map.upload_fields+1] = self
1699 function FileUpload.formcreated(self, section)
1700 return AbstractValue.formcreated(self, section) or
1701 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1702 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1705 function FileUpload.cfgvalue(self, section)
1706 local val = AbstractValue.cfgvalue(self, section)
1707 if val and fs.access(val) then
1713 function FileUpload.formvalue(self, section)
1714 local val = AbstractValue.formvalue(self, section)
1716 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1717 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1727 function FileUpload.remove(self, section)
1728 local val = AbstractValue.formvalue(self, section)
1729 if val and fs.access(val) then fs.unlink(val) end
1730 return AbstractValue.remove(self, section)
1734 FileBrowser = class(AbstractValue)
1736 function FileBrowser.__init__(self, ...)
1737 AbstractValue.__init__(self, ...)
1738 self.template = "cbi/browser"