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 uci = require("luci.model.uci")
37 local class = util.class
38 local instanceof = util.instanceof
50 CREATE_PREFIX = "cbi.cts."
51 REMOVE_PREFIX = "cbi.rts."
53 -- Loads a CBI map from given file, creating an environment and returns it
54 function load(cbimap, ...)
56 local i18n = require "luci.i18n"
57 require("luci.config")
60 local upldir = "/lib/uci/upload/"
61 local cbidir = luci.util.libpath() .. "/model/cbi/"
63 assert(luci.fs.stat(cbimap) or
64 luci.fs.stat(cbidir..cbimap..".lua") or
65 luci.fs.stat(cbidir..cbimap..".lua.gz"),
68 local func, err = loadfile(cbimap)
70 func, err = loadfile(cbidir..cbimap..".lua") or
71 loadfile(cbidir..cbimap..".lua.gz")
75 luci.i18n.loadc("cbi")
76 luci.i18n.loadc("uvl")
79 translate=i18n.translate,
80 translatef=i18n.translatef,
84 setfenv(func, setmetatable(env, {__index =
86 return rawget(tbl, key) or _M[key] or _G[key]
89 local maps = { func() }
91 local has_upload = false
93 for i, map in ipairs(maps) do
94 if not instanceof(map, Node) then
95 error("CBI map returns no valid map object!")
99 if map.upload_fields then
101 for _, field in ipairs(map.upload_fields) do
103 field.config .. '.' ..
104 field.section.sectiontype .. '.' ..
113 local uci = luci.model.uci.cursor()
114 local prm = luci.http.context.request.message.params
117 luci.http.setfilehandler(
118 function( field, chunk, eof )
119 if not field then return end
120 if field.name and not cbid then
121 local c, s, o = field.name:gmatch(
122 "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)"
125 if c and s and o then
126 local t = uci:get( c, s )
127 if t and uploads[c.."."..t.."."..o] then
128 local path = upldir .. field.name
129 fd = io.open(path, "w")
138 if field.name == cbid and fd then
154 local function _uvl_validate_section(node, name)
155 local co = node.map:get()
157 luci.uvl.STRICT_UNKNOWN_OPTIONS = false
158 luci.uvl.STRICT_UNKNOWN_SECTIONS = false
160 local function tag_fields(e)
161 if e.option and node.fields[e.option] then
162 if node.fields[e.option].error then
163 node.fields[e.option].error[name] = e
165 node.fields[e.option].error = { [name] = e }
168 for _, c in ipairs(e.childs) do tag_fields(c) end
172 local function tag_section(e)
174 for _, c in ipairs(e.childs or { e }) do
175 if c.childs and not c:is(luci.uvl.errors.ERR_DEPENDENCY) then
176 table.insert( s, c.childs[1]:string() )
178 table.insert( s, c:string() )
185 node.error = { [name] = s }
190 local stat, err = node.map.validator:validate_section(node.config, name, co)
192 node.map.save = false
199 local function _uvl_strip_remote_dependencies(deps)
202 for k, v in pairs(deps) do
203 k = k:gsub("%$config%.%$section%.", "")
204 if k:match("^[%w_]+$") and type(v) == "string" then
213 -- Node pseudo abstract class
216 function Node.__init__(self, title, description)
218 self.title = title or ""
219 self.description = description or ""
220 self.template = "cbi/node"
224 function Node._i18n(self, config, section, option, title, description)
227 if type(luci.i18n) == "table" then
229 local key = config and config:gsub("[^%w]+", "") or ""
231 if section then key = key .. "_" .. section:lower():gsub("[^%w]+", "") end
232 if option then key = key .. "_" .. tostring(option):lower():gsub("[^%w]+", "") end
234 self.title = title or luci.i18n.translate( key, option or section or config )
235 self.description = description or luci.i18n.translate( key .. "_desc", "" )
240 function Node.prepare(self, ...)
241 for k, child in ipairs(self.children) do
246 -- Append child nodes
247 function Node.append(self, obj)
248 table.insert(self.children, obj)
251 -- Parse this node and its children
252 function Node.parse(self, ...)
253 for k, child in ipairs(self.children) do
259 function Node.render(self, scope)
263 luci.template.render(self.template, scope)
266 -- Render the children
267 function Node.render_children(self, ...)
268 for k, node in ipairs(self.children) do
275 A simple template element
277 Template = class(Node)
279 function Template.__init__(self, template)
281 self.template = template
284 function Template.render(self)
285 luci.template.render(self.template, {self=self})
290 Map - A map describing a configuration file
294 function Map.__init__(self, config, ...)
295 Node.__init__(self, ...)
296 Node._i18n(self, config, nil, nil, ...)
299 self.parsechain = {self.config}
300 self.template = "cbi/map"
301 self.apply_on_parse = nil
302 self.readinput = true
306 self.uci = uci.cursor()
311 if not self.uci:load(self.config) then
312 error("Unable to read UCI data: " .. self.config)
315 self.validator = luci.uvl.UVL()
316 self.scheme = self.validator:get_scheme(self.config)
320 function Map.formvalue(self, key)
321 return self.readinput and luci.http.formvalue(key)
324 function Map.formvaluetable(self, key)
325 return self.readinput and luci.http.formvaluetable(key) or {}
328 function Map.get_scheme(self, sectiontype, option)
330 return self.scheme and self.scheme.sections[sectiontype]
332 return self.scheme and self.scheme.variables[sectiontype]
333 and self.scheme.variables[sectiontype][option]
337 function Map.submitstate(self)
338 return self:formvalue("cbi.submit")
341 -- Chain foreign config
342 function Map.chain(self, config)
343 table.insert(self.parsechain, config)
346 function Map.state_handler(self, state)
350 -- Use optimized UCI writing
351 function Map.parse(self, readinput, ...)
352 self.readinput = (readinput ~= false)
354 if self:formvalue("cbi.skip") then
355 self.state = FORM_SKIP
356 return self:state_handler(self.state)
359 Node.parse(self, ...)
362 for i, config in ipairs(self.parsechain) do
363 self.uci:save(config)
365 if self:submitstate() and not self.proceed and (self.flow.autoapply or luci.http.formvalue("cbi.apply")) then
366 for i, config in ipairs(self.parsechain) do
367 self.uci:commit(config)
369 -- Refresh data because commit changes section names
370 self.uci:load(config)
372 if self.apply_on_parse then
373 self.uci:apply(self.parsechain)
375 self._apply = function()
376 local cmd = self.uci:apply(self.parsechain, true)
382 Node.parse(self, true)
385 for i, config in ipairs(self.parsechain) do
386 self.uci:unload(config)
388 if type(self.commit_handler) == "function" then
389 self:commit_handler(self:submitstate())
393 if self:submitstate() then
394 if not self.save then
395 self.state = FORM_INVALID
396 elseif self.proceed then
397 self.state = FORM_PROCEED
399 self.state = self.changed and FORM_CHANGED or FORM_VALID
402 self.state = FORM_NODATA
405 return self:state_handler(self.state)
408 function Map.render(self, ...)
409 Node.render(self, ...)
411 local fp = self._apply()
417 -- Creates a child section
418 function Map.section(self, class, ...)
419 if instanceof(class, AbstractSection) then
420 local obj = class(self, ...)
424 error("class must be a descendent of AbstractSection")
429 function Map.add(self, sectiontype)
430 return self.uci:add(self.config, sectiontype)
434 function Map.set(self, section, option, value)
436 return self.uci:set(self.config, section, option, value)
438 return self.uci:set(self.config, section, value)
443 function Map.del(self, section, option)
445 return self.uci:delete(self.config, section, option)
447 return self.uci:delete(self.config, section)
452 function Map.get(self, section, option)
454 return self.uci:get_all(self.config)
456 return self.uci:get(self.config, section, option)
458 return self.uci:get_all(self.config, section)
465 Compound = class(Node)
467 function Compound.__init__(self, ...)
469 self.template = "cbi/compound"
470 self.children = {...}
473 function Compound.populate_delegator(self, delegator)
474 for _, v in ipairs(self.children) do
475 v.delegator = delegator
479 function Compound.parse(self, ...)
480 local cstate, state = 0
482 for k, child in ipairs(self.children) do
483 cstate = child:parse(...)
484 state = (not state or cstate < state) and cstate or state
492 Delegator - Node controller
494 Delegator = class(Node)
495 function Delegator.__init__(self, ...)
496 Node.__init__(self, ...)
498 self.defaultpath = {}
499 self.pageaction = false
500 self.readinput = true
501 self.allow_back = false
502 self.allow_finish = false
503 self.template = "cbi/delegator"
506 function Delegator.set(self, name, node)
507 if type(node) == "table" and getmetatable(node) == nil then
508 node = Compound(unpack(node))
510 assert(type(node) == "function" or instanceof(node, Compound), "Invalid")
511 assert(not self.nodes[name], "Duplicate entry")
513 self.nodes[name] = node
516 function Delegator.add(self, name, node)
517 node = self:set(name, node)
518 self.defaultpath[#self.defaultpath+1] = name
521 function Delegator.insert_after(self, name, after)
522 local n = #self.chain
523 for k, v in ipairs(self.chain) do
529 table.insert(self.chain, n, name)
532 function Delegator.set_route(self, ...)
533 local n, chain, route = 0, self.chain, {...}
535 if chain[i] == self.current then
544 for i = n + 1, #chain do
549 function Delegator.get(self, name)
550 return self.nodes[name]
553 function Delegator.parse(self, ...)
555 self.chain = self.chain or self:get_chain()
556 self.current = self.current or self:get_active()
557 self.active = self.active or self:get(self.current)
558 assert(self.active, "Invalid state")
560 local stat = FORM_DONE
561 if type(self.active) ~= "function" then
562 self.active:populate_delegator(self)
563 stat = self.active:parse()
568 if stat > FORM_PROCEED then
569 if Map.formvalue(self, "cbi.delg.back") then
570 newcurrent = self:get_prev(self.current)
572 newcurrent = self:get_next(self.current)
576 if not newcurrent or not self:get(newcurrent) then
579 self.current = newcurrent
580 self.active = self:get(self.current)
581 if type(self.active) ~= "function" then
582 self.active:parse(false)
585 return self:parse(...)
590 function Delegator.get_next(self, state)
591 for k, v in ipairs(self.chain) do
593 return self.chain[k+1]
598 function Delegator.get_prev(self, state)
599 for k, v in ipairs(self.chain) do
601 return self.chain[k-1]
606 function Delegator.get_chain(self)
607 local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath
608 return type(x) == "table" and x or {x}
611 function Delegator.get_active(self)
612 return Map.formvalue(self, "cbi.delg.current") or self.chain[1]
620 Page.__init__ = Node.__init__
621 Page.parse = function() end
625 SimpleForm - A Simple non-UCI form
627 SimpleForm = class(Node)
629 function SimpleForm.__init__(self, config, title, description, data)
630 Node.__init__(self, title, description)
632 self.data = data or {}
633 self.template = "cbi/simpleform"
635 self.pageaction = false
636 self.readinput = true
639 SimpleForm.formvalue = Map.formvalue
640 SimpleForm.formvaluetable = Map.formvaluetable
642 function SimpleForm.parse(self, readinput, ...)
643 self.readinput = (readinput ~= false)
645 if self:formvalue("cbi.skip") then
649 if self:submitstate() then
650 Node.parse(self, 1, ...)
654 for k, j in ipairs(self.children) do
655 for i, v in ipairs(j.children) do
657 and (not v.tag_missing or not v.tag_missing[1])
658 and (not v.tag_invalid or not v.tag_invalid[1])
664 not self:submitstate() and FORM_NODATA
665 or valid and FORM_VALID
668 self.dorender = not self.handle
670 local nrender, nstate = self:handle(state, self.data)
671 self.dorender = self.dorender or (nrender ~= false)
672 state = nstate or state
677 function SimpleForm.render(self, ...)
678 if self.dorender then
679 Node.render(self, ...)
683 function SimpleForm.submitstate(self)
684 return self:formvalue("cbi.submit")
687 function SimpleForm.section(self, class, ...)
688 if instanceof(class, AbstractSection) then
689 local obj = class(self, ...)
693 error("class must be a descendent of AbstractSection")
697 -- Creates a child field
698 function SimpleForm.field(self, class, ...)
700 for k, v in ipairs(self.children) do
701 if instanceof(v, SimpleSection) then
707 section = self:section(SimpleSection)
710 if instanceof(class, AbstractValue) then
711 local obj = class(self, section, ...)
712 obj.track_missing = true
716 error("class must be a descendent of AbstractValue")
720 function SimpleForm.set(self, section, option, value)
721 self.data[option] = value
725 function SimpleForm.del(self, section, option)
726 self.data[option] = nil
730 function SimpleForm.get(self, section, option)
731 return self.data[option]
735 function SimpleForm.get_scheme()
740 Form = class(SimpleForm)
742 function Form.__init__(self, ...)
743 SimpleForm.__init__(self, ...)
751 AbstractSection = class(Node)
753 function AbstractSection.__init__(self, map, sectiontype, ...)
754 Node.__init__(self, ...)
755 self.sectiontype = sectiontype
757 self.config = map.config
762 self.tag_invalid = {}
763 self.tag_deperror = {}
767 self.addremove = false
771 -- Appends a new option
772 function AbstractSection.option(self, class, option, ...)
773 -- Autodetect from UVL
774 if class == true and self.map:get_scheme(self.sectiontype, option) then
775 local vs = self.map:get_scheme(self.sectiontype, option)
776 if vs.type == "boolean" then
778 elseif vs.type == "list" then
780 elseif vs.type == "enum" or vs.type == "reference" then
787 if instanceof(class, AbstractValue) then
788 local obj = class(self.map, self, option, ...)
790 Node._i18n(obj, self.config, self.section or self.sectiontype, option, ...)
793 self.fields[option] = obj
795 elseif class == true then
796 error("No valid class was given and autodetection failed.")
798 error("class must be a descendant of AbstractValue")
802 -- Parse optional options
803 function AbstractSection.parse_optionals(self, section)
804 if not self.optional then
808 self.optionals[section] = {}
810 local field = self.map:formvalue("cbi.opt."..self.config.."."..section)
811 for k,v in ipairs(self.children) do
812 if v.optional and not v:cfgvalue(section) then
813 if field == v.option then
815 self.map.proceed = true
817 table.insert(self.optionals[section], v)
822 if field and #field > 0 and self.dynamic then
823 self:add_dynamic(field)
827 -- Add a dynamic option
828 function AbstractSection.add_dynamic(self, field, optional)
829 local o = self:option(Value, field, field)
830 o.optional = optional
833 -- Parse all dynamic options
834 function AbstractSection.parse_dynamic(self, section)
835 if not self.dynamic then
839 local arr = luci.util.clone(self:cfgvalue(section))
840 local form = self.map:formvaluetable("cbid."..self.config.."."..section)
841 for k, v in pairs(form) do
845 for key,val in pairs(arr) do
848 for i,c in ipairs(self.children) do
849 if c.option == key then
854 if create and key:sub(1, 1) ~= "." then
855 self.map.proceed = true
856 self:add_dynamic(key, true)
861 -- Returns the section's UCI table
862 function AbstractSection.cfgvalue(self, section)
863 return self.map:get(section)
867 function AbstractSection.push_events(self)
868 --luci.util.append(self.map.events, self.events)
869 self.map.changed = true
872 -- Removes the section
873 function AbstractSection.remove(self, section)
874 self.map.proceed = true
875 return self.map:del(section)
878 -- Creates the section
879 function AbstractSection.create(self, section)
883 stat = section:match("^%w+$") and self.map:set(section, nil, self.sectiontype)
885 section = self.map:add(self.sectiontype)
890 for k,v in pairs(self.children) do
892 self.map:set(section, v.option, v.default)
896 for k,v in pairs(self.defaults) do
897 self.map:set(section, k, v)
901 self.map.proceed = true
907 SimpleSection = class(AbstractSection)
909 function SimpleSection.__init__(self, form, ...)
910 AbstractSection.__init__(self, form, nil, ...)
911 self.template = "cbi/nullsection"
915 Table = class(AbstractSection)
917 function Table.__init__(self, form, data, ...)
918 local datasource = {}
920 datasource.config = "table"
921 self.data = data or {}
923 datasource.formvalue = Map.formvalue
924 datasource.formvaluetable = Map.formvaluetable
925 datasource.readinput = true
927 function datasource.get(self, section, option)
928 return tself.data[section] and tself.data[section][option]
931 function datasource.submitstate(self)
932 return Map.formvalue(self, "cbi.submit")
935 function datasource.del(...)
939 function datasource.get_scheme()
943 AbstractSection.__init__(self, datasource, "table", ...)
944 self.template = "cbi/tblsection"
945 self.rowcolors = true
946 self.anonymous = true
949 function Table.parse(self, readinput)
950 self.map.readinput = (readinput ~= false)
951 for i, k in ipairs(self:cfgsections()) do
952 if self.map:submitstate() then
958 function Table.cfgsections(self)
961 for i, v in luci.util.kspairs(self.data) do
962 table.insert(sections, i)
968 function Table.update(self, data)
975 NamedSection - A fixed configuration section defined by its name
977 NamedSection = class(AbstractSection)
979 function NamedSection.__init__(self, map, section, stype, ...)
980 AbstractSection.__init__(self, map, stype, ...)
981 Node._i18n(self, map.config, section, nil, ...)
984 self.addremove = false
986 -- Use defaults from UVL
987 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
988 local vs = self.map:get_scheme(self.sectiontype)
989 self.addremove = not vs.unique and not vs.required
990 self.dynamic = vs.dynamic
991 self.title = self.title or vs.title
992 self.description = self.description or vs.descr
995 self.template = "cbi/nsection"
996 self.section = section
999 function NamedSection.parse(self, novld)
1000 local s = self.section
1001 local active = self:cfgvalue(s)
1003 if self.addremove then
1004 local path = self.config.."."..s
1005 if active then -- Remove the section
1006 if self.map:formvalue("cbi.rns."..path) and self:remove(s) then
1010 else -- Create and apply default values
1011 if self.map:formvalue("cbi.cns."..path) then
1019 AbstractSection.parse_dynamic(self, s)
1020 if self.map:submitstate() then
1023 if not novld and not self.override_scheme and self.map.scheme then
1024 _uvl_validate_section(self, s)
1027 AbstractSection.parse_optionals(self, s)
1029 if self.changed then
1037 TypedSection - A (set of) configuration section(s) defined by the type
1038 addremove: Defines whether the user can add/remove sections of this type
1039 anonymous: Allow creating anonymous sections
1040 validate: a validation function returning nil if the section is invalid
1042 TypedSection = class(AbstractSection)
1044 function TypedSection.__init__(self, map, type, ...)
1045 AbstractSection.__init__(self, map, type, ...)
1046 Node._i18n(self, map.config, type, nil, ...)
1048 self.template = "cbi/tsection"
1050 self.anonymous = false
1052 -- Use defaults from UVL
1053 if not self.override_scheme and self.map:get_scheme(self.sectiontype) then
1054 local vs = self.map:get_scheme(self.sectiontype)
1055 self.addremove = not vs.unique and not vs.required
1056 self.dynamic = vs.dynamic
1057 self.anonymous = not vs.named
1058 self.title = self.title or vs.title
1059 self.description = self.description or vs.descr
1063 -- Return all matching UCI sections for this TypedSection
1064 function TypedSection.cfgsections(self)
1066 self.map.uci:foreach(self.map.config, self.sectiontype,
1068 if self:checkscope(section[".name"]) then
1069 table.insert(sections, section[".name"])
1076 -- Limits scope to sections that have certain option => value pairs
1077 function TypedSection.depends(self, option, value)
1078 table.insert(self.deps, {option=option, value=value})
1081 function TypedSection.parse(self, novld)
1082 if self.addremove then
1084 local crval = REMOVE_PREFIX .. self.config
1085 local name = self.map:formvaluetable(crval)
1086 for k,v in pairs(name) do
1087 if k:sub(-2) == ".x" then
1088 k = k:sub(1, #k - 2)
1090 if self:cfgvalue(k) and self:checkscope(k) then
1097 for i, k in ipairs(self:cfgsections()) do
1098 AbstractSection.parse_dynamic(self, k)
1099 if self.map:submitstate() then
1100 Node.parse(self, k, novld)
1102 if not novld and not self.override_scheme and self.map.scheme then
1103 _uvl_validate_section(self, k)
1106 AbstractSection.parse_optionals(self, k)
1109 if self.addremove then
1112 local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype
1113 local name = self.map:formvalue(crval)
1114 if self.anonymous then
1116 created = self:create()
1120 -- Ignore if it already exists
1121 if self:cfgvalue(name) then
1125 name = self:checkscope(name)
1128 self.err_invalid = true
1131 if name and #name > 0 then
1132 created = self:create(name) and name
1134 self.invalid_cts = true
1141 AbstractSection.parse_optionals(self, created)
1145 if created or self.changed then
1150 -- Verifies scope of sections
1151 function TypedSection.checkscope(self, section)
1152 -- Check if we are not excluded
1153 if self.filter and not self:filter(section) then
1157 -- Check if at least one dependency is met
1158 if #self.deps > 0 and self:cfgvalue(section) then
1161 for k, v in ipairs(self.deps) do
1162 if self:cfgvalue(section)[v.option] == v.value then
1172 return self:validate(section)
1176 -- Dummy validate function
1177 function TypedSection.validate(self, section)
1183 AbstractValue - An abstract Value Type
1184 null: Value can be empty
1185 valid: A function returning the value if it is valid otherwise nil
1186 depends: A table of option => value pairs of which one must be true
1187 default: The default value
1188 size: The size of the input fields
1189 rmempty: Unset value if empty
1190 optional: This value is optional (see AbstractSection.optionals)
1192 AbstractValue = class(Node)
1194 function AbstractValue.__init__(self, map, section, option, ...)
1195 Node.__init__(self, ...)
1196 self.section = section
1197 self.option = option
1199 self.config = map.config
1200 self.tag_invalid = {}
1201 self.tag_missing = {}
1202 self.tag_reqerror = {}
1205 --self.cast = "string"
1207 self.track_missing = false
1211 self.optional = false
1214 function AbstractValue.prepare(self)
1215 -- Use defaults from UVL
1216 if not self.override_scheme
1217 and self.map:get_scheme(self.section.sectiontype, self.option) then
1218 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1219 if self.cast == nil then
1220 self.cast = (vs.type == "list") and "list" or "string"
1222 self.title = self.title or vs.title
1223 self.description = self.description or vs.descr
1224 if self.default == nil then
1225 self.default = vs.default
1228 if vs.depends and not self.override_dependencies then
1229 for i, deps in ipairs(vs.depends) do
1230 deps = _uvl_strip_remote_dependencies(deps)
1238 self.cast = self.cast or "string"
1241 -- Add a dependencie to another section field
1242 function AbstractValue.depends(self, field, value)
1244 if type(field) == "string" then
1251 table.insert(self.deps, {deps=deps, add=""})
1254 -- Generates the unique CBID
1255 function AbstractValue.cbid(self, section)
1256 return "cbid."..self.map.config.."."..section.."."..self.option
1259 -- Return whether this object should be created
1260 function AbstractValue.formcreated(self, section)
1261 local key = "cbi.opt."..self.config.."."..section
1262 return (self.map:formvalue(key) == self.option)
1265 -- Returns the formvalue for this object
1266 function AbstractValue.formvalue(self, section)
1267 return self.map:formvalue(self:cbid(section))
1270 function AbstractValue.additional(self, value)
1271 self.optional = value
1274 function AbstractValue.mandatory(self, value)
1275 self.rmempty = not value
1278 function AbstractValue.parse(self, section, novld)
1279 local fvalue = self:formvalue(section)
1280 local cvalue = self:cfgvalue(section)
1282 if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI
1283 fvalue = self:transform(self:validate(fvalue, section))
1284 if not fvalue and not novld then
1286 self.error[section] = "invalid"
1288 self.error = { [section] = "invalid" }
1290 if self.section.error then
1291 table.insert(self.section.error[section], "invalid")
1293 self.section.error = {[section] = {"invalid"}}
1295 self.map.save = false
1297 if fvalue and not (fvalue == cvalue) then
1298 if self:write(section, fvalue) then
1300 self.section.changed = true
1301 --luci.util.append(self.map.events, self.events)
1304 else -- Unset the UCI or error
1305 if self.rmempty or self.optional then
1306 if self:remove(section) then
1308 self.section.changed = true
1309 --luci.util.append(self.map.events, self.events)
1311 elseif cvalue ~= fvalue and not novld then
1312 self:write(section, fvalue or "")
1314 self.error[section] = "missing"
1316 self.error = { [section] = "missing" }
1318 self.map.save = false
1323 -- Render if this value exists or if it is mandatory
1324 function AbstractValue.render(self, s, scope)
1325 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1328 scope.cbid = self:cbid(s)
1329 scope.striptags = luci.util.striptags
1331 scope.ifattr = function(cond,key,val)
1333 return string.format(
1334 ' %s="%s"', tostring(key),
1335 luci.util.pcdata(tostring( val
1337 or (type(self[key]) ~= "function" and self[key])
1345 scope.attr = function(...)
1346 return scope.ifattr( true, ... )
1349 Node.render(self, scope)
1353 -- Return the UCI value of this object
1354 function AbstractValue.cfgvalue(self, section)
1355 local value = self.map:get(section, self.option)
1358 elseif not self.cast or self.cast == type(value) then
1360 elseif self.cast == "string" then
1361 if type(value) == "table" then
1364 elseif self.cast == "table" then
1365 return luci.util.split(value, "%s+", nil, true)
1369 -- Validate the form value
1370 function AbstractValue.validate(self, value)
1374 AbstractValue.transform = AbstractValue.validate
1378 function AbstractValue.write(self, section, value)
1379 return self.map:set(section, self.option, value)
1383 function AbstractValue.remove(self, section)
1384 return self.map:del(section, self.option)
1391 Value - A one-line value
1392 maxlength: The maximum length
1394 Value = class(AbstractValue)
1396 function Value.__init__(self, ...)
1397 AbstractValue.__init__(self, ...)
1398 self.template = "cbi/value"
1403 function Value.value(self, key, val)
1405 table.insert(self.keylist, tostring(key))
1406 table.insert(self.vallist, tostring(val))
1410 -- DummyValue - This does nothing except being there
1411 DummyValue = class(AbstractValue)
1413 function DummyValue.__init__(self, ...)
1414 AbstractValue.__init__(self, ...)
1415 self.template = "cbi/dvalue"
1419 function DummyValue.cfgvalue(self, section)
1422 if type(self.value) == "function" then
1423 value = self:value(section)
1428 value = AbstractValue.cfgvalue(self, section)
1433 function DummyValue.parse(self)
1439 Flag - A flag being enabled or disabled
1441 Flag = class(AbstractValue)
1443 function Flag.__init__(self, ...)
1444 AbstractValue.__init__(self, ...)
1445 self.template = "cbi/fvalue"
1451 -- A flag can only have two states: set or unset
1452 function Flag.parse(self, section)
1453 local fvalue = self:formvalue(section)
1456 fvalue = self.enabled
1458 fvalue = self.disabled
1461 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1462 if not(fvalue == self:cfgvalue(section)) then
1463 self:write(section, fvalue)
1466 self:remove(section)
1473 ListValue - A one-line value predefined in a list
1474 widget: The widget that will be used (select, radio)
1476 ListValue = class(AbstractValue)
1478 function ListValue.__init__(self, ...)
1479 AbstractValue.__init__(self, ...)
1480 self.template = "cbi/lvalue"
1485 self.widget = "select"
1488 function ListValue.prepare(self, ...)
1489 AbstractValue.prepare(self, ...)
1490 if not self.override_scheme
1491 and self.map:get_scheme(self.section.sectiontype, self.option) then
1492 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1493 if self.value and vs.valuelist and not self.override_values then
1494 for k, v in ipairs(vs.valuelist) do
1496 if not self.override_dependencies
1497 and vs.enum_depends and vs.enum_depends[v.value] then
1498 for i, dep in ipairs(vs.enum_depends[v.value]) do
1499 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1502 self:value(v.value, v.title or v.value, unpack(deps))
1508 function ListValue.value(self, key, val, ...)
1509 if luci.util.contains(self.keylist, key) then
1514 table.insert(self.keylist, tostring(key))
1515 table.insert(self.vallist, tostring(val))
1517 for i, deps in ipairs({...}) do
1518 table.insert(self.deps, {add = "-"..key, deps=deps})
1522 function ListValue.validate(self, val)
1523 if luci.util.contains(self.keylist, val) then
1533 MultiValue - Multiple delimited values
1534 widget: The widget that will be used (select, checkbox)
1535 delimiter: The delimiter that will separate the values (default: " ")
1537 MultiValue = class(AbstractValue)
1539 function MultiValue.__init__(self, ...)
1540 AbstractValue.__init__(self, ...)
1541 self.template = "cbi/mvalue"
1546 self.widget = "checkbox"
1547 self.delimiter = " "
1550 function MultiValue.render(self, ...)
1551 if self.widget == "select" and not self.size then
1552 self.size = #self.vallist
1555 AbstractValue.render(self, ...)
1558 function MultiValue.value(self, key, val)
1559 if luci.util.contains(self.keylist, key) then
1564 table.insert(self.keylist, tostring(key))
1565 table.insert(self.vallist, tostring(val))
1568 function MultiValue.valuelist(self, section)
1569 local val = self:cfgvalue(section)
1571 if not(type(val) == "string") then
1575 return luci.util.split(val, self.delimiter)
1578 function MultiValue.validate(self, val)
1579 val = (type(val) == "table") and val or {val}
1583 for i, value in ipairs(val) do
1584 if luci.util.contains(self.keylist, value) then
1585 result = result and (result .. self.delimiter .. value) or value
1593 StaticList = class(MultiValue)
1595 function StaticList.__init__(self, ...)
1596 MultiValue.__init__(self, ...)
1598 self.valuelist = self.cfgvalue
1600 if not self.override_scheme
1601 and self.map:get_scheme(self.section.sectiontype, self.option) then
1602 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1603 if self.value and vs.values and not self.override_values then
1604 for k, v in pairs(vs.values) do
1611 function StaticList.validate(self, value)
1612 value = (type(value) == "table") and value or {value}
1615 for i, v in ipairs(value) do
1616 if luci.util.contains(self.keylist, v) then
1617 table.insert(valid, v)
1624 DynamicList = class(AbstractValue)
1626 function DynamicList.__init__(self, ...)
1627 AbstractValue.__init__(self, ...)
1628 self.template = "cbi/dynlist"
1634 function DynamicList.value(self, key, val)
1636 table.insert(self.keylist, tostring(key))
1637 table.insert(self.vallist, tostring(val))
1640 function DynamicList.write(self, ...)
1641 self.map.proceed = true
1642 return AbstractValue.write(self, ...)
1645 function DynamicList.formvalue(self, section)
1646 local value = AbstractValue.formvalue(self, section)
1647 value = (type(value) == "table") and value or {value}
1650 for i, v in ipairs(value) do
1652 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1653 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1654 table.insert(valid, v)
1663 TextValue - A multi-line value
1666 TextValue = class(AbstractValue)
1668 function TextValue.__init__(self, ...)
1669 AbstractValue.__init__(self, ...)
1670 self.template = "cbi/tvalue"
1676 Button = class(AbstractValue)
1678 function Button.__init__(self, ...)
1679 AbstractValue.__init__(self, ...)
1680 self.template = "cbi/button"
1681 self.inputstyle = nil
1686 FileUpload = class(AbstractValue)
1688 function FileUpload.__init__(self, ...)
1689 AbstractValue.__init__(self, ...)
1690 self.template = "cbi/upload"
1691 if not self.map.upload_fields then
1692 self.map.upload_fields = { self }
1694 self.map.upload_fields[#self.map.upload_fields+1] = self
1698 function FileUpload.formcreated(self, section)
1699 return AbstractValue.formcreated(self, section) or
1700 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1701 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1704 function FileUpload.cfgvalue(self, section)
1705 local val = AbstractValue.cfgvalue(self, section)
1706 if val and luci.fs.access(val) then
1712 function FileUpload.formvalue(self, section)
1713 local val = AbstractValue.formvalue(self, section)
1715 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1716 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1726 function FileUpload.remove(self, section)
1727 local val = AbstractValue.formvalue(self, section)
1728 if val and luci.fs.access(val) then luci.fs.unlink(val) end
1729 return AbstractValue.remove(self, section)
1733 FileBrowser = class(AbstractValue)
1735 function FileBrowser.__init__(self, ...)
1736 AbstractValue.__init__(self, ...)
1737 self.template = "cbi/browser"