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 self.map.save = false
1292 if fvalue and not (fvalue == cvalue) then
1293 if self:write(section, fvalue) then
1295 self.section.changed = true
1296 --luci.util.append(self.map.events, self.events)
1299 else -- Unset the UCI or error
1300 if self.rmempty or self.optional then
1301 if self:remove(section) then
1303 self.section.changed = true
1304 --luci.util.append(self.map.events, self.events)
1306 elseif cvalue ~= fvalue and not novld then
1307 self:write(section, fvalue or "")
1309 self.error[section] = "missing"
1311 self.error = { [section] = "missing" }
1313 self.map.save = false
1318 -- Render if this value exists or if it is mandatory
1319 function AbstractValue.render(self, s, scope)
1320 if not self.optional or self:cfgvalue(s) or self:formcreated(s) then
1323 scope.cbid = self:cbid(s)
1324 scope.striptags = luci.util.striptags
1326 scope.ifattr = function(cond,key,val)
1328 return string.format(
1329 ' %s="%s"', tostring(key),
1330 luci.util.pcdata(tostring( val
1332 or (type(self[key]) ~= "function" and self[key])
1340 scope.attr = function(...)
1341 return scope.ifattr( true, ... )
1344 Node.render(self, scope)
1348 -- Return the UCI value of this object
1349 function AbstractValue.cfgvalue(self, section)
1350 local value = self.map:get(section, self.option)
1353 elseif not self.cast or self.cast == type(value) then
1355 elseif self.cast == "string" then
1356 if type(value) == "table" then
1359 elseif self.cast == "table" then
1360 return luci.util.split(value, "%s+", nil, true)
1364 -- Validate the form value
1365 function AbstractValue.validate(self, value)
1369 AbstractValue.transform = AbstractValue.validate
1373 function AbstractValue.write(self, section, value)
1374 return self.map:set(section, self.option, value)
1378 function AbstractValue.remove(self, section)
1379 return self.map:del(section, self.option)
1386 Value - A one-line value
1387 maxlength: The maximum length
1389 Value = class(AbstractValue)
1391 function Value.__init__(self, ...)
1392 AbstractValue.__init__(self, ...)
1393 self.template = "cbi/value"
1398 function Value.value(self, key, val)
1400 table.insert(self.keylist, tostring(key))
1401 table.insert(self.vallist, tostring(val))
1405 -- DummyValue - This does nothing except being there
1406 DummyValue = class(AbstractValue)
1408 function DummyValue.__init__(self, ...)
1409 AbstractValue.__init__(self, ...)
1410 self.template = "cbi/dvalue"
1414 function DummyValue.cfgvalue(self, section)
1417 if type(self.value) == "function" then
1418 value = self:value(section)
1423 value = AbstractValue.cfgvalue(self, section)
1428 function DummyValue.parse(self)
1434 Flag - A flag being enabled or disabled
1436 Flag = class(AbstractValue)
1438 function Flag.__init__(self, ...)
1439 AbstractValue.__init__(self, ...)
1440 self.template = "cbi/fvalue"
1446 -- A flag can only have two states: set or unset
1447 function Flag.parse(self, section)
1448 local fvalue = self:formvalue(section)
1451 fvalue = self.enabled
1453 fvalue = self.disabled
1456 if fvalue == self.enabled or (not self.optional and not self.rmempty) then
1457 if not(fvalue == self:cfgvalue(section)) then
1458 self:write(section, fvalue)
1461 self:remove(section)
1468 ListValue - A one-line value predefined in a list
1469 widget: The widget that will be used (select, radio)
1471 ListValue = class(AbstractValue)
1473 function ListValue.__init__(self, ...)
1474 AbstractValue.__init__(self, ...)
1475 self.template = "cbi/lvalue"
1480 self.widget = "select"
1483 function ListValue.prepare(self, ...)
1484 AbstractValue.prepare(self, ...)
1485 if not self.override_scheme
1486 and self.map:get_scheme(self.section.sectiontype, self.option) then
1487 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1488 if self.value and vs.valuelist and not self.override_values then
1489 for k, v in ipairs(vs.valuelist) do
1491 if not self.override_dependencies
1492 and vs.enum_depends and vs.enum_depends[v.value] then
1493 for i, dep in ipairs(vs.enum_depends[v.value]) do
1494 table.insert(deps, _uvl_strip_remote_dependencies(dep))
1497 self:value(v.value, v.title or v.value, unpack(deps))
1503 function ListValue.value(self, key, val, ...)
1504 if luci.util.contains(self.keylist, key) then
1509 table.insert(self.keylist, tostring(key))
1510 table.insert(self.vallist, tostring(val))
1512 for i, deps in ipairs({...}) do
1513 table.insert(self.deps, {add = "-"..key, deps=deps})
1517 function ListValue.validate(self, val)
1518 if luci.util.contains(self.keylist, val) then
1528 MultiValue - Multiple delimited values
1529 widget: The widget that will be used (select, checkbox)
1530 delimiter: The delimiter that will separate the values (default: " ")
1532 MultiValue = class(AbstractValue)
1534 function MultiValue.__init__(self, ...)
1535 AbstractValue.__init__(self, ...)
1536 self.template = "cbi/mvalue"
1541 self.widget = "checkbox"
1542 self.delimiter = " "
1545 function MultiValue.render(self, ...)
1546 if self.widget == "select" and not self.size then
1547 self.size = #self.vallist
1550 AbstractValue.render(self, ...)
1553 function MultiValue.value(self, key, val)
1554 if luci.util.contains(self.keylist, key) then
1559 table.insert(self.keylist, tostring(key))
1560 table.insert(self.vallist, tostring(val))
1563 function MultiValue.valuelist(self, section)
1564 local val = self:cfgvalue(section)
1566 if not(type(val) == "string") then
1570 return luci.util.split(val, self.delimiter)
1573 function MultiValue.validate(self, val)
1574 val = (type(val) == "table") and val or {val}
1578 for i, value in ipairs(val) do
1579 if luci.util.contains(self.keylist, value) then
1580 result = result and (result .. self.delimiter .. value) or value
1588 StaticList = class(MultiValue)
1590 function StaticList.__init__(self, ...)
1591 MultiValue.__init__(self, ...)
1593 self.valuelist = self.cfgvalue
1595 if not self.override_scheme
1596 and self.map:get_scheme(self.section.sectiontype, self.option) then
1597 local vs = self.map:get_scheme(self.section.sectiontype, self.option)
1598 if self.value and vs.values and not self.override_values then
1599 for k, v in pairs(vs.values) do
1606 function StaticList.validate(self, value)
1607 value = (type(value) == "table") and value or {value}
1610 for i, v in ipairs(value) do
1611 if luci.util.contains(self.keylist, v) then
1612 table.insert(valid, v)
1619 DynamicList = class(AbstractValue)
1621 function DynamicList.__init__(self, ...)
1622 AbstractValue.__init__(self, ...)
1623 self.template = "cbi/dynlist"
1629 function DynamicList.value(self, key, val)
1631 table.insert(self.keylist, tostring(key))
1632 table.insert(self.vallist, tostring(val))
1635 function DynamicList.write(self, ...)
1636 self.map.proceed = true
1637 return AbstractValue.write(self, ...)
1640 function DynamicList.formvalue(self, section)
1641 local value = AbstractValue.formvalue(self, section)
1642 value = (type(value) == "table") and value or {value}
1645 for i, v in ipairs(value) do
1647 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i)
1648 and not self.map:formvalue("cbi.rle."..section.."."..self.option.."."..i..".x") then
1649 table.insert(valid, v)
1658 TextValue - A multi-line value
1661 TextValue = class(AbstractValue)
1663 function TextValue.__init__(self, ...)
1664 AbstractValue.__init__(self, ...)
1665 self.template = "cbi/tvalue"
1671 Button = class(AbstractValue)
1673 function Button.__init__(self, ...)
1674 AbstractValue.__init__(self, ...)
1675 self.template = "cbi/button"
1676 self.inputstyle = nil
1681 FileUpload = class(AbstractValue)
1683 function FileUpload.__init__(self, ...)
1684 AbstractValue.__init__(self, ...)
1685 self.template = "cbi/upload"
1686 if not self.map.upload_fields then
1687 self.map.upload_fields = { self }
1689 self.map.upload_fields[#self.map.upload_fields+1] = self
1693 function FileUpload.formcreated(self, section)
1694 return AbstractValue.formcreated(self, section) or
1695 self.map:formvalue("cbi.rlf."..section.."."..self.option) or
1696 self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1699 function FileUpload.cfgvalue(self, section)
1700 local val = AbstractValue.cfgvalue(self, section)
1701 if val and luci.fs.access(val) then
1707 function FileUpload.formvalue(self, section)
1708 local val = AbstractValue.formvalue(self, section)
1710 if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
1711 not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
1721 function FileUpload.remove(self, section)
1722 local val = AbstractValue.formvalue(self, section)
1723 if val and luci.fs.access(val) then luci.fs.unlink(val) end
1724 return AbstractValue.remove(self, section)
1728 FileBrowser = class(AbstractValue)
1730 function FileBrowser.__init__(self, ...)
1731 AbstractValue.__init__(self, ...)
1732 self.template = "cbi/browser"